blob: e248222dcc8a5a38d4cddf94f8ce2876901096b7 [file] [log] [blame]
// Copyright 2017 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.
cr.define('print_preview', function() {
/**
* |key| is the field in the serialized settings state that corresponds to the
* setting, or an empty string if the setting should not be saved in the
* serialized state.
* @typedef {{
* value: *,
* unavailableValue: *,
* valid: boolean,
* available: boolean,
* setByPolicy: boolean,
* setFromUi: boolean,
* key: string,
* updatesPreview: boolean,
* }}
*/
let Setting;
/**
* @typedef {{
* pages: !print_preview.Setting,
* copies: !print_preview.Setting,
* collate: !print_preview.Setting,
* layout: !print_preview.Setting,
* color: !print_preview.Setting,
* mediaSize: !print_preview.Setting,
* margins: !print_preview.Setting,
* dpi: !print_preview.Setting,
* scaling: !print_preview.Setting,
* scalingType: !print_preview.Setting,
* scalingTypePdf: !print_preview.Setting,
* duplex: !print_preview.Setting,
* duplexShortEdge: !print_preview.Setting,
* cssBackground: !print_preview.Setting,
* selectionOnly: !print_preview.Setting,
* headerFooter: !print_preview.Setting,
* rasterize: !print_preview.Setting,
* vendorItems: !print_preview.Setting,
* otherOptions: !print_preview.Setting,
* ranges: !print_preview.Setting,
* pagesPerSheet: !print_preview.Setting,
* pin: (print_preview.Setting|undefined),
* pinValue: (print_preview.Setting|undefined),
* }}
*/
let Settings;
/**
* @typedef {{
* version: string,
* recentDestinations: (!Array<!print_preview.RecentDestination> |
* undefined),
* dpi: ({horizontal_dpi: number,
* vertical_dpi: number,
* is_default: (boolean | undefined)} | undefined),
* mediaSize: ({height_microns: number,
* width_microns: number,
* custom_display_name: (string | undefined),
* is_default: (boolean | undefined)} | undefined),
* marginsType: (print_preview.MarginsType | undefined),
* customMargins: (print_preview.MarginsSetting | undefined),
* isColorEnabled: (boolean | undefined),
* isDuplexEnabled: (boolean | undefined),
* isHeaderFooterEnabled: (boolean | undefined),
* isLandscapeEnabled: (boolean | undefined),
* isCollateEnabled: (boolean | undefined),
* isCssBackgroundEnabled: (boolean | undefined),
* scaling: (string | undefined),
* scalingType: (print_preview.ScalingType | undefined),
* scalingTypePdf: (print_preview.ScalingType | undefined),
* vendor_options: (Object | undefined),
* isPinEnabled: (boolean | undefined),
* pinValue: (string | undefined)
* }}
*/
let SerializedSettings;
/**
* @typedef {{
* value: *,
* managed: boolean
* }}
*/
let PolicyEntry;
/**
* @typedef {{
* headerFooter: print_preview.PolicyEntry
* }}
*/
let PolicySettings;
/**
* Constant values matching printing::DuplexMode enum.
* @enum {number}
*/
const DuplexMode = {
SIMPLEX: 0,
LONG_EDGE: 1,
SHORT_EDGE: 2,
UNKNOWN_DUPLEX_MODE: -1,
};
/**
* Values matching the types of duplex in a CDD.
* @enum {string}
*/
const DuplexType = {
NO_DUPLEX: 'NO_DUPLEX',
LONG_EDGE: 'LONG_EDGE',
SHORT_EDGE: 'SHORT_EDGE'
};
return {
DuplexMode: DuplexMode,
DuplexType: DuplexType,
PolicyEntry: PolicyEntry,
PolicySettings: PolicySettings,
SerializedSettings: SerializedSettings,
Setting: Setting,
Settings: Settings,
};
});
cr.define('print_preview.Model', () => {
return {
/** @private {?PrintPreviewModelElement} */
instance_: null,
/** @private {!PromiseResolver} */
whenReady_: new PromiseResolver(),
/** @return {!PrintPreviewModelElement} */
getInstance: () => assert(print_preview.Model.instance_),
/** @return {!Promise} */
whenReady:
() => {
return print_preview.Model.whenReady_.promise;
},
};
});
(function() {
'use strict';
/**
* Sticky setting names in alphabetical order.
* @type {!Array<string>}
*/
const STICKY_SETTING_NAMES = [
'recentDestinations',
'collate',
'color',
'cssBackground',
'customMargins',
'dpi',
'duplex',
'duplexShortEdge',
'headerFooter',
'layout',
'margins',
'mediaSize',
'scaling',
'scalingType',
'scalingTypePdf',
'vendorItems',
];
// <if expr="chromeos">
STICKY_SETTING_NAMES.push('pin', 'pinValue');
// </if>
/**
* Minimum height of page in microns to allow headers and footers. Should
* match the value for min_size_printer_units in printing/print_settings.cc
* so that we do not request header/footer for margins that will be zero.
* @type {number}
*/
const MINIMUM_HEIGHT_MICRONS = 25400;
Polymer({
is: 'print-preview-model',
properties: {
/**
* Object containing current settings of Print Preview, for use by Polymer
* controls.
* Initialize all settings to available so that more settings always stays
* in a collapsed state during startup, when document information and
* printer capabilities may arrive at slightly different times.
* @type {!print_preview.Settings}
*/
settings: {
type: Object,
notify: true,
value: function() {
return {
pages: {
value: [1],
unavailableValue: [],
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: '',
updatesPreview: false,
},
copies: {
value: 1,
unavailableValue: 1,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: '',
updatesPreview: false,
},
collate: {
value: true,
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'isCollateEnabled',
updatesPreview: false,
},
layout: {
value: false, /* portrait */
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'isLandscapeEnabled',
updatesPreview: true,
},
color: {
value: true, /* color */
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'isColorEnabled',
updatesPreview: true,
},
mediaSize: {
value: {},
unavailableValue: {
width_microns: 215900,
height_microns: 279400,
},
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'mediaSize',
updatesPreview: true,
},
margins: {
value: print_preview.MarginsType.DEFAULT,
unavailableValue: print_preview.MarginsType.DEFAULT,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'marginsType',
updatesPreview: true,
},
customMargins: {
value: {},
unavailableValue: {},
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'customMargins',
updatesPreview: true,
},
dpi: {
value: {},
unavailableValue: {},
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'dpi',
updatesPreview: false,
},
scaling: {
value: '100',
unavailableValue: '100',
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'scaling',
updatesPreview: true,
},
scalingType: {
value: print_preview.ScalingType.DEFAULT,
unavailableValue: print_preview.ScalingType.DEFAULT,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'scalingType',
updatesPreview: true,
},
scalingTypePdf: {
value: print_preview.ScalingType.DEFAULT,
unavailableValue: print_preview.ScalingType.DEFAULT,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'scalingTypePdf',
updatesPreview: true,
},
duplex: {
value: true,
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'isDuplexEnabled',
updatesPreview: false,
},
duplexShortEdge: {
value: false,
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'isDuplexShortEdge',
updatesPreview: false,
},
cssBackground: {
value: false,
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'isCssBackgroundEnabled',
updatesPreview: true,
},
selectionOnly: {
value: false,
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: '',
updatesPreview: true,
},
headerFooter: {
value: true,
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'isHeaderFooterEnabled',
updatesPreview: true,
},
rasterize: {
value: false,
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: '',
updatesPreview: true,
},
vendorItems: {
value: {},
unavailableValue: {},
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'vendorOptions',
updatesPreview: false,
},
pagesPerSheet: {
value: 1,
unavailableValue: 1,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: '',
updatesPreview: true,
},
// This does not represent a real setting value, and is used only to
// expose the availability of the other options settings section.
otherOptions: {
value: null,
unavailableValue: null,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: '',
updatesPreview: false,
},
// This does not represent a real settings value, but is used to
// propagate the correctly formatted ranges for print tickets.
ranges: {
value: [],
unavailableValue: [],
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: '',
updatesPreview: true,
},
recentDestinations: {
value: [],
unavailableValue: [],
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'recentDestinations',
updatesPreview: false,
},
// <if expr="chromeos">
pin: {
value: false,
unavailableValue: false,
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'isPinEnabled',
updatesPreview: false,
},
pinValue: {
value: '',
unavailableValue: '',
valid: true,
available: true,
setByPolicy: false,
setFromUi: false,
key: 'pinValue',
updatesPreview: false,
},
// </if>
};
},
},
controlsManaged: {
type: Boolean,
notify: true,
value: false,
},
/** @type {print_preview.Destination} */
destination: Object,
/** @type {!print_preview.DocumentSettings} */
documentSettings: Object,
/** @type {print_preview.Margins} */
margins: Object,
/** @type {!print_preview.Size} */
pageSize: Object,
},
observers: [
'updateSettingsFromDestination_(destination.capabilities)',
'updateSettingsAvailabilityFromDocumentSettings_(' +
'documentSettings.isModifiable, documentSettings.isPdf,' +
'documentSettings.hasCssMediaStyles, documentSettings.hasSelection)',
'updateHeaderFooterAvailable_(' +
'margins, settings.margins.value, ' +
'settings.customMargins.value, settings.mediaSize.value)',
],
/** @private {boolean} */
initialized_: false,
/** @private {?print_preview.SerializedSettings} */
stickySettings_: null,
/** @private {?print_preview.PolicySettings} */
policySettings_: null,
/** @private {?print_preview.Cdd} */
lastDestinationCapabilities_: null,
/** @override */
attached: function() {
assert(!print_preview.Model.instance_);
print_preview.Model.instance_ = this;
print_preview.Model.whenReady_.resolve();
},
/** @override */
detached: function() {
print_preview.Model.instance_ = null;
print_preview.Model.whenReady_ = new PromiseResolver();
},
/**
* @param {string} settingName Name of the setting to get.
* @return {print_preview.Setting} The setting object.
*/
getSetting: function(settingName) {
const setting = /** @type {print_preview.Setting} */ (
this.get(settingName, this.settings));
assert(setting, 'Setting is missing: ' + settingName);
return setting;
},
/**
* @param {string} settingName Name of the setting to get the value for.
* @return {*} The value of the setting, accounting for availability.
*/
getSettingValue: function(settingName) {
const setting = this.getSetting(settingName);
return setting.available ? setting.value : setting.unavailableValue;
},
/**
* Updates settings.settingPath to |value|. Fires a preview-setting-changed
* event if the modification results in a change to the value returned by
* getSettingValue().
* @param {string} settingPath Setting path to set
* @param {*} value value to set.
* @private
*/
setSettingPath_: function(settingPath, value) {
const settingName = settingPath.split('.')[0];
const setting = this.getSetting(settingName);
const oldValue = this.getSettingValue(settingName);
this.set(`settings.${settingPath}`, value);
const newValue = this.getSettingValue(settingName);
if (newValue !== oldValue && setting.updatesPreview) {
this.fire('preview-setting-changed');
}
},
/**
* Sets settings.settingName.value to |value|, unless updating the setting is
* disallowed by enterprise policy. Fires preview-setting-changed and
* sticky-setting-changed events if the update impacts the preview or requires
* an update to sticky settings. Used for setting settings from UI elements.
* @param {string} settingName Name of the setting to set
* @param {*} value The value to set the setting to.
* @param {boolean=} noSticky Whether to avoid stickying the setting. Defaults
* to false.
*/
setSetting: function(settingName, value, noSticky) {
const setting = this.getSetting(settingName);
if (setting.setByPolicy) {
return;
}
const fireStickyEvent = !noSticky && setting.value !== value && setting.key;
this.setSettingPath_(`${settingName}.value`, value);
if (!noSticky) {
this.setSettingPath_(`${settingName}.setFromUi`, true);
}
if (fireStickyEvent && this.initialized_) {
this.fire('sticky-setting-changed', this.getStickySettings_());
}
},
/**
* @param {string} settingName Name of the setting to set
* @param {number} start
* @param {number} end
* @param {*} newValue The value to add (if any).
* @param {boolean=} noSticky Whether to avoid stickying the setting. Defaults
* to false.
*/
setSettingSplice: function(settingName, start, end, newValue, noSticky) {
const setting = this.getSetting(settingName);
if (setting.setByPolicy) {
return;
}
if (newValue) {
this.splice(`settings.${settingName}.value`, start, end, newValue);
} else {
this.splice(`settings.${settingName}.value`, start, end);
}
if (!noSticky) {
this.setSettingPath_(`${settingName}.setFromUi`, true);
}
if (!noSticky && setting.key && this.initialized_) {
this.fire('sticky-setting-changed', this.getStickySettings_());
}
},
/**
* Sets the validity of |settingName| to |valid|. If the validity is changed,
* fires a setting-valid-changed event.
* @param {string} settingName Name of the setting to set
* @param {boolean} valid Whether the setting value is currently valid.
*/
setSettingValid: function(settingName, valid) {
const setting = this.getSetting(settingName);
// Should not set the setting to invalid if it is not available, as there
// is no way for the user to change the value in this case.
if (!valid) {
assert(setting.available, 'Setting is not available: ' + settingName);
}
const shouldFireEvent = valid != setting.valid;
this.set(`settings.${settingName}.valid`, valid);
if (shouldFireEvent) {
this.fire('setting-valid-changed', valid);
}
},
/**
* Updates the availability of the settings sections and values of dpi and
* media size settings based on the destination capabilities.
* @private
*/
updateSettingsFromDestination_: function() {
if (!this.destination || !this.settings) {
return;
}
if (this.destination.capabilities == this.lastDestinationCapabilities_) {
return;
}
this.lastDestinationCapabilities_ = this.destination.capabilities;
const caps = this.destination.capabilities ?
this.destination.capabilities.printer :
null;
this.updateSettingsAvailabilityFromDestination_(caps);
if (!caps) {
return;
}
this.updateSettingsValues_(caps);
},
/**
* @param {?print_preview.CddCapabilities} caps The printer capabilities.
* @private
*/
updateSettingsAvailabilityFromDestination_: function(caps) {
this.setSettingPath_('copies.available', !!caps && !!caps.copies);
this.setSettingPath_('collate.available', !!caps && !!caps.collate);
this.setSettingPath_(
'color.available', this.destination.hasColorCapability);
this.setSettingPath_(
'dpi.available',
!!caps && !!caps.dpi && !!caps.dpi.option &&
caps.dpi.option.length > 1);
const capsHasDuplex = !!caps && !!caps.duplex && !!caps.duplex.option;
const capsHasLongEdge = capsHasDuplex &&
caps.duplex.option.some(
o => o.type == print_preview.DuplexType.LONG_EDGE);
const capsHasShortEdge = capsHasDuplex &&
caps.duplex.option.some(
o => o.type == print_preview.DuplexType.SHORT_EDGE);
this.setSettingPath_(
'duplexShortEdge.available', capsHasLongEdge && capsHasShortEdge);
this.setSettingPath_(
'duplex.available',
(capsHasLongEdge || capsHasShortEdge) &&
caps.duplex.option.some(
o => o.type == print_preview.DuplexType.NO_DUPLEX));
this.setSettingPath_(
'vendorItems.available', !!caps && !!caps.vendor_capability);
// <if expr="chromeos">
const pinSupported = !!caps && !!caps.pin && !!caps.pin.supported &&
loadTimeData.getBoolean('isEnterpriseManaged');
this.set('settings.pin.available', pinSupported);
this.set('settings.pinValue.available', pinSupported);
// </if>
if (this.documentSettings) {
this.updateSettingsAvailabilityFromDestinationAndDocumentSettings_();
}
},
/** @private */
updateSettingsAvailabilityFromDestinationAndDocumentSettings_: function() {
const isSaveAsPDF = this.destination.id ==
print_preview.Destination.GooglePromotedId.SAVE_AS_PDF;
const knownSizeToSaveAsPdf = isSaveAsPDF &&
(!this.documentSettings.isModifiable ||
this.documentSettings.hasCssMediaStyles);
const scalingAvailable = !knownSizeToSaveAsPdf &&
(this.documentSettings.isModifiable || this.documentSettings.isPdf);
this.setSettingPath_('scaling.available', scalingAvailable);
this.setSettingPath_(
'scalingType.available',
scalingAvailable && !this.documentSettings.isPdf);
this.setSettingPath_(
'scalingTypePdf.available',
scalingAvailable && this.documentSettings.isPdf);
const caps = this.destination && this.destination.capabilities ?
this.destination.capabilities.printer :
null;
this.setSettingPath_(
'mediaSize.available',
!!caps && !!caps.media_size && !knownSizeToSaveAsPdf);
this.setSettingPath_('layout.available', this.isLayoutAvailable_(caps));
},
/** @private */
updateSettingsAvailabilityFromDocumentSettings_: function() {
if (!this.settings) {
return;
}
this.setSettingPath_(
'pagesPerSheet.available',
this.documentSettings.isModifiable || this.documentSettings.isPdf);
this.setSettingPath_(
'margins.available', this.documentSettings.isModifiable);
this.setSettingPath_(
'customMargins.available', this.documentSettings.isModifiable);
this.setSettingPath_(
'cssBackground.available', this.documentSettings.isModifiable);
this.setSettingPath_(
'selectionOnly.available',
this.documentSettings.isModifiable &&
this.documentSettings.hasSelection);
this.setSettingPath_(
'headerFooter.available', this.isHeaderFooterAvailable_());
this.setSettingPath_(
'rasterize.available',
!this.documentSettings.isModifiable && !cr.isWindows && !cr.isMac);
this.setSettingPath_(
'otherOptions.available',
this.settings.cssBackground.available ||
this.settings.selectionOnly.available ||
this.settings.headerFooter.available ||
this.settings.rasterize.available);
if (this.destination) {
this.updateSettingsAvailabilityFromDestinationAndDocumentSettings_();
}
},
/** @private */
updateHeaderFooterAvailable_: function() {
if (this.documentSettings === undefined) {
return;
}
this.setSettingPath_(
'headerFooter.available', this.isHeaderFooterAvailable_());
},
/**
* @return {boolean} Whether the header/footer setting should be available.
* @private
*/
isHeaderFooterAvailable_: function() {
// Always unavailable for PDFs.
if (!this.documentSettings.isModifiable) {
return false;
}
// Always unavailable for small paper sizes.
const microns = this.getSettingValue('layout') ?
this.getSettingValue('mediaSize').width_microns :
this.getSettingValue('mediaSize').height_microns;
if (microns < MINIMUM_HEIGHT_MICRONS) {
return false;
}
// Otherwise, availability depends on the margins.
let available = false;
const marginsType =
/** @type {!print_preview.MarginsType} */ (
this.getSettingValue('margins'));
switch (marginsType) {
case print_preview.MarginsType.DEFAULT:
available = !this.margins ||
this.margins.get(print_preview.CustomMarginsOrientation.TOP) > 0 ||
this.margins.get(print_preview.CustomMarginsOrientation.BOTTOM) > 0;
break;
case print_preview.MarginsType.NO_MARGINS:
break;
case print_preview.MarginsType.MINIMUM:
available = true;
break;
case print_preview.MarginsType.CUSTOM:
const margins = this.getSettingValue('customMargins');
available = margins.marginTop > 0 || margins.marginBottom > 0;
break;
default:
break;
}
return available;
},
/**
* @param {?print_preview.CddCapabilities} caps The printer capabilities.
* @private
*/
isLayoutAvailable_: function(caps) {
if (!caps || !caps.page_orientation || !caps.page_orientation.option ||
!this.documentSettings.isModifiable ||
this.documentSettings.hasCssMediaStyles) {
return false;
}
let hasAutoOrPortraitOption = false;
let hasLandscapeOption = false;
caps.page_orientation.option.forEach(option => {
hasAutoOrPortraitOption = hasAutoOrPortraitOption ||
option.type == 'AUTO' || option.type == 'PORTRAIT';
hasLandscapeOption = hasLandscapeOption || option.type == 'LANDSCAPE';
});
return hasLandscapeOption && hasAutoOrPortraitOption;
},
/**
* @param {?print_preview.CddCapabilities} caps The printer capabilities.
* @private
*/
updateSettingsValues_: function(caps) {
if (this.settings.mediaSize.available) {
const defaultOption = caps.media_size.option.find(o => !!o.is_default) ||
caps.media_size.option[0];
let matchingOption = null;
// If the setting does not have a valid value, the UI has just started so
// do not try to get a matching value; just set the printer default in
// case the user doesn't have sticky settings.
if (this.settings.mediaSize.setFromUi) {
const currentMediaSize = this.getSettingValue('mediaSize');
matchingOption = caps.media_size.option.find(o => {
return o.height_microns === currentMediaSize.height_microns &&
o.width_microns === currentMediaSize.width_microns;
});
}
this.setSetting('mediaSize', matchingOption || defaultOption, true);
}
if (this.settings.dpi.available) {
const defaultOption =
caps.dpi.option.find(o => !!o.is_default) || caps.dpi.option[0];
let matchingOption = null;
if (this.settings.dpi.setFromUi) {
const currentDpi = this.getSettingValue('dpi');
matchingOption = caps.dpi.option.find(o => {
return o.horizontal_dpi === currentDpi.horizontal_dpi &&
o.vertical_dpi === currentDpi.vertical_dpi;
});
}
this.setSetting('dpi', matchingOption || defaultOption, true);
} else if (
caps && caps.dpi && caps.dpi.option && caps.dpi.option.length > 0) {
this.setSettingPath_('dpi.unavailableValue', caps.dpi.option[0]);
}
if (!this.settings.color.setFromUi && this.settings.color.available) {
const defaultOption = this.destination.defaultColorOption;
if (defaultOption) {
this.setSetting(
'color',
!['STANDARD_MONOCHROME', 'CUSTOM_MONOCHROME'].includes(
defaultOption.type),
true);
}
} else if (
!this.settings.color.available &&
(this.destination.id ===
print_preview.Destination.GooglePromotedId.DOCS ||
this.destination.type === print_preview.DestinationType.MOBILE)) {
this.setSettingPath_('color.unavailableValue', true);
} else if (
!this.settings.color.available && caps && caps.color &&
caps.color.option && caps.color.option.length > 0) {
this.setSettingPath_(
'color.unavailableValue',
!['STANDARD_MONOCHROME', 'CUSTOM_MONOCHROME'].includes(
caps.color.option[0].type));
} else if (!this.settings.color.available) {
// if no color capability is reported, assume black and white.
this.setSettingPath_('color.unavailableValue', false);
}
if (!this.settings.duplex.setFromUi && this.settings.duplex.available) {
const defaultOption = caps.duplex.option.find(o => !!o.is_default);
this.setSetting(
'duplex',
defaultOption ?
(defaultOption.type == print_preview.DuplexType.LONG_EDGE ||
defaultOption.type == print_preview.DuplexType.SHORT_EDGE) :
false,
true);
this.setSetting(
'duplexShortEdge',
defaultOption ?
defaultOption.type == print_preview.DuplexType.SHORT_EDGE :
false,
true);
if (!this.settings.duplexShortEdge.available) {
// Duplex is available, so must have only one two sided printing option.
// Set duplexShortEdge's unavailable value based on the printer.
this.setSettingPath_(
'duplexShortEdge.unavailableValue',
caps.duplex.option.some(
o => o.type == print_preview.DuplexType.SHORT_EDGE));
}
} else if (
!this.settings.duplex.available && caps && caps.duplex &&
caps.duplex.option) {
// In this case, there must only be one option.
const hasLongEdge = caps.duplex.option.some(
o => o.type == print_preview.DuplexType.LONG_EDGE);
const hasShortEdge = caps.duplex.option.some(
o => o.type == print_preview.DuplexType.SHORT_EDGE);
// If the only option available is long edge, the value should always be
// true.
this.setSettingPath_(
'duplex.unavailableValue', hasLongEdge || hasShortEdge);
this.setSettingPath_('duplexShortEdge.unavailableValue', hasShortEdge);
} else if (!this.settings.duplex.available) {
// If no duplex capability is reported, assume false.
this.setSettingPath_('duplex.unavailableValue', false);
this.setSettingPath_('duplexShortEdge.unavailableValue', false);
}
if (this.settings.vendorItems.available) {
const vendorSettings = {};
for (const item of caps.vendor_capability) {
let defaultValue = null;
if (item.type == 'SELECT' && item.select_cap &&
item.select_cap.option) {
const defaultOption =
item.select_cap.option.find(o => !!o.is_default);
defaultValue = defaultOption ? defaultOption.value : null;
} else if (item.type == 'RANGE') {
if (item.range_cap) {
defaultValue = item.range_cap.default || null;
}
} else if (item.type == 'TYPED_VALUE') {
if (item.typed_value_cap) {
defaultValue = item.typed_value_cap.default || null;
}
}
if (defaultValue != null) {
vendorSettings[item.id] = defaultValue;
}
}
this.setSetting('vendorItems', vendorSettings, true);
}
},
/**
* Caches the sticky settings and sets up the recent destinations. Sticky
* settings will be applied when destinaton capabilities have been retrieved.
* @param {?string} savedSettingsStr The sticky settings from native layer
*/
setStickySettings: function(savedSettingsStr) {
assert(!this.stickySettings_);
if (!savedSettingsStr) {
return;
}
let savedSettings;
try {
savedSettings = /** @type {print_preview.SerializedSettings} */ (
JSON.parse(savedSettingsStr));
} catch (e) {
console.error('Unable to parse state ' + e);
return; // use default values rather than updating.
}
if (savedSettings.version != 2) {
return;
}
let recentDestinations = savedSettings.recentDestinations || [];
if (!Array.isArray(recentDestinations)) {
recentDestinations = [recentDestinations];
}
// Initialize recent destinations early so that the destination store can
// start trying to fetch them.
this.setSetting('recentDestinations', recentDestinations);
this.stickySettings_ = savedSettings;
},
/**
* Sets settings in accordance to policies from native code, and prevents
* those settings from being changed via other means.
* @param {boolean|undefined} headerFooter Value of
* printing.print_header_footer, if set in prefs (or undefined, if not).
* @param {boolean} isHeaderFooterManaged true if the header/footer UI state
* is managed by a policy.
*/
setPolicySettings: function(headerFooter, isHeaderFooterManaged) {
this.policySettings_ = {
headerFooter: {
value: headerFooter,
managed: isHeaderFooterManaged,
},
};
},
applyStickySettings: function() {
if (this.stickySettings_) {
STICKY_SETTING_NAMES.forEach(settingName => {
const setting = this.get(settingName, this.settings);
const value = this.stickySettings_[setting.key];
if (value != undefined) {
this.setSetting(settingName, value);
} else {
this.applyScalingStickySettings_(settingName);
}
});
}
if (this.policySettings_) {
for (const [settingName, policy] of Object.entries(
this.policySettings_)) {
if (policy.value !== undefined) {
this.setSetting(settingName, policy.value, true);
}
if (policy.managed) {
this.set(`settings.${settingName}.setByPolicy`, true);
}
}
}
this.initialized_ = true;
this.updateManaged_();
this.stickySettings_ = null;
this.fire('sticky-settings-changed', this.getStickySettings_());
},
/**
* Helper function for applyStickySettings(). Checks if the setting
* is a scaling setting and applies by applying the old types
* that rely on 'fitToPage' and 'customScaling'.
* @param {string} settingName Name of the setting being applied.
* @private
*/
applyScalingStickySettings_: function(settingName) {
// TODO(dhoss): Remove checks for 'customScaling' and 'fitToPage'
if (settingName === 'scalingType' &&
'customScaling' in this.stickySettings_) {
const isCustom = this.stickySettings_['customScaling'];
const scalingType = isCustom ? print_preview.ScalingType.CUSTOM :
print_preview.ScalingType.DEFAULT;
this.setSetting(settingName, scalingType);
} else if (settingName === 'scalingTypePdf') {
if ('isFitToPageEnabled' in this.stickySettings_) {
const isFitToPage = this.stickySettings_['isFitToPageEnabled'];
const scalingTypePdf = isFitToPage ?
print_preview.ScalingType.FIT_TO_PAGE :
this.getSetting('scalingType').value;
this.setSetting(settingName, scalingTypePdf);
} else if (
this.getSetting('scalingType').value ===
print_preview.ScalingType.CUSTOM) {
// In the event that 'isFitToPageEnabled' was not in the sticky
// settings, and 'scalingType' has been set to custom, we want
// 'scalingTypePdf' to match.
this.setSetting(settingName, print_preview.ScalingType.CUSTOM);
}
}
},
// <if expr="chromeos">
/**
* Restricts settings and applies defaults as defined by policy applicable to
* current destination.
*/
applyDestinationSpecificPolicies: function() {
const colorPolicy = this.destination.colorPolicy;
const colorValue =
colorPolicy ? colorPolicy : this.destination.defaultColorPolicy;
if (colorValue) {
// |this.setSetting| does nothing if policy is present.
// We want to set the value nevertheless so we call |this.set| directly.
this.set(
'settings.color.value',
colorValue === print_preview.ColorModeRestriction.COLOR);
}
this.set('settings.color.setByPolicy', !!colorPolicy);
const duplexPolicy = this.destination.duplexPolicy;
const duplexValue =
duplexPolicy ? duplexPolicy : this.destination.defaultDuplexPolicy;
let setDuplexTypeByPolicy = false;
if (duplexValue) {
this.set(
'settings.duplex.value',
duplexValue != print_preview.DuplexModeRestriction.SIMPLEX);
if (duplexValue === print_preview.DuplexModeRestriction.SHORT_EDGE) {
this.set('settings.duplexShortEdge.value', true);
setDuplexTypeByPolicy = true;
} else if (
duplexValue === print_preview.DuplexModeRestriction.LONG_EDGE) {
this.set('settings.duplexShortEdge.value', false);
setDuplexTypeByPolicy = true;
}
}
this.set('settings.duplex.setByPolicy', !!duplexPolicy);
this.set(
'settings.duplexShortEdge.setByPolicy',
!!duplexPolicy && setDuplexTypeByPolicy);
const pinPolicy = this.destination.pinPolicy;
if (pinPolicy === print_preview.PinModeRestriction.NO_PIN) {
this.set('settings.pin.available', false);
this.set('settings.pinValue.available', false);
}
const pinValue = pinPolicy ? pinPolicy : this.destination.defaultPinPolicy;
if (pinValue) {
this.set(
'settings.pin.value',
pinValue === print_preview.PinModeRestriction.PIN);
}
this.set('settings.pin.setByPolicy', !!pinPolicy);
const backgroundGraphicsPolicy = this.destination.backgroundGraphicsPolicy;
const backgroundGraphicsValue = backgroundGraphicsPolicy ?
backgroundGraphicsPolicy :
this.destination.defaultBackgroundGraphicsPolicy;
if (backgroundGraphicsValue) {
this.set(
'settings.cssBackground.value',
backgroundGraphicsValue ===
print_preview.BackgroundGraphicsModeRestriction.ENABLED);
}
this.set('settings.cssBackground.setByPolicy', !!backgroundGraphicsPolicy);
this.updateManaged_();
},
// </if>
/** @private */
updateManaged_: function() {
let managedSettings = ['headerFooter'];
// <if expr="chromeos">
managedSettings = managedSettings.concat(
['color', 'cssBackground', 'duplex', 'duplexShortEdge', 'pin']);
// </if>
this.controlsManaged = managedSettings.some(settingName => {
const setting = this.getSetting(settingName);
return setting.available && setting.setByPolicy;
});
},
/** @return {boolean} Whether the model has been initialized. */
initialized: function() {
return this.initialized_;
},
/**
* @return {string} The current serialized settings.
* @private
*/
getStickySettings_: function() {
const serialization = {
version: 2,
};
STICKY_SETTING_NAMES.forEach(settingName => {
const setting = this.get(settingName, this.settings);
if (setting.setFromUi) {
serialization[assert(setting.key)] = setting.value;
}
});
return JSON.stringify(serialization);
},
/**
* @return {!print_preview.DuplexMode} The duplex mode selected.
* @private
*/
getDuplexMode_: function() {
if (!this.getSettingValue('duplex')) {
return print_preview.DuplexMode.SIMPLEX;
}
return this.getSettingValue('duplexShortEdge') ?
print_preview.DuplexMode.SHORT_EDGE :
print_preview.DuplexMode.LONG_EDGE;
},
/**
* @return {!print_preview.DuplexType} The duplex type selected.
* @private
*/
getCddDuplexType_: function() {
if (!this.getSettingValue('duplex')) {
return print_preview.DuplexType.NO_DUPLEX;
}
return this.getSettingValue('duplexShortEdge') ?
print_preview.DuplexType.SHORT_EDGE :
print_preview.DuplexType.LONG_EDGE;
},
/**
* Creates a string that represents a print ticket.
* @param {!print_preview.Destination} destination Destination to print to.
* @param {boolean} openPdfInPreview Whether this print request is to open
* the PDF in Preview app (Mac only).
* @param {boolean} showSystemDialog Whether this print request is to show
* the system dialog.
* @return {string} Serialized print ticket.
*/
createPrintTicket: function(destination, openPdfInPreview, showSystemDialog) {
const dpi =
/**
@type {{horizontal_dpi: (number | undefined),
vertical_dpi: (number | undefined),
vendor_id: (number | undefined)}}
*/
(this.getSettingValue('dpi'));
const scalingSettingKey = this.getSetting('scalingTypePdf').available ?
'scalingTypePdf' :
'scalingType';
const ticket = {
mediaSize: this.getSettingValue('mediaSize'),
pageCount: this.getSettingValue('pages').length,
landscape: this.getSettingValue('layout'),
color: destination.getNativeColorModel(
/** @type {boolean} */ (this.getSettingValue('color'))),
headerFooterEnabled: false, // only used in print preview
marginsType: this.getSettingValue('margins'),
duplex: this.getDuplexMode_(),
copies: this.getSettingValue('copies'),
collate: this.getSettingValue('collate'),
shouldPrintBackgrounds: this.getSettingValue('cssBackground'),
shouldPrintSelectionOnly: false, // only used in print preview
previewModifiable: this.documentSettings.isModifiable,
printToGoogleDrive:
destination.id == print_preview.Destination.GooglePromotedId.DOCS,
printerType: print_preview.getPrinterTypeForDestination(destination),
rasterizePDF: this.getSettingValue('rasterize'),
scaleFactor: this.getSettingValue(scalingSettingKey) ===
print_preview.ScalingType.CUSTOM ?
parseInt(this.getSettingValue('scaling'), 10) :
100,
pagesPerSheet: this.getSettingValue('pagesPerSheet'),
dpiHorizontal: (dpi && 'horizontal_dpi' in dpi) ? dpi.horizontal_dpi : 0,
dpiVertical: (dpi && 'vertical_dpi' in dpi) ? dpi.vertical_dpi : 0,
dpiDefault: (dpi && 'is_default' in dpi) ? dpi.is_default : false,
deviceName: destination.id,
// TODO(dhoss): Pass the enum in the ticket.
fitToPageEnabled: this.getSettingValue(scalingSettingKey) ===
print_preview.ScalingType.FIT_TO_PAGE,
pageWidth: this.pageSize.width,
pageHeight: this.pageSize.height,
showSystemDialog: showSystemDialog,
};
// Set 'cloudPrintID' only if the destination is not local.
if (!destination.isLocal) {
ticket.cloudPrintID = destination.id;
}
if (this.getSettingValue('margins') == print_preview.MarginsType.CUSTOM) {
ticket.marginsCustom = this.getSettingValue('customMargins');
}
if (destination.isPrivet || destination.isExtension) {
// TODO(rbpotter): Get local and PDF printers to use the same ticket and
// send only this ticket instead of nesting it in a larger ticket.
ticket.ticket = this.createCloudJobTicket(destination);
ticket.capabilities = JSON.stringify(destination.capabilities);
}
if (openPdfInPreview) {
ticket.OpenPDFInPreview = true;
}
// <if expr="chromeos">
if (this.getSettingValue('pin')) {
ticket.pinValue = this.getSettingValue('pinValue');
}
if (destination.origin == print_preview.DestinationOrigin.CROS) {
ticket.advancedSettings = this.getSettingValue('vendorItems');
}
// </if>
return JSON.stringify(ticket);
},
/**
* Creates an object that represents a Google Cloud Print print ticket.
* @param {!print_preview.Destination} destination Destination to print to.
* @return {string} Google Cloud Print print ticket.
*/
createCloudJobTicket: function(destination) {
assert(
!destination.isLocal || destination.isPrivet || destination.isExtension,
'Trying to create a Google Cloud Print print ticket for a local ' +
' non-privet and non-extension destination');
assert(
destination.capabilities,
'Trying to create a Google Cloud Print print ticket for a ' +
'destination with no print capabilities');
// Create CJT (Cloud Job Ticket)
const cjt = {version: '1.0', print: {}};
if (this.settings.collate.available) {
cjt.print.collate = {collate: this.settings.collate.value};
}
if (this.settings.color.available) {
const selectedOption = destination.getSelectedColorOption(
/** @type {boolean} */ (this.settings.color.value));
if (!selectedOption) {
console.error('Could not find correct color option');
} else {
cjt.print.color = {type: selectedOption.type};
if (selectedOption.hasOwnProperty('vendor_id')) {
cjt.print.color.vendor_id = selectedOption.vendor_id;
}
}
} else {
// Always try setting the color in the print ticket, otherwise a
// reasonable reader of the ticket will have to do more work, or process
// the ticket sub-optimally, in order to safely handle the lack of a
// color ticket item.
const defaultOption = destination.defaultColorOption;
if (defaultOption) {
cjt.print.color = {type: defaultOption.type};
if (defaultOption.hasOwnProperty('vendor_id')) {
cjt.print.color.vendor_id = defaultOption.vendor_id;
}
}
}
if (this.settings.copies.available) {
cjt.print.copies = {copies: this.getSettingValue('copies')};
}
if (this.settings.duplex.available) {
cjt.print.duplex = {
type: this.getCddDuplexType_(),
};
}
if (this.settings.mediaSize.available) {
const mediaValue = this.settings.mediaSize.value;
cjt.print.media_size = {
width_microns: mediaValue.width_microns,
height_microns: mediaValue.height_microns,
is_continuous_feed: mediaValue.is_continuous_feed,
vendor_id: mediaValue.vendor_id
};
}
if (!this.settings.layout.available) {
// In this case "orientation" option is hidden from user, so user can't
// adjust it for page content, see Landscape.isCapabilityAvailable().
// We can improve results if we set AUTO here.
const capability = destination.capabilities.printer ?
destination.capabilities.printer.page_orientation :
null;
if (capability && capability.option &&
capability.option.some(option => option.type == 'AUTO')) {
cjt.print.page_orientation = {type: 'AUTO'};
}
} else {
cjt.print.page_orientation = {
type: this.settings.layout.value ? 'LANDSCAPE' : 'PORTRAIT'
};
}
if (this.settings.dpi.available) {
const dpiValue = this.settings.dpi.value;
cjt.print.dpi = {
horizontal_dpi: dpiValue.horizontal_dpi,
vertical_dpi: dpiValue.vertical_dpi,
vendor_id: dpiValue.vendor_id
};
}
if (this.settings.vendorItems.available) {
const items = this.settings.vendorItems.value;
cjt.print.vendor_ticket_item = [];
for (const itemId in items) {
if (items.hasOwnProperty(itemId)) {
cjt.print.vendor_ticket_item.push({id: itemId, value: items[itemId]});
}
}
}
return JSON.stringify(cjt);
},
});
})();