diff options
author | abodenha@chromium.org <abodenha@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-24 04:46:24 +0000 |
---|---|---|
committer | abodenha@chromium.org <abodenha@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-24 04:46:24 +0000 |
commit | 4e2fbb6c9dd4f2c227309c975ce055b62ec818eb (patch) | |
tree | 3bf492328caff48b92162b0f00e1b8f998603140 /chrome/browser/resources/print_preview | |
parent | cdb60e3ef390d93af671f2b9cb7086392e883bcc (diff) | |
download | chromium_src-4e2fbb6c9dd4f2c227309c975ce055b62ec818eb.zip chromium_src-4e2fbb6c9dd4f2c227309c975ce055b62ec818eb.tar.gz chromium_src-4e2fbb6c9dd4f2c227309c975ce055b62ec818eb.tar.bz2 |
I've separated out the model from the UI widgets into a separate "data" folder. Some of the classes in the "data" folder are event targets that dispatch events when their state changes. UI widgets listen to these state changes and update their UI accordingly. The two most important "data" classes are PrintTicketStore and DestinationStore. These two manage the state of the print ticket (aka print settings) and the list of all of the print destinations respectively.
The UI widgets are divided into top-level components, settings components, and previewarea components.
Top-level components just include the outer most container widget (called PrintPreview) which handles global events and PrintHeader which renders the print and cancel buttons and some statistics on the document. If you were to start reviewing, PrintPreview is where I would start.
Settings components include all of the UI widgets that can be used to change the state of the PrintTicketStore. They live in the "settings" folder and include "CopiesSettings" and "PageSettings".
Preview area components include all of the UI widgets involved on the main part of the preview UI. PreviewArea and CustomMargins are the two main widgets in the "previewarea" folder.
It's important to note that almost all of the UI widgets have a dependency on PrintTicketStore. Crucially, this allows all UI widgets to be independent from each other and just depend on a common object.
Previously, the state of the print ticket (aka print settings) were distributed in the various UI controls. For example, custom margins were stored both in the preview area and in the margin settings and when it came time to print, these UI components had to be queried for that information, or the question on whether to show the header-footer setting depended on the state of the preview area. Now the state is consolidated into PrintTicketStore. This allows modification of each of the UI widgets without the worry of breaking other widgets.
BUG=114206
TEST=
Review URL: https://chromiumcodereview.appspot.com/10108001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@138737 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/resources/print_preview')
83 files changed, 9521 insertions, 5462 deletions
diff --git a/chrome/browser/resources/print_preview/cloud_print_interface.js b/chrome/browser/resources/print_preview/cloud_print_interface.js new file mode 100644 index 0000000..b7e7c7c --- /dev/null +++ b/chrome/browser/resources/print_preview/cloud_print_interface.js @@ -0,0 +1,304 @@ +// Copyright (c) 2012 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('cloudprint', function() { + 'use strict'; + + /** + * API to the Google Cloud Print service. + * @param {string} baseUrl Base part of the Google Cloud Print service URL + * with no trailing slash. For example, + * 'https://www.google.com/cloudprint'. + * @constructor + * @extends {cr.EventTarget} + */ + function CloudPrintInterface(baseUrl) { + /** + * The base URL of the Google Cloud Print API. + * @type {string} + * @private + */ + this.baseURL_ = baseUrl; + + /** + * Last received XSRF token. Sent as a parameter in every request. + * @type {string} + * @private + */ + this.xsrfToken_ = ''; + }; + + /** + * Event types dispatched by the interface. + * @enum {string} + */ + CloudPrintInterface.EventType = { + ERROR: 'cloudprint.CloudPrintInterface.ERROR', + PRINTER_DONE: 'cloudprint.CloudPrintInterface.PRINTER_DONE', + SEARCH_DONE: 'cloudprint.CloudPrintInterface.SEARCH_DONE', + SUBMIT_DONE: 'cloudprint.CloudPrintInterface.SUBMIT_DONE' + }; + + /** + * Content type header value for a URL encoded HTTP request. + * @type {string} + * @private + */ + CloudPrintInterface.URL_ENCODED_CONTENT_TYPE_ = + 'application/x-www-form-urlencoded'; + + /** + * Content type header value for a multipart HTTP request. + * @type {string} + * @private + */ + CloudPrintInterface.MULTIPART_CONTENT_TYPE_ = + 'multipart/form-data; boundary=----CloudPrintFormBoundaryjc9wuprokl8i'; + + /** + * Enumeration of JSON response fields from Google Cloud Print API. + * @enum {string} + * @private + */ + CloudPrintInterface.JsonFields_ = { + PRINTER: 'printer' + }; + + CloudPrintInterface.prototype = { + __proto__: cr.EventTarget.prototype, + + /** + * Sends a Google Cloud Print search API request. + * @param {boolean} isRecent Whether to search for only recently used + * printers. + */ + search: function(isRecent) { + var params = {}; + if (isRecent) { + params['q'] = '^recent'; + } + this.sendRequest_('GET', 'search', params, null, this.onSearchDone_); + }, + + /** + * Sends a Google Cloud Print submit API request. + * @param {string} body Body of the HTTP post request to send. + */ + submit: function(body) { + this.sendRequest_('POST', 'submit', null, body, this.onSubmitDone_); + }, + + /** + * Sends a Google Cloud Print printer API request. + * @param {string} printerId ID of the printer to lookup. + */ + printer: function(printerId) { + var params = {'printerid': printerId}; + this.sendRequest_('GET', 'printer', params, null, this.onPrinterDone_); + }, + + /** + * Creates an object that represents a Google Cloud Print print ticket. + * @param {!print_preview.Destination} destination Destination to print to. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to create + * the state of the print ticket. + * @return {object} Google Cloud Print print ticket. + */ + createPrintTicket: function(destination, printTicketStore) { + assert(!destination.isLocal, + 'Trying to create a Google Cloud Print print ticket for a local ' + + 'destination'); + assert(destination.capabilities, + 'Trying to create a Google Cloud Print print ticket for a ' + + 'destination with no print capabilities'); + + var ticketItems = []; + + if (destination.capabilities.collateCapability) { + var collateCap = destination.capabilities.collateCapability; + var ticketItem = { + 'name': collateCap.id, + 'type': collateCap.type, + 'options': [{'name': printTicketStore.isCollateEnabled() ? + collateCap.collateOption : collateCap.noCollateOption}] + }; + ticketItems.push(ticketItem); + } + + if (destination.capabilities.colorCapability) { + var colorCap = destination.capabilities.colorCapability; + var ticketItem = { + 'name': colorCap.id, + 'type': colorCap.type, + 'options': [{'name': printTicketStore.isColorEnabled() ? + colorCap.colorOption : colorCap.bwOption}] + }; + ticketItems.push(ticketItem); + } + + if (destination.capabilities.copiesCapability) { + var copiesCap = destination.capabilities.copiesCapability; + var ticketItem = { + 'name': copiesCap.id, + 'type': copiesCap.type, + 'value': printTicketStore.getCopies() + }; + ticketItems.push(ticketItem); + } + + if (destination.capabilities.duplexCapability) { + var duplexCap = destination.capabilities.duplexCapability; + var ticketItem = { + 'name': duplexCap.id, + 'type': duplexCap.type, + 'options': [{'name': printTicketStore.isDuplexEnabled() ? + duplexCap.longEdgeOption : duplexCap.simplexOption}] + }; + ticketItems.push(ticketItem); + } + + return { + 'capabilities': ticketItems + }; + }, + + /** + * Sends a request to the Google Cloud Print API. + * @param {string} method HTTP method of the request. + * @param {string} action Google Cloud Print action to perform. + * @param {Object} params HTTP parameters to include in the request. + * @param {string} body HTTP multi-part encoded body. + * @param {function(Object)} successCallback Callback to invoke when request + * completes successfully. + */ + sendRequest_: function(method, action, params, body, successCallback) { + if (!this.xsrfToken_) { + // TODO(rltoscano): Should throw an error if not a read-only action or + // issue an xsrf token request. + } + var url = this.baseURL_ + '/' + action + '?xsrf=' + this.xsrfToken_; + + if (params) { + for (var paramName in params) { + url += '&' + paramName + '=' + encodeURIComponent(params[paramName]); + } + } + + var headers = {}; + headers['X-CloudPrint-Proxy'] = 'ChromePrintPreview'; + if (method == 'GET') { + headers['Content-Type'] = CloudPrintInterface.URL_ENCODED_CONTENT_TYPE_; + } else if (method == 'POST') { + headers['Content-Type'] = CloudPrintInterface.MULTIPART_CONTENT_TYPE_; + } + + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = this.onReadyStateChange_.bind( + this, xhr, successCallback.bind(this)); + xhr.open(method, url, true); + xhr.withCredentials = true; + for (var header in headers) { + xhr.setRequestHeader(header, headers[header]); + } + xhr.send(body); + }, + + /** + * Dispatches an ERROR event with the given error message. + * @param {string} message Error message to include in the ERROR event. + * @private + */ + dispatchErrorEvent_: function(message) { + var errorEvent = new cr.Event(CloudPrintInterface.EventType.ERROR); + errorEvent.message = message; + this.dispatchEvent(errorEvent); + }, + + /** + * Called when the ready-state of a XML http request changes. + * Calls the successCallback with the result or dispatches an ERROR event. + * @param {XMLHttpRequest} xhr XML http request that changed. + * @param {function(Object)} successCallback Callback to call if the request + * was successful. + * @private + */ + onReadyStateChange_: function(xhr, successCallback) { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + var result = JSON.parse(xhr.responseText); + if (result['success']) { + this.xsrfToken_ = result['xsrf_token']; + successCallback(result); + } else { + this.dispatchErrorEvent_(result['message']); + } + } else { + this.dispatchErrorEvent_(xhr.status + ''); + } + } + }, + + /** + * Called when the search request completes successfully. + * @param {Object} result JSON response. + * @private + */ + onSearchDone_: function(result) { + var printerListJson = result['printers'] || []; + var printerList = []; + for (var printerJson, i = 0; printerJson = printerListJson[i]; i++) { + try { + printerList.push( + cloudprint.CloudDestinationParser.parse(printerJson)); + } catch (err) { + console.error('Unable to parse cloud print destination: ' + err); + } + } + var isRecent = result['request']['params']['q'] == '^recent'; + var searchDoneEvent = + new cr.Event(CloudPrintInterface.EventType.SEARCH_DONE); + searchDoneEvent.printers = printerList; + searchDoneEvent.isRecent = isRecent; + searchDoneEvent.email = result['request']['user']; + this.dispatchEvent(searchDoneEvent); + }, + + /** + * Called when the submit request completes successfully. + * @param {Object} result JSON response. + * @private + */ + onSubmitDone_: function(result) { + this.dispatchEvent( + new cr.Event(CloudPrintInterface.EventType.SUBMIT_DONE)); + }, + + /** + * Called when the printer request completes successfully. + * @param {Object} result JSON response. + * @private + */ + onPrinterDone_: function(result) { + // TODO(rltoscano): Better error handling here. + var printerJson = result['printers'][0]; + var printer; + try { + printer = cloudprint.CloudDestinationParser.parse(printerJson); + } catch (err) { + console.error('Failed to parse cloud print destination: ' + + JSON.stringify(printerJson)); + return; + } + var printerDoneEvent = + new cr.Event(CloudPrintInterface.EventType.PRINTER_DONE); + printerDoneEvent.printer = printer; + this.dispatchEvent(printerDoneEvent); + } + }; + + // Export + return { + CloudPrintInterface: CloudPrintInterface + }; +}); diff --git a/chrome/browser/resources/print_preview/color_settings.html b/chrome/browser/resources/print_preview/color_settings.html deleted file mode 100644 index 7eadd0a..0000000 --- a/chrome/browser/resources/print_preview/color_settings.html +++ /dev/null @@ -1,14 +0,0 @@ -<div id="color-option" class="two-column visible" aria-hidden="true" - aria-live="polite"> - <h1 i18n-content="optionColor"></h1> - <div class="right-column"> - <div class="radio"><label> - <input id="color" type="radio" name="color"> - <span i18n-content="optionColor"></span> - </label></div> - <div class="radio"><label> - <input id="bw" type="radio" name="color" checked> - <span i18n-content="optionBw"></span> - </label></div> - </div> - </div> diff --git a/chrome/browser/resources/print_preview/color_settings.js b/chrome/browser/resources/print_preview/color_settings.js deleted file mode 100644 index 2d9cdc2..0000000 --- a/chrome/browser/resources/print_preview/color_settings.js +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Creates a ColorSettings object. This object encapsulates all settings and - * logic related to color selection (color/bw). - * @constructor - */ - function ColorSettings() { - this.colorOption_ = $('color-option'); - this.colorRadioButton_ = $('color'); - this.bwRadioButton_ = $('bw'); - - this.printerColorModelForColor_ = ColorSettings.COLOR; - this.printerColorModelForBlack_ = ColorSettings.GRAY; - this.addEventListeners_(); - } - - ColorSettings.GRAY = 1; - ColorSettings.COLOR = 2; - cr.addSingletonGetter(ColorSettings); - - ColorSettings.prototype = { - /** - * The radio button corresponding to the color option. - * @type {HTMLInputElement} - */ - get colorRadioButton() { - return this.colorRadioButton_; - }, - - /** - * The radio button corresponding to the black and white option. - * @type {HTMLInputElement} - */ - get bwRadioButton() { - return this.bwRadioButton_; - }, - - /** - * @return {number} The color mode for print preview. - */ - get colorMode() { - return this.bwRadioButton_.checked ? - this.printerColorModelForBlack_ : - this.printerColorModelForColor_; - }, - - /** - * Adding listeners to all color related controls. The listeners take care - * of altering their behavior depending on |hasPendingPreviewRequest|. - * @private - */ - addEventListeners_: function() { - this.colorRadioButton_.onclick = function() { - setColor(true); - }; - this.bwRadioButton_.onclick = function() { - setColor(false); - }; - document.addEventListener(customEvents.PDF_LOADED, - this.onPDFLoaded_.bind(this)); - document.addEventListener(customEvents.PRINTER_CAPABILITIES_UPDATED, - this.onPrinterCapabilitiesUpdated_.bind(this)); - }, - - /** - * Executes when a |customEvents.PRINTER_CAPABILITIES_UPDATED| event occurs. - * @private - */ - onPrinterCapabilitiesUpdated_: function(e) { - var disableColorOption = e.printerCapabilities.disableColorOption; - - disableColorOption ? fadeOutOption(this.colorOption_) : - fadeInOption(this.colorOption_); - this.colorOption_.setAttribute('aria-hidden', disableColorOption); - - var setColorAsDefault = e.printerCapabilities.setColorAsDefault; - this.printerColorModelForColor_ = - e.printerCapabilities.printerColorModelForColor || - ColorSettings.COLOR; - this.printerColorModelForBlack_ = - e.printerCapabilities.printerColorModelForBlack || - ColorSettings.GRAY; - - this.colorRadioButton_.checked = setColorAsDefault; - this.bwRadioButton_.checked = !setColorAsDefault; - setColor(this.colorRadioButton_.checked); - }, - - /** - * Executes when a |customEvents.PDF_LOADED| event occurs. - * @private - */ - onPDFLoaded_: function() { - setColor(this.colorRadioButton_.checked); - } - - }; - - return { - ColorSettings: ColorSettings - }; -}); diff --git a/chrome/browser/resources/print_preview/component.js b/chrome/browser/resources/print_preview/component.js new file mode 100644 index 0000000..f51c3ae1 --- /dev/null +++ b/chrome/browser/resources/print_preview/component.js @@ -0,0 +1,192 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Class that represents a UI component. + * @constructor + * @extends {cr.EventTarget} + */ + function Component() { + cr.EventTarget.call(this); + + /** + * Component's HTML element. + * @type {Element} + * @private + */ + this.element_ = null; + + this.isInDocument_ = false; + + /** + * Component's event tracker. + * @type {EventTracker} + * @private + */ + this.tracker_ = new EventTracker(); + + /** + * Child components of the component. + * @type {Array.<print_preview.Component>} + * @private + */ + this.children_ = []; + }; + + Component.prototype = { + __proto__: cr.EventTarget.prototype, + + /** Gets the component's element. */ + getElement: function() { + return this.element_; + }, + + /** @return {EventTracker} Component's event tracker. */ + get tracker() { + return this.tracker_; + }, + + /** + * @return {boolean} Whether the element of the component is already in the + * HTML document. + */ + get isInDocument() { + return this.isInDocument_; + }, + + /** + * Creates the root element of the component. Sub-classes should override + * this method. + */ + createDom: function() { + this.element_ = cr.doc.createElement('div'); + }, + + /** + * Called when the component's element is known to be in the document. + * Anything using document.getElementById etc. should be done at this stage. + * Sub-classes should extend this method and attach listeners. + */ + enterDocument: function() { + this.isInDocument_ = true; + for (var child, i = 0; child = this.children_[i]; i++) { + if (!child.isInDocument && child.getElement()) { + child.enterDocument(); + } + } + }, + + /** Removes all event listeners. */ + exitDocument: function() { + for (var child, i = 0; child = this.children_[i]; i++) { + if (child.isInDocument) { + child.exitDocument(); + } + } + this.tracker_.removeAll(); + this.isInDocument_ = false; + }, + + /** + * Renders this UI component and appends the element to the given parent + * element. + * @param {!Element} parentElement Element to render the component's + * element into. + */ + render: function(parentElement) { + assert(!this.isInDocument, 'Component is already in the document'); + if (!this.element_) { + this.createDom(); + } + parentElement.appendChild(this.element_); + this.enterDocument(); + }, + + /** + * Decorates an existing DOM element. Sub-classes should override the + * override the decorateInternal method. + * @param {Element} element Element to decorate. + */ + decorate: function(element) { + assert(!this.isInDocument, 'Component is already in the document'); + this.setElementInternal(element); + this.decorateInternal(); + this.enterDocument(); + }, + + /** + * @param {print_preview.Component} child Component to add as a child of + * this component. + */ + addChild: function(child) { + this.children_.push(child); + }, + + /** + * @param {!print_preview.Component} child Component to remove from this + * component's children. + */ + removeChild: function(child) { + var childIdx = this.children_.indexOf(child); + if (childIdx != -1) { + this.children_.splice(childIdx, 1); + } + if (child.isInDocument) { + child.exitDocument(); + if (child.getElement()) { + child.getElement().parentNode.removeChild(child.getElement()); + } + } + }, + + /** Removes all of the component's children. */ + removeChildren: function() { + while (this.children_.length > 0) { + this.removeChild(this.children_[0]); + } + }, + + /** + * Sets the component's element. + * @param {Element} element HTML element to set as the component's element. + * @protected + */ + setElementInternal: function(element) { + this.element_ = element; + }, + + /** + * Decorates the given element for use as the element of the component. + * @protected + */ + decorateInternal: function() { /*abstract*/ }, + + /** + * Clones a template HTML DOM tree. + * @param {string} templateId Template element ID. + * @param {boolean=} opt_keepHidden Whether to leave the cloned template + * hidden after cloning. + * @return {Element} Cloned element with its 'id' attribute stripped. + * @protected + */ + cloneTemplateInternal: function(templateId, opt_keepHidden) { + var templateEl = $(templateId); + assert(templateEl != null, + 'Could not find element with ID: ' + templateId); + var el = templateEl.cloneNode(true); + el.id = ''; + if (!opt_keepHidden) { + setIsVisible(el, true); + } + return el; + } + }; + + return { + Component: Component + }; +}); diff --git a/chrome/browser/resources/print_preview/copies_settings.html b/chrome/browser/resources/print_preview/copies_settings.html deleted file mode 100644 index f368a6b..0000000 --- a/chrome/browser/resources/print_preview/copies_settings.html +++ /dev/null @@ -1,27 +0,0 @@ -<div id="copies-option" class="two-column visible"> - <h1 i18n-content="copiesLabel"></h1> - <div class="right-column"> - <div> - <input id="copies" type="text" value="1" maxlength="3"> - <button id="increment" - i18n-values="title:incrementTitle;">+</button> - <button id="decrement" - i18n-values="title:decrementTitle;">–</button> - <div id="collate-option" class="checkbox" aria-live="polite" hidden> - <label> - <input id="collate" type="checkbox" checked> - <span i18n-content="optionCollate"></span> - </label> - </div> - </div> - <span id="copies-hint" class="hint" - i18n-content="copiesInstruction" aria-live="polite"> - </span> - <div class="checkbox"> - <label id="two-sided-option" aria-live="polite"> - <input id="two-sided" type="checkbox"> - <span i18n-content="optionTwoSided"></span> - </label> - </div> - </div> -</div> diff --git a/chrome/browser/resources/print_preview/copies_settings.js b/chrome/browser/resources/print_preview/copies_settings.js deleted file mode 100644 index b5b94e9..0000000 --- a/chrome/browser/resources/print_preview/copies_settings.js +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Creates a CopiesSettings object. - * @constructor - */ - function CopiesSettings() { - this.copiesOption_ = $('copies-option'); - this.textfield_ = $('copies'); - this.incrementButton_ = $('increment'); - this.decrementButton_ = $('decrement'); - // Minimum allowed value for number of copies. - this.minValue_ = 1; - // Maximum allowed value for number of copies. - this.maxValue_ = 999; - this.collateOption_ = $('collate-option'); - this.collateCheckbox_ = $('collate'); - this.hint_ = $('copies-hint'); - this.twoSidedCheckbox_ = $('two-sided'); - this.twoSidedOption_ = $('two-sided-option'); - - this.previousDuplexMode_ = CopiesSettings.UNKNOWN_DUPLEX_MODE; - this.addEventListeners_(); - } - - // Constant values matches printing::DuplexMode enum. - CopiesSettings.SIMPLEX = 0; - CopiesSettings.LONG_EDGE = 1; - CopiesSettings.UNKNOWN_DUPLEX_MODE = -1; - - cr.addSingletonGetter(CopiesSettings); - - CopiesSettings.prototype = { - /** - * The number of copies represented by the contents of |this.textfield_|. - * If the text is not valid returns |this.minValue_|. - * @type {number} - */ - get numberOfCopies() { - var value = parseInt(this.textfield_.value, 10); - if (!value || value <= this.minValue_) - value = this.minValue_; - return value; - }, - - /** - * Getter method for |collateOption_|. - * @type {HTMLElement} - */ - get collateOption() { - return this.collateOption_; - }, - - /** - * Getter method for |twoSidedCheckbox_|. - * @type {HTMLInputElement} - */ - get twoSidedCheckbox() { - return this.twoSidedCheckbox_; - }, - - /** - * Gets the duplex mode information for printing. - * @return {number} duplex mode. - */ - get duplexMode() { - if (this.twoSidedOption_.hidden) - return CopiesSettings.UNKNOWN_DUPLEX_MODE; - else if (this.twoSidedCheckbox_.checked) - return CopiesSettings.LONG_EDGE; - else - return CopiesSettings.SIMPLEX; - }, - - set previousDuplexMode(duplexMode) { - this.previousDuplexMode_ = duplexMode; - }, - - /** - * @return {boolean} true if |this.textfield_| is empty, or represents a - * positive integer value. - */ - isValid: function() { - return !this.textfield_.value || isPositiveInteger(this.textfield_.value); - }, - - /** - * Checks whether the preview collate setting value is set or not. - * @return {boolean} true if collate option is enabled and checked. - */ - isCollated: function() { - return !this.collateOption_.hidden && this.collateCheckbox_.checked; - }, - - /** - * Resets |this.textfield_| to |this.minValue_|. - * @private - */ - reset_: function() { - this.textfield_.value = this.minValue_; - }, - - /** - * Listener function to execute whenever the increment/decrement buttons are - * clicked. - * @private - * @param {int} sign Must be 1 for an increment button click and -1 for a - * decrement button click. - */ - onButtonClicked_: function(sign) { - if (!this.isValid()) { - this.reset_(); - } else { - var newValue = this.numberOfCopies + sign; - this.textfield_.value = newValue; - } - this.updateButtonsState_(); - this.showHideCollateOption_(); - - if (!hasPendingPreviewRequest) { - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_PRINT_BUTTON); - } - }, - - /** - * Listener function to execute whenever a change occurs in |textfield_| - * textfield. - * @private - */ - onTextfieldChanged_: function() { - this.updateButtonsState_(); - this.showHideCollateOption_(); - if (!hasPendingPreviewRequest) { - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_PRINT_BUTTON); - } - }, - - /** - * Adding listeners to all copies related controls. The listeners take care - * of altering their behavior depending on |hasPendingPreviewRequest|. - * @private - */ - addEventListeners_: function() { - this.textfield_.oninput = this.onTextfieldChanged_.bind(this); - this.incrementButton_.onclick = this.onIncrementButtonClicked_.bind(this); - this.decrementButton_.onclick = this.onDecrementButtonClicked_.bind(this); - this.twoSidedCheckbox_.onclick = function() { - if (!hasPendingPreviewRequest) - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - } - document.addEventListener(customEvents.PDF_LOADED, - this.updateButtonsState_.bind(this)); - document.addEventListener(customEvents.PRINTER_CAPABILITIES_UPDATED, - this.onPrinterCapabilitiesUpdated_.bind(this)); - }, - - /** - * Executes when a |customEvents.PRINTER_CAPABILITIES_UPDATED| event occurs. - * @private - */ - onPrinterCapabilitiesUpdated_: function(e) { - var duplexValue = e.printerCapabilities.printerDefaultDuplexValue; - if (duplexValue != CopiesSettings.UNKNOWN_DUPLEX_MODE && - this.previousDuplexMode_ != CopiesSettings.UNKNOWN_DUPLEX_MODE) { - duplexValue = this.previousDuplexMode_; - } - this.updateTwoSidedOption_(duplexValue); - e.printerCapabilities.disableCopiesOption ? - fadeOutOption(this.copiesOption_) : - fadeInOption(this.copiesOption_); - }, - - /** - * Listener triggered when |incrementButton_| is clicked. - * @private - */ - onIncrementButtonClicked_: function() { - this.onButtonClicked_(1); - }, - - /** - * Listener triggered when |decrementButton_| is clicked. - * @private - */ - onDecrementButtonClicked_: function() { - this.onButtonClicked_(-1); - }, - - /** - * Takes care of showing/hiding the collate option. - * @private - */ - showHideCollateOption_: function() { - this.collateOption_.hidden = this.numberOfCopies <= 1; - }, - - /* - * Takes care of showing/hiding the two sided option. - * @param {number} defaultDuplexValue Specifies the default duplex value. - * @private - */ - updateTwoSidedOption_: function(defaultDuplexValue) { - // On Windows, some printers don't specify their duplex values in the - // printer schema. If the printer duplex value is UNKNOWN_DUPLEX_MODE, - // hide the two sided option in preview tab UI. - // Ref bug: http://crbug.com/89204 - this.twoSidedOption_.hidden = - (defaultDuplexValue == CopiesSettings.UNKNOWN_DUPLEX_MODE); - - if (!this.twoSidedOption_.hidden) { - this.twoSidedCheckbox_.checked = !!defaultDuplexValue; - if (pageSettings.totalPageCount) - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - } - }, - - /** - * Updates the state of the increment/decrement buttons based on the current - * |textfield_| value. - * @private - */ - updateButtonsState_: function() { - if (!this.isValid()) { - this.textfield_.classList.add('invalid'); - this.incrementButton_.disabled = false; - this.decrementButton_.disabled = false; - fadeInElement(this.hint_); - } else { - this.textfield_.classList.remove('invalid'); - this.incrementButton_.disabled = this.numberOfCopies == this.maxValue_; - this.decrementButton_.disabled = this.numberOfCopies == this.minValue_; - fadeOutElement(this.hint_); - } - this.hint_.setAttribute('aria-hidden', this.isValid()); - } - }; - - return { - CopiesSettings: CopiesSettings - }; -}); diff --git a/chrome/browser/resources/print_preview/data/capabilities_holder.js b/chrome/browser/resources/print_preview/data/capabilities_holder.js new file mode 100644 index 0000000..bb4a1aa --- /dev/null +++ b/chrome/browser/resources/print_preview/data/capabilities_holder.js @@ -0,0 +1,43 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Mutable reference to a capabilities object. + * @constructor + */ + function CapabilitiesHolder() { + /** + * Reference to the capabilities object. + * @type {print_preview.ChromiumCapabilities} + * @private + */ + this.capabilities_ = null; + }; + + CapabilitiesHolder.prototype = { + /** + * @return {print_preview.ChromiumCapabilities} The instance held by the + * holder. + */ + get: function() { + return this.capabilities_; + }, + + /** + * @param {!print_preview.ChromiumCapabilities} New instance to put into the + * holder. + */ + set: function(capabilities) { + this.capabilities_ = capabilities; + } + }; + + // Export + return { + CapabilitiesHolder: CapabilitiesHolder + }; +}); diff --git a/chrome/browser/resources/print_preview/data/chromium_capabilities.js b/chrome/browser/resources/print_preview/data/chromium_capabilities.js new file mode 100644 index 0000000..1cb238d --- /dev/null +++ b/chrome/browser/resources/print_preview/data/chromium_capabilities.js @@ -0,0 +1,180 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Capabilities of a print destination not including the capabilities of the + * document renderer. + * @param {boolean} hasCopiesCapability Whether the print destination has a + * copies capability. + * @param {string} defaultCopiesStr Default string representation of the + * copies value. + * @param {boolean} hasCollateCapability Whether the print destination has + * collation capability. + * @param {boolean} defaultIsCollateEnabled Whether collate is enabled by + * default. + * @param {boolean} hasDuplexCapability Whether the print destination has + * duplexing capability. + * @param {boolean} defaultIsDuplexEnabled Whether duplexing is enabled by + * default. + * @param {boolean} hasOrientationCapability Whether the print destination has + * orientation capability. + * @param {boolean} defaultIsLandscapeEnabled Whether the document should be + * printed in landscape by default. + * @param {boolean} hasColorCapability Whether the print destination has + * color printing capability. + * @param {boolean} defaultIsColorEnabled Whether the document should be + * printed in color by default. + * @constructor + */ + function ChromiumCapabilities( + hasCopiesCapability, + defaultCopiesStr, + hasCollateCapability, + defaultIsCollateEnabled, + hasDuplexCapability, + defaultIsDuplexEnabled, + hasOrientationCapability, + defaultIsLandscapeEnabled, + hasColorCapability, + defaultIsColorEnabled) { + /** + * Whether the print destination has a copies capability. + * @type {boolean} + * @private + */ + this.hasCopiesCapability_ = hasCopiesCapability; + + /** + * Default string representation of the copies value. + * @type {string} + * @private + */ + this.defaultCopiesStr_ = defaultCopiesStr; + + /** + * Whether the print destination has collation capability. + * @type {boolean} + * @private + */ + this.hasCollateCapability_ = hasCollateCapability; + + /** + * Whether collate is enabled by default. + * @type {boolean} + * @private + */ + this.defaultIsCollateEnabled_ = defaultIsCollateEnabled; + + /** + * Whether the print destination has duplexing capability. + * @type {boolean} + * @private + */ + this.hasDuplexCapability_ = hasDuplexCapability; + + /** + * Whether duplex is enabled by default. + * @type {boolean} + * @private + */ + this.defaultIsDuplexEnabled_ = defaultIsDuplexEnabled; + + /** + * Whether the print destination has orientation capability. + * @type {boolean} + * @private + */ + this.hasOrientationCapability_ = hasOrientationCapability; + + /** + * Whether the document should be printed in landscape by default. + * @type {boolean} + * @private + */ + this.defaultIsLandscapeEnabled_ = defaultIsLandscapeEnabled; + + /** + * Whether the print destination has color printing capability. + * @type {boolean} + * @private + */ + this.hasColorCapability_ = hasColorCapability; + + /** + * Whether the document should be printed in color. + * @type {boolean} + * @private + */ + this.defaultIsColorEnabled_ = defaultIsColorEnabled; + }; + + ChromiumCapabilities.prototype = { + /** @return {boolean} Whether the destination has the copies capability. */ + get hasCopiesCapability() { + return this.hasCopiesCapability_; + }, + + /** @return {string} Default number of copies in string format. */ + get defaultCopiesStr() { + return this.defaultCopiesStr_; + }, + + /** @return {boolean} Whether the destination has collation capability. */ + get hasCollateCapability() { + return this.hasCollateCapability_; + }, + + /** @return {boolean} Whether collation is enabled by default. */ + get defaultIsCollateEnabled() { + return this.defaultIsCollateEnabled_; + }, + + /** @return {boolean} Whether the destination has the duplex capability. */ + get hasDuplexCapability() { + return this.hasDuplexCapability_; + }, + + /** @return {boolean} Whether duplexing is enabled by default. */ + get defaultIsDuplexEnabled() { + return this.defaultIsDuplexEnabled_; + }, + + /** + * @return {boolean} Whether the destination has the orientation capability. + */ + get hasOrientationCapability() { + return this.hasOrientationCapability_; + }, + + /** + * @return {boolean} Whether document should be printed in landscape by + * default. + */ + get defaultIsLandscapeEnabled() { + return this.defaultIsLandscapeEnabled_; + }, + + /** + * @return {boolean} Whether the destination has color printing capability. + */ + get hasColorCapability() { + return this.hasColorCapability_; + }, + + /** + * @return {boolean} Whether document should be printed in color by default. + */ + get defaultIsColorEnabled() { + return this.defaultIsColorEnabled_; + } + }; + + // Export + return { + ChromiumCapabilities: ChromiumCapabilities + }; +}); diff --git a/chrome/browser/resources/print_preview/data/cloud_capabilities.js b/chrome/browser/resources/print_preview/data/cloud_capabilities.js new file mode 100644 index 0000000..7bb107f --- /dev/null +++ b/chrome/browser/resources/print_preview/data/cloud_capabilities.js @@ -0,0 +1,413 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Capabilities of a cloud-based print destination. + * @param {print_preview.CollateCapability} collateCapability Print + * destination collate capability. + * @param {print_preview.ColorCapability} colorCapability Print destination + * color capability. + * @param {print_preview.CopiesCapability} copiesCapability Print destination + * copies capability. + * @param {print_preview.DuplexCapability} duplexCapability Print destination + * duplexing capability. + * @constructor + * @extends {print_preview.ChromiumCapabilities} + */ + function CloudCapabilities( + collateCapability, colorCapability, copiesCapability, duplexCapability) { + print_preview.ChromiumCapabilities.call( + this, + !!copiesCapability, + '1' /*defaultCopiesStr*/, + !!collateCapability, + !!collateCapability && collateCapability.isCollateDefault, + !!duplexCapability, + !!duplexCapability && duplexCapability.isDuplexDefault, + true /*hasOrientationCapability*/, + false /*defaultIsLandscapeEnabled*/, + !!colorCapability, + !!colorCapability && colorCapability.isColorDefault); + + /** + * Print destination collate capability. + * @type {print_preview.CollateCapability} + * @private + */ + this.collateCapability_ = collateCapability; + + /** + * Print destination color capability. + * @type {print_preview.ColorCapability} + * @private + */ + this.colorCapability_ = colorCapability; + + /** + * Print destination copies capability. + * @type {print_preview.CopiesCapability} + * @private + */ + this.copiesCapability_ = copiesCapability; + + /** + * Print destination duplexing capability. + * @type {print_preview.DuplexCapability} + * @private + */ + this.duplexCapability_ = duplexCapability; + }; + + /** + * Enumeration of the capability formats of cloud-based print destinations. + * @enum {string} + */ + CloudCapabilities.Format = { + HP: 'hp', + PPD: 'ppd', + XPS: 'xps' + }; + + CloudCapabilities.prototype = { + __proto__: print_preview.ChromiumCapabilities.prototype, + + /** + * @return {print_preview.CollateCapability} The print destination's collate + * capability. + */ + get collateCapability() { + return this.collateCapability_; + }, + + /** + * @return {print_preview.CollateCapability} The print destination's color + * capability. + */ + get colorCapability() { + return this.colorCapability_; + }, + + /** + * @return {print_preview.CollateCapability} The print destination's copies + * capability. + */ + get copiesCapability() { + return this.copiesCapability_; + }, + + /** + * @return {print_preview.CollateCapability} The print destination's + * duplexing capability. + */ + get duplexCapability() { + return this.duplexCapability_; + } + }; + + /** + * A single print capability of a cloud-based print destination. + * @param {string} id Identifier of the capability. + * @param {print_preview.CloudCapability.Type} type Type of the capability. + * @constructor + */ + function CloudCapability(id, type) { + /** + * Identifier of the capability. + * @type {string} + * @private + */ + this.id_ = id; + + /** + * Type of the capability. + * @type {print_preview.CloudCapability.Type} + * @private + */ + this.type_ = type; + }; + + /** + * Enumeration of the types of cloud-based print capabilities. + * @enum {string} + */ + CloudCapability.Type = { + FEATURE: 'Feature', + PARAMETER_DEF: 'ParameterDef' + }; + + CloudCapability.prototype = { + /** @return {string} Identifier of the capability. */ + get id() { + return this.id_; + }, + + /** @return {print_preview.CloudCapability.Type} Type of the capability. */ + get type() { + return this.type_; + } + }; + + /** + * Cloud-based collate capability. + * @param {string} id Identifier of the collate capability. + * @param {string} collateOption Identifier of the option that enables + * collation. + * @param {string} noCollateOption Identifier of the option that disables + * collation. + * @param {boolean} isCollateDefault Whether collation is enabled by default. + * @constructor + * @extends {print_preview.CloudCapability} + */ + function CollateCapability( + id, collateOption, noCollateOption, isCollateDefault) { + CloudCapability.call(this, id, CloudCapability.Type.FEATURE); + + /** + * Identifier of the option that enables collation. + * @type {string} + * @private + */ + this.collateOption_ = collateOption; + + /** + * Identifier of the option that disables collation. + * @type {string} + * @private + */ + this.noCollateOption_ = noCollateOption; + + /** + * Whether collation is enabled by default. + * @type {boolean} + * @private + */ + this.isCollateDefault_ = isCollateDefault; + }; + + /** + * Mapping of capability formats to an identifier of the collate capability. + * @type {object<CloudCapabilities.Format, string>} + */ + CollateCapability.Id = {}; + CollateCapability.Id[CloudCapabilities.Format.PPD] = 'Collate'; + CollateCapability.Id[CloudCapabilities.Format.XPS] = 'psk:DocumentCollate'; + + /** + * Regular expression that matches a collate option. + * @type {!RegExp} + * @const + */ + CollateCapability.COLLATE_REGEX = /(.*:collated.*|true)/i; + + /** + * Regular expression that matches a no-collate option. + * @type {!RegExp} + * @const + */ + CollateCapability.NO_COLLATE_REGEX = /(.*:uncollated.*|false)/i; + + CollateCapability.prototype = { + __proto__: CloudCapability.prototype, + + /** @return {string} Identifier of the option that enables collation. */ + get collateOption() { + return this.collateOption_; + }, + + /** @return {string} Identifier of the option that disables collation. */ + get noCollateOption() { + return this.noCollateOption_; + }, + + /** @return {boolean} Whether collation is enabled by default. */ + get isCollateDefault() { + return this.isCollateDefault_; + } + }; + + /** + * Cloud-based color print capability. + * @param {string} id Identifier of the color capability. + * @param {string} colorOption Identifier of the color option. + * @param {string} bwOption Identifier of the black-white option. + * @param {boolean} Whether color printing is enabled by default. + * @constructor + */ + function ColorCapability(id, colorOption, bwOption, isColorDefault) { + CloudCapability.call(this, id, CloudCapability.Type.FEATURE); + + /** + * Identifier of the color option. + * @type {string} + * @private + */ + this.colorOption_ = colorOption; + + /** + * Identifier of the black-white option. + * @type {string} + * @private + */ + this.bwOption_ = bwOption; + + /** + * Whether to print in color by default. + * @type {boolean} + * @private + */ + this.isColorDefault_ = isColorDefault; + }; + + /** + * Mapping of capability formats to an identifier of the color capability. + * @type {object<CloudCapabilities.Format, string>} + */ + ColorCapability.Id = {}; + ColorCapability.Id[CloudCapabilities.Format.HP] = 'ns1:Colors'; + ColorCapability.Id[CloudCapabilities.Format.PPD] = 'ColorModel'; + ColorCapability.Id[CloudCapabilities.Format.XPS] = 'psk:PageOutputColor'; + + /** + * Regular expression that matches a color option. + * @type {!RegExp} + * @const + */ + ColorCapability.COLOR_REGEX = /(.*color.*|.*rgb.*|.*cmy.*|true)/i; + + /** + * Regular expression that matches a black-white option. + * @type {!RegExp} + * @const + */ + ColorCapability.BW_REGEX = /(.*gray.*|.*mono.*|.*black.*|false)/i; + + ColorCapability.prototype = { + __proto__: CloudCapability.prototype, + + /** @return {string} Identifier of the color option. */ + get colorOption() { + return this.colorOption_; + }, + + /** @return {string} Identifier of the black-white option. */ + get bwOption() { + return this.bwOption_; + }, + + /** @return {boolean} Whether to print in color by default. */ + get isColorDefault() { + return this.isColorDefault_; + } + }; + + /** + * Cloud-based copies print capability. + * @param {string} id Identifier of the copies capability. + * @constructor + */ + function CopiesCapability(id) { + CloudCapability.call(this, id, CloudCapability.Type.PARAMETER_DEF); + }; + + CopiesCapability.prototype = { + __proto__: CloudCapability.prototype + }; + + /** + * Mapping of capability formats to an identifier of the copies capability. + * @type {object<CloudCapabilities.Format, string>} + */ + CopiesCapability.Id = {}; + CopiesCapability.Id[CloudCapabilities.Format.XPS] = + 'psk:JobCopiesAllDocuments'; + + /** + * Cloud-based duplex print capability. + * @param {string} id Identifier of the duplex capability. + * @param {string} simplexOption Identifier of the no-duplexing option. + * @param {string} longEdgeOption Identifier of the duplex on long edge + * option. + * @param {boolean} Whether duplexing is enabled by default. + * @constructor + */ + function DuplexCapability( + id, simplexOption, longEdgeOption, isDuplexDefault) { + CloudCapability.call(this, id, CloudCapability.Type.FEATURE); + + /** + * Identifier of the no-duplexing option. + * @type {string} + * @private + */ + this.simplexOption_ = simplexOption; + + /** + * Identifier of the duplex on long edge option. + * @type {string} + * @private + */ + this.longEdgeOption_ = longEdgeOption; + + /** + * Whether duplexing is enabled by default. + * @type {boolean} + * @private + */ + this.isDuplexDefault_ = isDuplexDefault; + }; + + /** + * Mapping of capability formats to an identifier of the duplex capability. + * @type {object<CloudCapabilities.Format, string>} + */ + DuplexCapability.Id = {}; + DuplexCapability.Id[CloudCapabilities.Format.PPD] = 'Duplex'; + DuplexCapability.Id[CloudCapabilities.Format.XPS] = + 'psk:JobDuplexAllDocumentsContiguously'; + + /** + * Regular expression that matches a no-duplexing option. + * @type {!RegExp} + * @const + */ + DuplexCapability.SIMPLEX_REGEX = /(.*onesided.*|.*none.*)/i; + + /** + * Regular expression that matches a duplex on long edge option. + * @type {!RegExp} + * @const + */ + DuplexCapability.LONG_EDGE_REGEX = /(.*longedge.*|duplexNoTumble)/i; + + DuplexCapability.prototype = { + __proto__: CloudCapability.prototype, + + /** @return {string} Identifier of the no-duplexing option. */ + get simplexOption() { + return this.simplexOption_; + }, + + /** @return {string} Identifier of the duplex on long edge option. */ + get longEdgeOption() { + return this.longEdgeOption_; + }, + + /** @return {boolean} Whether duplexing is enabled by default. */ + get isDuplexDefault() { + return this.isDuplexDefault_; + } + }; + + // Export + return { + CloudCapabilities: CloudCapabilities, + CollateCapability: CollateCapability, + ColorCapability: ColorCapability, + CopiesCapability: CopiesCapability, + DuplexCapability: DuplexCapability + }; +}); diff --git a/chrome/browser/resources/print_preview/data/cloud_parsers.js b/chrome/browser/resources/print_preview/data/cloud_parsers.js new file mode 100644 index 0000000..965e881 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/cloud_parsers.js @@ -0,0 +1,207 @@ +// Copyright (c) 2012 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('cloudprint', function() { + 'use strict'; + + /** Namespace which contains a method to parse cloud destinations directly. */ + function CloudDestinationParser() {}; + + /** + * Enumeration of cloud destination field names. + * @enum {string} + * @private + */ + CloudDestinationParser.Field_ = { + CAPABILITIES: 'capabilities', + DISPLAY_NAME: 'displayName', + FORMAT: 'capsFormat', + ID: 'id', + TAGS: 'tags' + }; + + /** + * Special tag that denotes whether the destination has been recently used. + * @type {string} + * @private + */ + CloudDestinationParser.RECENT_TAG_ = '^recent'; + + /** + * Parses a destination from JSON from a Google Cloud Print search or printer + * response. + * @param {object} json Object that represents a Google Cloud Print search or + * printer response. + * @return {!print_preview.Destination} Parsed destination. + */ + CloudDestinationParser.parse = function(json) { + if (!json.hasOwnProperty(CloudDestinationParser.Field_.ID) || + !json.hasOwnProperty(CloudDestinationParser.Field_.DISPLAY_NAME)) { + throw Error('Cloud destination does not have an ID or a display name'); + } + var isRecent = arrayContains( + json[CloudDestinationParser.Field_.TAGS] || [], + CloudDestinationParser.RECENT_TAG_); + var cloudDest = new print_preview.Destination( + json[CloudDestinationParser.Field_.ID], + json[CloudDestinationParser.Field_.DISPLAY_NAME], + isRecent, + false /*isLocal*/, + json[CloudDestinationParser.Field_.TAGS] || []); + if (json.hasOwnProperty(CloudDestinationParser.Field_.CAPABILITIES) && + json.hasOwnProperty(CloudDestinationParser.Field_.FORMAT)) { + cloudDest.capabilities = CloudCapabilitiesParser.parse( + json[CloudDestinationParser.Field_.FORMAT], + json[CloudDestinationParser.Field_.CAPABILITIES]); + } + return cloudDest; + }; + + /** + * Namespace which contains a method to parse a cloud destination's print + * capabilities. + */ + function CloudCapabilitiesParser() {}; + + /** + * Enumeration of cloud destination print capabilities field names. + * @enum {string} + * @private + */ + CloudCapabilitiesParser.Field_ = { + CAP_ID: 'name', + DEFAULT: 'default', + IS_DEFAULT: 'default', + OPTIONS: 'options', + OPTION_ID: 'name' + }; + + /** + * Parses print capabilities from an object in a given capabilities format. + * @param {print_preview.CloudCapabilities.Format} capsFormat Format of the + * printer capabilities. + * @param {object} json Object representing the cloud capabilities. + * @return {!print_preview.CloudCapabilities} Parsed print capabilities. + */ + CloudCapabilitiesParser.parse = function(capsFormat, json) { + var colorCapability = null; + var duplexCapability = null; + var copiesCapability = null; + var collateCapability = null; + for (var cap, i = 0; cap = json[i]; i++) { + var capId = cap[CloudCapabilitiesParser.Field_.CAP_ID]; + if (capId == print_preview.CollateCapability.Id[capsFormat]) { + collateCapability = CloudCapabilitiesParser.parseCollate(capId, cap); + } else if (capId == print_preview.ColorCapability.Id[capsFormat]) { + colorCapability = CloudCapabilitiesParser.parseColor(capId, cap); + } else if (capId == print_preview.CopiesCapability.Id[capsFormat]) { + copiesCapability = new print_preview.CopiesCapability(capId); + } else if (capId == print_preview.DuplexCapability.Id[capsFormat]) { + duplexCapability = CloudCapabilitiesParser.parseDuplex(capId, cap); + } + } + return new print_preview.CloudCapabilities( + collateCapability, colorCapability, copiesCapability, duplexCapability); + }; + + /** + * Parses a collate capability from the given object. + * @param {string} capId Native ID of the given capability object. + * @param {object} Object that represents the collate capability. + * @return {print_preview.CollateCapability} Parsed collate capability or + * {@code null} if the given capability object was not a valid collate + * capability. + */ + CloudCapabilitiesParser.parseCollate = function(capId, cap) { + var options = cap[CloudCapabilitiesParser.Field_.OPTIONS]; + var collateOption = null; + var noCollateOption = null; + var isCollateDefault = false; + for (var option, i = 0; option = options[i]; i++) { + var optionId = option[CloudCapabilitiesParser.Field_.OPTION_ID]; + if (!collateOption && + print_preview.CollateCapability.COLLATE_REGEX.test(optionId)) { + collateOption = optionId; + isCollateDefault = !!option[CloudCapabilitiesParser.Field_.DEFAULT]; + } else if (!noCollateOption && + print_preview.CollateCapability.NO_COLLATE_REGEX.test(optionId)) { + noCollateOption = optionId; + } + } + if (!collateOption || !noCollateOption) { + return null; + } + return new print_preview.CollateCapability( + capId, collateOption, noCollateOption, isCollateDefault); + }; + + /** + * Parses a color capability from the given object. + * @param {string} capId Native ID of the given capability object. + * @param {object} Object that represents the color capability. + * @return {print_preview.ColorCapability} Parsed color capability or + * {@code null} if the given capability object was not a valid color + * capability. + */ + CloudCapabilitiesParser.parseColor = function(capId, cap) { + var options = cap[CloudCapabilitiesParser.Field_.OPTIONS]; + var colorOption = null; + var bwOption = null; + var isColorDefault = false; + for (var option, i = 0; option = options[i]; i++) { + var optionId = option[CloudCapabilitiesParser.Field_.OPTION_ID]; + if (!colorOption && + print_preview.ColorCapability.COLOR_REGEX.test(optionId)) { + colorOption = optionId; + isColorDefault = !!option[CloudCapabilitiesParser.Field_.DEFAULT]; + } else if (!bwOption && + print_preview.ColorCapability.BW_REGEX.test(optionId)) { + bwOption = optionId; + } + } + if (!colorOption || !bwOption) { + return null; + } + return new print_preview.ColorCapability( + capId, colorOption, bwOption, isColorDefault); + }; + + /** + * Parses a duplex capability from the given object. + * @param {string} capId Native ID of the given capability object. + * @param {object} Object that represents the duplex capability. + * @return {print_preview.DuplexCapability} Parsed duplex capability or + * {@code null} if the given capability object was not a valid duplex + * capability. + */ + CloudCapabilitiesParser.parseDuplex = function(capId, cap) { + var options = cap[CloudCapabilitiesParser.Field_.OPTIONS]; + var simplexOption = null; + var longEdgeOption = null; + var isDuplexDefault = false; + for (var option, i = 0; option = options[i]; i++) { + var optionId = option[CloudCapabilitiesParser.Field_.OPTION_ID]; + if (!simplexOption && + print_preview.DuplexCapability.SIMPLEX_REGEX.test(optionId)) { + simplexOption = optionId; + } else if (!longEdgeOption && + print_preview.DuplexCapability.LONG_EDGE_REGEX.test(optionId)) { + longEdgeOption = optionId; + isDuplexDefault = !!option[CloudCapabilitiesParser.Field_.DEFAULT]; + } + } + if (!simplexOption || !longEdgeOption) { + return null; + } + return new print_preview.DuplexCapability( + capId, simplexOption, longEdgeOption, isDuplexDefault); + }; + + // Export + return { + CloudCapabilitiesParser: CloudCapabilitiesParser, + CloudDestinationParser: CloudDestinationParser + }; +}); + diff --git a/chrome/browser/resources/print_preview/data/coordinate2d.js b/chrome/browser/resources/print_preview/data/coordinate2d.js new file mode 100644 index 0000000..4b10af6 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/coordinate2d.js @@ -0,0 +1,76 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Immutable two dimensional point in space. The units of the dimensions are + * undefined. + * @param {number} x X-dimension of the point. + * @param {number} y Y-dimension of the point. + * @constructor + */ + function Coordinate2d(x, y) { + /** + * X-dimension of the point. + * @type {number} + * @private + */ + this.x_ = x; + + /** + * Y-dimension of the point. + * @type {number} + * @private + */ + this.y_ = y; + }; + + Coordinate2d.prototype = { + /** @return {number} X-dimension of the point. */ + get x() { + return this.x_; + }, + + /** @return {number} Y-dimension of the point. */ + get y() { + return this.y_; + }, + + /** + * @param {number} x Amount to translate in the X dimension. + * @param {number} y Amount to translate in the Y dimension. + * @return {!print_preview.Coordinate2d} A new two-dimensional point + * translated along the X and Y dimensions. + */ + translate: function(x, y) { + return new Coordinate2d(this.x_ + x, this.y_ + y); + }, + + /** + * @param {number} factor Amount to scale the X and Y dimensions. + * @return {!print_preview.Coordinate2d} A new two-dimensional point scaled + * by the given factor. + */ + scale: function(factor) { + return new Coordinate2d(this.x_ * factor, this.y_ * factor); + }, + + /** + * @param {print_preview.Coordinate2d} other The point to compare against. + * @return {boolean} Whether another point is equal to this one. + */ + equals: function(other) { + return other != null && + this.x_ == other.x_ && + this.y_ == other.y_; + } + }; + + // Export + return { + Coordinate2d: Coordinate2d + }; +}); diff --git a/chrome/browser/resources/print_preview/data/destination.js b/chrome/browser/resources/print_preview/data/destination.js new file mode 100644 index 0000000..01a9491 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/destination.js @@ -0,0 +1,188 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Print destination data object that holds data for both local and cloud + * destinations. + * @param {string} id ID of the destination. + * @param {string} displayName Display name of the destination. + * @param {boolean} isRecent Whether the destination has been used recently. + * @param {boolean} isLocal Whether the destination is local or cloud-based. + * @param {Array.<string>=} opt_tags Tags associated with the destination. + * @constructor + */ + function Destination(id, displayName, isRecent, isLocal, opt_tags) { + /** + * ID of the destination. + * @type {string} + * @private + */ + this.id_ = id; + + /** + * Display name of the destination. + * @type {string} + * @private + */ + this.displayName_ = displayName; + + /** + * Whether the destination has been used recently. + * @type {boolean} + * @private + */ + this.isRecent_ = isRecent; + + /** + * Whether the destination is local or cloud-based. + * @type {boolean} + * @private + */ + this.isLocal_ = isLocal; + + /** + * Tags associated with the destination. + * @type {!Array.<string>} + * @private + */ + this.tags_ = opt_tags || []; + + /** + * Print capabilities of the destination. + * @type {print_preview.ChromiumCapabilities} + * @private + */ + this.capabilities_ = null; + + /** + * Cache of destination location fetched from tags. + * @type {string} + * @private + */ + this.location_ = null; + }; + + /** + * Prefix of the location destination tag. + * @type {string} + * @const + */ + Destination.LOCATION_TAG_PREFIX = '__cp__printer-location='; + + /** + * Enumeration of Google-promoted destination IDs. + * @enum {string} + */ + Destination.GooglePromotedId = { + DOCS: '__google__docs', + SAVE_AS_PDF: 'Save as PDF', + PRINT_WITH_CLOUD_PRINT: 'printWithCloudPrint' + }; + + Destination.prototype = { + /** @return {string} ID of the destination. */ + get id() { + return this.id_; + }, + + /** @return {string} Display name of the destination. */ + get displayName() { + return this.displayName_; + }, + + /** @return {boolean} Whether the destination has been used recently. */ + get isRecent() { + return this.isRecent_; + }, + + /** + * @param {boolean} isRecent Whether the destination has been used recently. + */ + set isRecent(isRecent) { + this.isRecent_ = isRecent; + }, + + /** @return {boolean} Whether the destination is local or cloud-based. */ + get isLocal() { + return this.isLocal_; + }, + + /** @return {boolean} Whether the destination is promoted by Google. */ + get isGooglePromoted() { + for (var key in Destination.GooglePromotedId) { + if (Destination.GooglePromotedId[key] == this.id_) { + return true; + } + } + return false; + }, + + /** + * @return {boolean} Whether the destination is the "Print with Cloud Print" + * destination. + */ + get isPrintWithCloudPrint() { + return this.id_ == Destination.GooglePromotedId.PRINT_WITH_CLOUD_PRINT; + }, + + /** + * @return {string} The location of the destination, or an empty string if + * the location is unknown. + */ + get location() { + if (this.location_ == null) { + for (var tag, i = 0; tag = this.tags_[i]; i++) { + if (tag.indexOf(Destination.LOCATION_TAG_PREFIX) == 0) { + this.location_ = tag.substring( + Destination.LOCATION_TAG_PREFIX.length); + } + } + if (this.location_ == null) { + this.location_ = ''; + } + } + return this.location_; + }, + + /** @return {!Array.<string>} Tags associated with the destination. */ + get tags() { + return this.tags_.slice(0); + }, + + /** + * @return {print_preview.ChromiumCapabilities} Print capabilities of the + * destination. + */ + get capabilities() { + return this.capabilities_; + }, + + /** + * @param {!print_preview.ChromiumCapabilities} capabilities Print + * capabilities of the destination. + */ + set capabilities(capabilities) { + this.capabilities_ = capabilities; + }, + + /** + * Matches a query against the destination. + * @param {string} query Query to match against the destination. + * @return {boolean} {@code true} if the query matches this destination, + * {@code false} otherwise. + */ + matches: function(query) { + return this.displayName_.toLowerCase().indexOf( + query.toLowerCase().trim()) != -1; + } + }; + + // Export + return { + Destination: Destination + }; +}); diff --git a/chrome/browser/resources/print_preview/data/destination_store.js b/chrome/browser/resources/print_preview/data/destination_store.js new file mode 100644 index 0000000..69f766a --- /dev/null +++ b/chrome/browser/resources/print_preview/data/destination_store.js @@ -0,0 +1,192 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * A data store that stores destinations and dispatches events when the data + * store changes. + * @constructor + * @extends {cr.EventTarget} + */ + function DestinationStore() { + cr.EventTarget.call(this); + + /** + * Internal backing store for the data store. + * @type {!Array.<print_preview.Destination>} + * @private + */ + this.destinations_ = []; + + /** + * Currently selected destination. + * @type {print_preview.Destination} + * @private + */ + this.selectedDestination_ = null; + + /** + * Initial destination ID used to auto-select the first inserted destination + * that matches. If {@code null}, the first destination inserted into the + * store will be selected. + * @type {?string} + * @private + */ + this.initialDestinationId_ = null; + + /** + * Whether the destination store will auto select the destination that + * matches the initial destination. + * @type {boolean} + * @private + */ + this.isInAutoSelectMode_ = false; + }; + + /** + * Event types dispatched by the data store. + * @enum {string} + */ + DestinationStore.EventType = { + DESTINATIONS_INSERTED: + 'print_preview.DestinationStore.DESTINATIONS_INSERTED', + DESTINATION_SELECT: 'print_preview.DestinationStore.DESTINATION_SELECT' + }; + + DestinationStore.prototype = { + __proto__: cr.EventTarget.prototype, + + /** + * @return {!Array.<!print_preview.Destination>} List of destinations in + * the store. + */ + get destinations() { + return this.destinations_.slice(0); + }, + + /** + * @return {print_preview.Destination} The currently selected destination or + * {@code null} if none is selected. + */ + get selectedDestination() { + return this.selectedDestination_; + }, + + /** + * Sets the initially selected destination. If any inserted destinations + * match this ID, that destination will be automatically selected. This + * occurs only once for every time this setter is called or if the store is + * cleared. + * @param {string} ID of the destination that should be selected + * automatically when added to the store. + */ + setInitialDestinationId: function(initialDestinationId) { + this.initialDestinationId_ = initialDestinationId; + this.isInAutoSelectMode_ = true; + if (this.initialDestinationId_ == null && this.destinations_.length > 0) { + this.selectDestination(this.destinations_[0]); + } else if (this.initialDestinationId_ != null) { + for (var dest, i = 0; dest = this.destinations_[i]; i++) { + if (dest.id == initialDestinationId) { + this.selectDestination(dest); + break; + } + } + } + }, + + /** @param {!print_preview.Destination} Destination to select. */ + selectDestination: function(destination) { + this.selectedDestination_ = destination; + this.selectedDestination_.isRecent = true; + this.isInAutoSelectMode_ = false; + cr.dispatchSimpleEvent( + this, DestinationStore.EventType.DESTINATION_SELECT); + }, + + /** + * Inserts a print destination to the data store and dispatches a + * DESTINATIONS_INSERTED event. If the destination matches the initial + * destination ID, then the destination will be automatically selected. + * @param {!print_preview.Destination} destination Print destination to + * insert. + */ + insertDestination: function(destination) { + this.destinations_.push(destination); + cr.dispatchSimpleEvent( + this, DestinationStore.EventType.DESTINATIONS_INSERTED); + if (this.isInAutoSelectMode_) { + if (this.initialDestinationId_ == null) { + this.selectDestination(destination); + } else { + if (destination.id == this.initialDestinationId_) { + this.selectDestination(destination); + } + } + } + }, + + /** + * Inserts multiple print destinations to the data store and dispatches one + * DESTINATIONS_INSERTED event. If any of the destinations match the initial + * destination ID, then that destination will be automatically selected. + * @param {!Array.<print_preview.Destination>} destinations Print + * destinations to insert. + */ + insertDestinations: function(destinations) { + this.destinations_ = this.destinations_.concat(destinations); + cr.dispatchSimpleEvent( + this, DestinationStore.EventType.DESTINATIONS_INSERTED); + if (this.isInAutoSelectMode_) { + if (this.initialDestinationId_ == null && destinations.length > 0) { + this.selectDestination(destinations[0]); + } else if (this.initialDestinationId_ != null) { + for (var dest, i = 0; dest = destinations[i]; i++) { + if (dest.id == this.initialDestinationId_) { + this.selectDestination(dest); + break; + } + } + } + } + }, + + /** + * Updates an existing print destination with capabilities information. If + * the destination doesn't already exist, it will be added. + * @param {!print_preview.Destination} destination Destination to update. + * @return {!print_preview.Destination} The existing destination that was + * updated. + */ + updateDestination: function(destination) { + var existingDestination = null; + for (var d, i = 0; d = this.destinations_[i]; i++) { + if (destination.id == d.id) { + existingDestination = d; + break; + } + } + if (existingDestination) { + existingDestination.capabilities = destination.capabilities; + return existingDestination; + } else { + this.insertDestination(destination); + } + }, + + /** Clears all print destinations. */ + clear: function() { + this.destinations_ = []; + this.selectedDestination_ = null; + this.isInAutoSelectMode_ = true; + } + }; + + // Export + return { + DestinationStore: DestinationStore + }; +}); diff --git a/chrome/browser/resources/print_preview/data/document_info.js b/chrome/browser/resources/print_preview/data/document_info.js new file mode 100644 index 0000000..dd2fdcd --- /dev/null +++ b/chrome/browser/resources/print_preview/data/document_info.js @@ -0,0 +1,55 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Object which contains information related to the document to print. + * @constructor + */ + function DocumentInfo() { + /** + * Whether the document to print is modifiable (i.e. can be reflowed). + * @type {boolean} + */ + this.isModifiable = true; + + /** + * Number of pages in the document to print. + * @type {number} + */ + this.pageCount = 1; + + /** + * Size of the pages of the document in points. + * @type {!print_preview.Size} + */ + this.pageSize = new print_preview.Size(0, 0); + + /** + * Printable area of the document in points. + * @type {!print_preview.PrintableArea} + */ + this.printableArea = new print_preview.PrintableArea( + new print_preview.Coordinate2d(0, 0), new print_preview.Size(0, 0)); + + /** + * Whether the document is styled by CSS media styles. + * @type {boolean} + */ + this.hasCssMediaStyles = false; + + /** + * Margins of the document in points. + * @type {print_preview.Margins} + */ + this.margins = null; + }; + + // Export + return { + DocumentInfo: DocumentInfo + }; +}); diff --git a/chrome/browser/resources/print_preview/data/local_parsers.js b/chrome/browser/resources/print_preview/data/local_parsers.js new file mode 100644 index 0000000..af58290 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/local_parsers.js @@ -0,0 +1,70 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** Namespace that contains a method to parse local print destinations. */ + function LocalDestinationParser() {}; + + /** + * Parses a local print destination. + * @param {object} destinationInfo Information describing a local print + * destination. + * @return {!print_preview.Destination} Parsed local print destination. + */ + LocalDestinationParser.parse = function(destinationInfo) { + return new print_preview.Destination( + destinationInfo.deviceName, + destinationInfo.printerName, + false /*isRecent*/, + true /*isLocal*/); + }; + + /** Namespace that contains a method to parse local print capabilities. */ + function LocalCapabilitiesParser() {}; + + /** + * Parses local print capabilities. + * @param {object} settingsInfo Object that describes local print + * capabilities. + * @return {!print_preview.ChromiumCapabilities} Parsed local print + * capabilities. + */ + LocalCapabilitiesParser.parse = function(settingsInfo) { + var hasColorCapability = false; + var defaultIsColorEnabled = false; + if (hasColorCapability = !settingsInfo['disableColorOption']) { + defaultIsColorEnabled = settingsInfo['setColorAsDefault']; + } + + var hasDuplexCapability = false; + var defaultIsDuplexEnabled = false; + if (hasDuplexCapability = + settingsInfo['printerDefaultDuplexValue'] != + print_preview.NativeLayer.DuplexMode.UNKNOWN_DUPLEX_MODE) { + defaultIsDuplexEnabled = + settingsInfo['printerDefaultDuplexValue'] == + print_preview.NativeLayer.DuplexMode.LONG_EDGE; + } + + return new print_preview.ChromiumCapabilities( + !settingsInfo['disableCopiesOption'] /*hasCopiesCapability*/, + '1' /*defaultCopiesStr*/, + true /*hasCollateCapability*/, + true /*defaultIsCollateEnabled*/, + hasDuplexCapability, + defaultIsDuplexEnabled, + !settingsInfo['disableLandscapeOption'] /*hasOrientationCapability*/, + false /*defaultIsLandscapeEnabled*/, + hasColorCapability, + defaultIsColorEnabled); + }; + + // Export + return { + LocalCapabilitiesParser: LocalCapabilitiesParser, + LocalDestinationParser: LocalDestinationParser + }; +}); diff --git a/chrome/browser/resources/print_preview/data/margins.js b/chrome/browser/resources/print_preview/data/margins.js new file mode 100644 index 0000000..cf6a6f8 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/margins.js @@ -0,0 +1,86 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Creates a Margins object that holds four margin values in points. + * @param {number} top The top margin in pts. + * @param {number} right The right margin in pts. + * @param {number} bottom The bottom margin in pts. + * @param {number} left The left margin in pts. + * @constructor + */ + function Margins(top, right, bottom, left) { + /** + * Backing store for the margin values in points. + * @type {Object.< + * print_preview.ticket_items.CustomMargins.Orientation, + * number>} + * @private + */ + this.value_ = {}; + this.value_[print_preview.ticket_items.CustomMargins.Orientation.TOP] = top; + this.value_[print_preview.ticket_items.CustomMargins.Orientation.RIGHT] = + right; + this.value_[print_preview.ticket_items.CustomMargins.Orientation.BOTTOM] = + bottom; + this.value_[print_preview.ticket_items.CustomMargins.Orientation.LEFT] = + left; + }; + + Margins.prototype = { + /** + * @param {print_preview.ticket_items.CustomMargins.Orientation} orientation + * Specifies the margin value to get. + * @return {number} Value of the margin of the given orientation. + */ + get: function(orientation) { + return this.value_[orientation]; + }, + + /** + * @param {print_preview.ticket_items.CustomMargins.Orientation} orientation + * Specifies the margin to set. + * @param {number} value Updated value of the margin in points to modify. + * @return {!print_preview.Margins} A new copy of |this| with the + * modification made to the specified margin. + */ + set: function(orientation, value) { + var newValue = {}; + for (var o in this.value_) { + newValue[o] = this.value_[o]; + } + newValue[orientation] = value; + return new Margins( + newValue[print_preview.ticket_items.CustomMargins.Orientation.TOP], + newValue[print_preview.ticket_items.CustomMargins.Orientation.RIGHT], + newValue[print_preview.ticket_items.CustomMargins.Orientation.BOTTOM], + newValue[print_preview.ticket_items.CustomMargins.Orientation.LEFT]); + }, + + /** + * @param {print_preview.Margins} other The other margins object to compare + * against. + * @return {boolean} Whether this margins object is equal to another. + */ + equals: function(other) { + if (other == null) { + return false; + } + for (var orientation in this.value_) { + if (this.value_[orientation] != other.value_[orientation]) { + return false; + } + } + return true; + } + }; + + // Export + return { + Margins: Margins + }; +}); diff --git a/chrome/browser/resources/print_preview/data/measurement_system.js b/chrome/browser/resources/print_preview/data/measurement_system.js new file mode 100644 index 0000000..ccee0d9a --- /dev/null +++ b/chrome/browser/resources/print_preview/data/measurement_system.js @@ -0,0 +1,159 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Measurement system of the print preview. Used to parse and serialize point + * measurements into the system's local units (e.g. millimeters, inches). + * @param {string} thousandsDelimeter Delimeter between thousands digits. + * @param {string} decimalDelimeter Delimeter between integers and decimals. + * @param {print_preview.MeasurementSystem.UnitType} unitType Measurement unit + * type of the system. + * @constructor + */ + function MeasurementSystem(thousandsDelimeter, decimalDelimeter, unitType) { + this.thousandsDelimeter_ = thousandsDelimeter || ','; + this.decimalDelimeter_ = decimalDelimeter || '.'; + this.unitType_ = unitType; + }; + + /** + * Parses |numberFormat| and extracts the symbols used for the thousands point + * and decimal point. + * @param {string} numberFormat The formatted version of the number 12345678. + * @return {!Array.<string>} The extracted symbols in the order + * [thousandsSymbol, decimalSymbol]. For example, + * parseNumberFormat("123,456.78") returns [",", "."]. + */ + MeasurementSystem.parseNumberFormat = function(numberFormat) { + if (!numberFormat) { + return [',', '.']; + } + var regex = /^(\d+)(\W?)(\d+)(\W?)(\d+)$/; + var matches = numberFormat.match(regex) || ['', '', ',', '', '.']; + return [matches[2], matches[4]]; + }; + + /** + * Enumeration of measurement unit types. + * @enum {number} + */ + MeasurementSystem.UnitType = { + METRIC: 0, // millimeters + IMPERIAL: 1 // inches + }; + + /** + * Maximum resolution of local unit values. + * @type {Object.<print_preview.MeasurementSystem.UnitType, number>} + * @private + */ + MeasurementSystem.Precision_ = {}; + MeasurementSystem.Precision_[MeasurementSystem.UnitType.METRIC] = 0.5; + MeasurementSystem.Precision_[MeasurementSystem.UnitType.IMPERIAL] = 0.01; + + /** + * Maximum number of decimal places to keep for local unit. + * @type {Object.<print_preview.MeasurementSystem.UnitType, number>} + * @private + */ + MeasurementSystem.DecimalPlaces_ = {}; + MeasurementSystem.DecimalPlaces_[MeasurementSystem.UnitType.METRIC] = 1; + MeasurementSystem.DecimalPlaces_[MeasurementSystem.UnitType.IMPERIAL] = 2; + + /** + * Number of points per inch. + * @type {number} + * @const + * @private + */ + MeasurementSystem.PTS_PER_INCH_ = 72.0; + + /** + * Number of points per millimeter. + * @type {number} + * @const + * @private + */ + MeasurementSystem.PTS_PER_MM_ = MeasurementSystem.PTS_PER_INCH_ / 25.4; + + MeasurementSystem.prototype = { + /** @return {string} The unit type symbol of the measurement system. */ + get unitSymbol() { + if (this.unitType_ == MeasurementSystem.UnitType.METRIC) { + return 'mm'; + } else if (this.unitType_ == MeasurementSystem.UnitType.IMPERIAL) { + return '"'; + } else { + throw Error('Unit type not supported: ' + this.unitType_); + } + }, + + /** + * @return {string} The thousands delimeter character of the measurement + * system. + */ + get thousandsDelimeter() { + return this.thousandsDelimeter_; + }, + + /** + * @return {string} The decimal delimeter character of the measurement + * system. + */ + get decimalDelimeter() { + return this.decimalDelimeter_; + }, + + setSystem: function(thousandsDelimeter, decimalDelimeter, unitType) { + this.thousandsDelimeter_ = thousandsDelimeter; + this.decimalDelimeter_ = decimalDelimeter; + this.unitType_ = unitType; + }, + + /** + * Rounds a value in the local system's units to the appropriate precision. + * @param {number} value Value to round. + * @return {number} Rounded value. + */ + roundValue: function(value) { + var precision = MeasurementSystem.Precision_[this.unitType_]; + var roundedValue = Math.round(value / precision) * precision; + // Truncate + return roundedValue.toFixed( + MeasurementSystem.DecimalPlaces_[this.unitType_]); + }, + + /** + * @param {number} pts Value in points to convert to local units. + * @return {number} Value in local units. + */ + convertFromPoints: function(pts) { + if (this.unitType_ == MeasurementSystem.UnitType.METRIC) { + return pts / MeasurementSystem.PTS_PER_MM_; + } else { + return pts / MeasurementSystem.PTS_PER_INCH_; + } + }, + + /** + * @param {number} Value in local units to convert to points. + * @return {number} Value in points. + */ + convertToPoints: function(localUnits) { + if (this.unitType_ == MeasurementSystem.UnitType.METRIC) { + return localUnits * MeasurementSystem.PTS_PER_MM_; + } else { + return localUnits * MeasurementSystem.PTS_PER_INCH_; + } + } + }; + + // Export + return { + MeasurementSystem: MeasurementSystem + }; +}); diff --git a/chrome/browser/resources/print_preview/data/measurement_system_unittest.gtestjs b/chrome/browser/resources/print_preview/data/measurement_system_unittest.gtestjs new file mode 100644 index 0000000..ed246c8 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/measurement_system_unittest.gtestjs @@ -0,0 +1,68 @@ +// Copyright (c) 2012 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. + +/** + * Test fixture for the MeasurementSystem. + * @constructor + * @extends {testing.Test} + */ +function MeasurementSystemUnitTest() { + testing.Test.call(this); +} + +MeasurementSystemUnitTest.prototype = { + __proto__: testing.Test.prototype, + + extraLibraries: [ + '../../shared/js/cr.js', + '../print_preview_utils.js', + 'measurement_system.js' + ] +}; + +TEST_F('MeasurementSystemUnitTest', 'parseNumberFormat', function() { + assertTrue(areArraysEqual( + ['.', ','], + print_preview.MeasurementSystem.parseNumberFormat('123.456,78'))); + assertTrue(areArraysEqual( + ['.', '.'], + print_preview.MeasurementSystem.parseNumberFormat('123.456.78'))); + assertTrue(areArraysEqual( + [',', '.'], + print_preview.MeasurementSystem.parseNumberFormat('123,456.78'))); + assertTrue(areArraysEqual( + [',', ','], + print_preview.MeasurementSystem.parseNumberFormat('123,456,78'))); + assertTrue(areArraysEqual( + [' ', ','], + print_preview.MeasurementSystem.parseNumberFormat('123 456,78'))); + assertTrue(areArraysEqual( + [' ', '.'], + print_preview.MeasurementSystem.parseNumberFormat('123 456.78'))); + assertTrue(areArraysEqual( + [' ', ' '], + print_preview.MeasurementSystem.parseNumberFormat('123 456 78'))); + assertTrue(areArraysEqual( + ['', ''], + print_preview.MeasurementSystem.parseNumberFormat('123'))); + + assertTrue(areArraysEqual( + [',', '.'], + print_preview.MeasurementSystem.parseNumberFormat('abcdef'))); + assertTrue(areArraysEqual( + [',', '.'], + print_preview.MeasurementSystem.parseNumberFormat(null))); + assertTrue(areArraysEqual( + [',', '.'], + print_preview.MeasurementSystem.parseNumberFormat(undefined))); + assertTrue(areArraysEqual( + [',', '.'], + print_preview.MeasurementSystem.parseNumberFormat(''))); + assertTrue(areArraysEqual( + [',', '.'], + print_preview.MeasurementSystem.parseNumberFormat('1'))); + assertTrue(areArraysEqual( + [',', '.'], + print_preview.MeasurementSystem.parseNumberFormat('12'))); +}); diff --git a/chrome/browser/resources/print_preview/data/page_number_set.js b/chrome/browser/resources/print_preview/data/page_number_set.js new file mode 100644 index 0000000..cccb6bf --- /dev/null +++ b/chrome/browser/resources/print_preview/data/page_number_set.js @@ -0,0 +1,105 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * An immutable ordered set of page numbers. + * @param {!Array.<number>} pageNumberList A list of page numbers to include + * in the set. + * @constructor + */ + function PageNumberSet(pageNumberList) { + /** + * Internal data store for the page number set. + * @type {!Array.<number>} + * @private + */ + this.pageNumberSet_ = pageListToPageSet(pageNumberList); + }; + + /** + * @param {string} pageRangeStr String form of a page range. I.e. '2,3,4-5'. + * If string is empty, all page numbers will be in the page number set. + * @param {number} totalPageCount Total number of pages in the original + * document. + * @return {print_preview.PageNumberSet} Page number set parsed from the + * given page range string and total page count. Null returned if + * the given page range string is invalid. + */ + PageNumberSet.parse = function(pageRangeStr, totalPageCount) { + if (pageRangeStr == '') { + var pageNumberList = []; + for (var i = 0; i < totalPageCount; i++) { + pageNumberList.push(i + 1); + } + return new PageNumberSet(pageNumberList); + } else { + return isPageRangeTextValid(pageRangeStr, totalPageCount) ? + new PageNumberSet( + pageRangeTextToPageList(pageRangeStr, totalPageCount)) : null; + } + }; + + PageNumberSet.prototype = { + /** @return {number} The number of page numbers in the set. */ + get size() { + return this.pageNumberSet_.length; + }, + + /** + * @param {number} index 0-based index of the page number to get. + * @return {number} Page number at the given index. + */ + getPageNumberAt: function(index) { + return this.pageNumberSet_[index]; + }, + + /** + * @param {number} 1-based page number to check for. + * @return {boolean} Whether the given page number is in the page range. + */ + hasPageNumber: function(pageNumber) { + return arrayContains(this.pageNumberSet_, pageNumber); + }, + + /** + * @param {number} 1-based number of the page to get index of. + * @return {number} 0-based index of the given page number with respect to + * all of the pages in the page range. + */ + getPageNumberIndex: function(pageNumber) { + return this.pageNumberSet_.indexOf(pageNumber); + }, + + /** + * @return {!Array.<object.<{from: number, to: number}>>} A list of page + * ranges suitable for use in the native layer. + */ + getPageRanges: function() { + return pageSetToPageRanges(this.pageNumberSet_); + }, + + /** @return {!Array.<number>} Array representation of the set. */ + asArray: function() { + return this.pageNumberSet_.slice(0); + }, + + /** + * @param {print_preview.PageNumberSet} other Page number set to compare + * against. + * @return {boolean} Whether another page number set is equal to this one. + */ + equals: function(other) { + return other == null ? + false : areArraysEqual(this.pageNumberSet_, other.pageNumberSet_); + } + }; + + // Export + return { + PageNumberSet: PageNumberSet + }; +}); diff --git a/chrome/browser/resources/print_preview/data/print_ticket_store.js b/chrome/browser/resources/print_preview/data/print_ticket_store.js new file mode 100644 index 0000000..e200798 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/print_ticket_store.js @@ -0,0 +1,629 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + // TODO(rltoscano): Maybe clear print ticket when destination changes. Or + // better yet, carry over any print ticket state that is possible. I.e. if + // destination changes, the new destination might not support duplex anymore, + // so we should clear the ticket's isDuplexEnabled state. + + /** + * Storage of the print ticket and document statistics. Dispatches events when + * the contents of the print ticket or document statistics change. Also + * handles validation of the print ticket against destination capabilities and + * against the document. + * @param {!print_preview.DestinationStore} destinationStore Used to + * understand which printer is selected. + * @constructor + * @extends {cr.EventTarget} + */ + function PrintTicketStore(destinationStore) { + cr.EventTarget.call(this); + + /** + * Destination store used to understand which printer is selected. + * @type {!print_preview.DestinationStore} + * @private + */ + this.destinationStore_ = destinationStore; + + // Create the document info with some initial settings. Actual + // page-related information won't be set until preview generation occurs, + // so we'll use some defaults until then. This way, the print ticket store + // will be valid even if no preview can be generated. + var initialPageSize = new print_preview.Size(612, 792); // 8.5"x11" + + /** + * Information about the document to print. + * @type {!print_preview.DocumentInfo} + * @private + */ + this.documentInfo_ = new print_preview.DocumentInfo(); + this.documentInfo_.isModifiable = true; + this.documentInfo_.pageCount = 1; + this.documentInfo_.pageSize = initialPageSize; + this.documentInfo_.printableArea = new print_preview.PrintableArea( + new print_preview.Coordinate2d(0, 0), initialPageSize); + + /** + * Printing capabilities of Chromium and the currently selected destination. + * @type {!print_preview.CapabilitiesHolder} + * @private + */ + this.capabilitiesHolder_ = new print_preview.CapabilitiesHolder(); + + /** + * Current measurement system. Used to work with margin measurements. + * @type {!print_preview.MeasurementSystem} + * @private + */ + this.measurementSystem_ = new print_preview.MeasurementSystem( + ',', '.', print_preview.MeasurementSystem.UnitType.IMPERIAL); + + /** + * Collate ticket item. + * @type {!print_preview.ticket_items.Collate} + * @private + */ + this.collate_ = + new print_preview.ticket_items.Collate(this.capabilitiesHolder_); + + /** + * Color ticket item. + * @type {!print_preview.ticket_items.Color} + * @private + */ + this.color_ = new print_preview.ticket_items.Color( + this.capabilitiesHolder_, this.destinationStore_); + + /** + * Copies ticket item. + * @type {!print_preview.ticket_items.Copies} + * @private + */ + this.copies_ = + new print_preview.ticket_items.Copies(this.capabilitiesHolder_); + + /** + * Duplex ticket item. + * @type {!print_preview.ticket_items.Duplex} + * @private + */ + this.duplex_ = + new print_preview.ticket_items.Duplex(this.capabilitiesHolder_); + + /** + * Landscape ticket item. + * @type {!print_preview.ticket_items.Landscape} + * @private + */ + this.landscape_ = new print_preview.ticket_items.Landscape( + this.capabilitiesHolder_, this.documentInfo_); + + /** + * Page range ticket item. + * @type {!print_preview.ticket_items.PageRange} + * @private + */ + this.pageRange_ = + new print_preview.ticket_items.PageRange(this.documentInfo_); + + /** + * Margins type ticket item. + * @type {!print_preview.ticket_items.MarginsType} + * @private + */ + this.marginsType_ = + new print_preview.ticket_items.MarginsType(this.documentInfo_); + + /** + * Custom margins ticket item. + * @type {!print_preview.ticket_items.CustomMargins} + * @private + */ + this.customMargins_ = new print_preview.ticket_items.CustomMargins( + this.documentInfo_, this.measurementSystem_); + + /** + * Header-footer ticket item. + * @type {!print_preview.ticket_items.HeaderFooter} + * @private + */ + this.headerFooter_ = new print_preview.ticket_items.HeaderFooter( + this.documentInfo_, this.marginsType_, this.customMargins_); + + /** + * Fit-to-page ticket item. + * @type {!print_preview.ticket_items.FitToPage} + * @private + */ + this.fitToPage_ = new print_preview.ticket_items.FitToPage( + this.documentInfo_, this.destinationStore_); + }; + + /** + * Event types dispatched by the print ticket store. + * @enum {string} + */ + PrintTicketStore.EventType = { + CAPABILITIES_CHANGE: 'print_preview.PrintTicketStore.CAPABILITIES_CHANGE', + DOCUMENT_CHANGE: 'print_preview.PrintTicketStore.DOCUMENT_CHANGE', + INITIALIZE: 'print_preview.PrintTicketStore.INITIALIZE', + TICKET_CHANGE: 'print_preview.PrintTicketStore.TICKET_CHANGE' + }; + + PrintTicketStore.prototype = { + __proto__: cr.EventTarget.prototype, + + /** @return {boolean} Whether the document is modifiable. */ + get isDocumentModifiable() { + return this.documentInfo_.isModifiable; + }, + + /** @return {number} Number of pages in the document. */ + get pageCount() { + return this.documentInfo_.pageCount; + }, + + /** + * @param {number} pageCount New number of pages in the document. + * Dispatches a DOCUMENT_CHANGE event if the value changes. + */ + updatePageCount: function(pageCount) { + if (this.documentInfo_.pageCount != pageCount) { + this.documentInfo_.pageCount = pageCount; + cr.dispatchSimpleEvent( + this, PrintTicketStore.EventType.DOCUMENT_CHANGE); + } + }, + + /** + * @return {!print_preview.PrintableArea} Printable area of the document in + * points. + */ + get printableArea() { + return this.documentInfo_.printableArea; + }, + + /** @return {!print_preview.Size} Size of the document in points. */ + get pageSize() { + return this.documentInfo_.pageSize; + }, + + /** + * Updates a subset of fields of the print document relating to the format + * of the page. + * @param {!print_preview.PrintableArea} printableArea New printable area of + * the document in points. Dispatches a DOCUMENT_CHANGE event if the + * value changes. + * @param {!print_preview.Size} pageSize New size of the document in points. + * Dispatches a DOCUMENT_CHANGE event if the value changes. + * @param {boolean} documentHasCssMediaStyles Whether the document is styled + * with CSS media styles. + * @param {!print_preview.Margins} margins Document margins in points. + */ + updateDocumentPageInfo: function( + printableArea, pageSize, documentHasCssMediaStyles, margins) { + if (!this.documentInfo_.printableArea.equals(printableArea) || + !this.documentInfo_.pageSize.equals(pageSize) || + this.documentInfo_.hasCssMediaStyles != documentHasCssMediaStyles || + this.documentInfo_.margins == null || + !this.documentInfo_.margins.equals(margins)) { + this.documentInfo_.printableArea = printableArea; + this.documentInfo_.pageSize = pageSize; + this.documentInfo_.hasCssMediaStyles = documentHasCssMediaStyles; + this.documentInfo_.margins = margins; + cr.dispatchSimpleEvent( + this, PrintTicketStore.EventType.DOCUMENT_CHANGE); + } + }, + + /** + * @return {!print_preview.MeasurementSystem} Measurement system of the + * local system. + */ + get measurementSystem() { + return this.measurementSystem_; + }, + + /** + * @return {print_preview.Margins} Document margins of the currently + * generated preview. + */ + getDocumentMargins: function() { + return this.documentInfo_.margins; + }, + + /** + * Initializes the print ticket store. Dispatches an INITIALIZE event. + * @param {boolean} isDocumentModifiable Whether the document to print is + * modifiable (i.e. can be re-flowed by Chromium). + * @param {?boolean} isDuplexEnabled Previous duplex setting. + * @param {?boolean} isHeaderFooterEnabled Previous header-footer setting. + * @param {?print_preview.ticket_items.MarginsType.Value} marginsType + * Previous margins type. + * @param {print_preview.Margins} customMargins Previous custom margins. + * @param {string} thousandsDelimeter Delimeter of the thousands place. + * @param {string} decimalDelimeter Delimeter of the decimal point. + * @param {print_preview.MeasurementSystem.UnitType} unitType Type of unit + * of the local measurement system. + */ + initialize: function( + isDocumentModifiable, + isDuplexEnabled, + isHeaderFooterEnabled, + marginsType, + customMargins, + thousandsDelimeter, + decimalDelimeter, + unitType) { + + this.documentInfo_.isModifiable = isDocumentModifiable; + this.measurementSystem_.setSystem( + thousandsDelimeter, decimalDelimeter, unitType); + + // Initialize ticket with user's previous values. + this.duplex_.updateValue(isDuplexEnabled); + this.headerFooter_.updateValue(isHeaderFooterEnabled); + if (marginsType != null) { + this.marginsType_.updateValue(marginsType); + } + if (customMargins != null) { + this.customMargins_.updateValue(customMargins); + } + }, + + /** + * Updates the capabilities of the destination the print ticket is for. + * Dispatches a CAPABILITIES_CHANGE event. + * @param {!print_preview.ChromiumCapabilities} caps New capabilities. + */ + updateDestinationCapabilities: function(caps) { + var isFirstUpdate = this.capabilitiesHolder_.get() == null; + this.capabilitiesHolder_.set(caps); + if (isFirstUpdate) { + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.INITIALIZE); + } else { + this.customMargins_.updateValue(null); + if (this.marginsType_.getValue() == + print_preview.ticket_items.MarginsType.Value.CUSTOM) { + this.marginsType_.updateValue( + print_preview.ticket_items.MarginsType.Value.DEFAULT); + } + cr.dispatchSimpleEvent( + this, PrintTicketStore.EventType.CAPABILITIES_CHANGE); + } + }, + + /** @return {boolean} Whether the ticket store has the copies capability. */ + hasCopiesCapability: function() { + return this.copies_.isCapabilityAvailable(); + }, + + /** + * @return {boolean} Whether the string representation of the copies value + * currently in the ticket store is valid. + */ + isCopiesValid: function() { + return this.copies_.isValid(); + }, + + isCopiesValidForValue: function(value) { + return this.copies_.wouldValueBeValid(value); + }, + + /** @return {number} Number of copies to print. */ + getCopies: function() { + return this.copies_.getValueAsNumber(); + }, + + /** + * @return {string} String representation of the number of copies to print. + */ + getCopiesStr: function() { + return this.copies_.getValue(); + }, + + /** + * Updates the string representation of the number of copies to print. + * Dispatches a TICKET_CHANGE event if the string value has changed. + * @param {string} New string representation of the number of copies to + * print. + */ + updateCopies: function(copies) { + if (this.copies_.getValue() != copies) { + this.copies_.updateValue(copies); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** @return {boolean} Whether the ticket store has a collate capability. */ + hasCollateCapability: function() { + return this.collate_.isCapabilityAvailable(); + }, + + /** @return {boolean} Whether collate is enabled. */ + isCollateEnabled: function() { + return this.collate_.getValue(); + }, + + /** + * Updates whether collate is enabled. Dispatches a TICKET_CHANGE event if + * collate has changed. + * @param {boolean} isCollateEnabled Whether collate is enabled. + */ + updateCollate: function(isCollateEnabled) { + if (this.collate_.getValue() != isCollateEnabled) { + this.collate_.updateValue(isCollateEnabled); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** + * @return {boolean} Whether the ticket store has color printing capability. + */ + hasColorCapability: function() { + return this.color_.isCapabilityAvailable(); + }, + + /** @return {boolean} Whether color printing is enabled. */ + isColorEnabled: function() { + return this.color_.getValue(); + }, + + /** + * Updates whether color printing is enabled. Dispatches a TICKET_CHANGE if + * color has changed. + * @param {boolean} isColorEnabled Whether the color printing is enabled. + */ + updateColor: function(isColorEnabled) { + if (this.color_.getValue() != isColorEnabled) { + this.color_.updateValue(isColorEnabled); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** @return {boolean} Whether the header-footer capability is available. */ + hasHeaderFooterCapability: function() { + return this.headerFooter_.isCapabilityAvailable(); + }, + + /** @return {boolean} Whether the header-footer setting is enabled. */ + isHeaderFooterEnabled: function() { + return this.headerFooter_.getValue(); + }, + + /** + * Updates the whether the header-footer setting is enabled. Dispatches a + * TICKET_CHANGE event if the setting changed. + * @param {boolean} isHeaderFooterEnabled Whether the header-footer setting + * is enabled. + */ + updateHeaderFooter: function(isHeaderFooterEnabled) { + if (this.headerFooter_.getValue() != isHeaderFooterEnabled) { + this.headerFooter_.updateValue(isHeaderFooterEnabled); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** + * @return {boolean} Whether the page orientation capability is available. + */ + hasOrientationCapability: function() { + return this.landscape_.isCapabilityAvailable(); + }, + + /** + * @return {boolean} Whether the document should be printed in landscape. + */ + isLandscapeEnabled: function() { + return this.landscape_.getValue(); + }, + + /** + * Updates whether the document should be printed in landscape. Dispatches + * a TICKET_CHANGE event if the setting changes. + * @param {boolean} isLandscapeEnabled Whether the document should be + * printed in landscape. + */ + updateOrientation: function(isLandscapeEnabled) { + if (this.landscape_.getValue() != isLandscapeEnabled) { + this.landscape_.updateValue(isLandscapeEnabled); + // Reset the user set margins. + this.marginsType_.updateValue( + print_preview.ticket_items.MarginsType.Value.DEFAULT); + this.customMargins_.updateValue(null); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** @return {boolean} Whether the duplexing capability is available. */ + hasDuplexCapability: function() { + return this.duplex_.isCapabilityAvailable(); + }, + + /** @return {boolean} Whether the document should be printed in duplex. */ + isDuplexEnabled: function() { + return this.duplex_.getValue(); + }, + + /** + * Updates the duplexing setting. Dispatches a TICKET_CHANGE event if the + * value changes. + * @param {boolean} isDuplexEnabled Whether the document should be printed + * in duplex. + */ + updateDuplex: function(isDuplexEnabled) { + if (this.duplex_.getValue() != isDuplexEnabled) { + this.duplex_.updateValue(isDuplexEnabled); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** @return {boolean} Whether the margins capability is available. */ + hasMarginsCapability: function() { + return this.marginsType_.isCapabilityAvailable(); + }, + + /** + * @return {print_preview.ticket_items.MarginsType.Value} Type of predefined + * margins. + */ + getMarginsType: function() { + return this.marginsType_.getValue(); + }, + + /** + * Updates the type of predefined margins. Dispatches a TICKET_CHANGE event + * if the margins type changes. + * @param {print_preview.ticket_items.MarginsType.Value} marginsType Type of + * predefined margins. + */ + updateMarginsType: function(marginsType) { + if (this.marginsType_.getValue() != marginsType) { + this.marginsType_.updateValue(marginsType); + if (marginsType == + print_preview.ticket_items.MarginsType.Value.CUSTOM) { + // If CUSTOM, set the value of the custom margins so that it won't be + // overridden by the default value. + this.customMargins_.updateValue(this.customMargins_.getValue()); + } + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** @return {boolean} Whether all of the custom margins are valid. */ + isCustomMarginsValid: function() { + return this.customMargins_.isValid(); + }, + + /** + * @return {!print_preview.Margins} Custom margins of the document in + * points. + */ + getCustomMargins: function() { + return this.customMargins_.getValue(); + }, + + /** + * @param {print_preview.ticket_items.CustomMargins.Orientation} orientation + * Specifies the margin to get the maximum value for. + * @return {number} Maximum value in points of the specified margin. + */ + getCustomMarginMax: function(orientation) { + return this.customMargins_.getMarginMax(orientation); + }, + + /** + * Updates the custom margins of the document. Dispatches a TICKET_CHANGE + * event if the margins have changed. + * @param {!print_preview.Margins} margins New document page margins in + * points. + */ + updateCustomMargins: function(margins) { + if (!this.isCustomMarginsValid() || + !margins.equals(this.getCustomMargins())) { + this.customMargins_.updateValue(margins); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** + * Updates a single custom margin's value in points. + * @param {print_preview.ticket_items.CustomMargins.Orientation} orientation + * Specifies the margin to update. + * @param {number} value Updated margin in points. + */ + updateCustomMargin: function(orientation, value) { + if (this.customMargins_.getValue().get(orientation) != value) { + this.customMargins_.updateMargin(orientation, value); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** @return {boolean} Whether the page range capability is available. */ + hasPageRangeCapability: function() { + return this.pageRange_.isCapabilityAvailable(); + }, + + /** + * @return {boolean} Whether the current page range string is defines a + * valid page number set. + */ + isPageRangeValid: function() { + return this.pageRange_.isValid(); + }, + + /** @return {string} String representation of the page range. */ + getPageRangeStr: function() { + return this.pageRange_.getValue(); + }, + + /** + * @return {!print_preview.PageNumberSet} Page number set specified by the + * string representation of the page range string. + */ + getPageNumberSet: function() { + return this.pageRange_.getPageNumberSet(); + }, + + /** + * Updates the page range string. Dispatches a TICKET_CHANGE if the string + * changed. + * @param {string} pageRangeStr New page range string. + */ + updatePageRange: function(pageRangeStr) { + if (this.pageRange_.getValue() != pageRangeStr) { + this.pageRange_.updateValue(pageRangeStr); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** @return {boolean} Whether the fit-to-page capability is available. */ + hasFitToPageCapability: function() { + return this.fitToPage_.isCapabilityAvailable(); + }, + + /** @return {boolean} Whether the fit-to-page capability is enabled. */ + isFitToPageEnabled: function() { + return this.fitToPage_.getValue(); + }, + + /** + * @param {boolean} isFitToPageEnabled Whether to enable the fit-to-page + * capability. + */ + updateFitToPage: function(isFitToPageEnabled) { + if (this.fitToPage_.getValue() != isFitToPageEnabled) { + this.fitToPage_.updateValue(isFitToPageEnabled); + cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.TICKET_CHANGE); + } + }, + + /** + * @return {boolean} {@code true} if the stored print ticket is valid, + * {@code false} otherwise. + */ + isTicketValid: function() { + return this.isTicketValidForPreview() && + (!this.hasPageRangeCapability() || this.isPageRangeValid()); + }, + + /** @return {boolean} Whether the ticket is valid for preview generation. */ + isTicketValidForPreview: function() { + return (!this.hasCopiesCapability() || this.isCopiesValid()) && + (!this.hasMarginsCapability() || + this.getMarginsType() != + print_preview.ticket_items.MarginsType.Value.CUSTOM || + this.isCustomMarginsValid()); + } + }; + + // Export + return { + PrintTicketStore: PrintTicketStore + }; +}); diff --git a/chrome/browser/resources/print_preview/data/printable_area.js b/chrome/browser/resources/print_preview/data/printable_area.js new file mode 100644 index 0000000..4dd4955 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/printable_area.js @@ -0,0 +1,64 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Object describing the printable area of a page in the document. + * @param {!print_preview.Coordinate2d} origin Top left corner of the + * printable area of the document. + * @param {!print_preview.Size} size Size of the printable area of the + * document. + * @constructor + */ + function PrintableArea(origin, size) { + /** + * Top left corner of the printable area of the document. + * @type {!print_preview.Coordinate2d} + * @private + */ + this.origin_ = origin; + + /** + * Size of the printable area of the document. + * @type {!print_preview.Size} + * @private + */ + this.size_ = size; + }; + + PrintableArea.prototype = { + /** + * @return {!print_preview.Coordinate2d} Top left corner of the printable + * area of the document. + */ + get origin() { + return this.origin_; + }, + + /** + * @return {!print_preview.Size} Size of the printable area of the document. + */ + get size() { + return this.size_; + }, + + /** + * @param {print_preview.PrintableArea} other Other printable area to check + * for equality. + * @return {boolean} Whether another printable area is equal to this one. + */ + equals: function(other) { + return other != null && + this.origin_.equals(other.origin_) && + this.size_.equals(other.size_); + } + }; + + // Export + return { + PrintableArea: PrintableArea + }; +}); diff --git a/chrome/browser/resources/print_preview/data/size.js b/chrome/browser/resources/print_preview/data/size.js new file mode 100644 index 0000000..7b43418 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/size.js @@ -0,0 +1,56 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Immutable two-dimensional size. + * @param {number} width Width of the size. + * @param {number} height Height of the size. + * @constructor + */ + function Size(width, height) { + /** + * Width of the size. + * @type {number} + * @private + */ + this.width_ = width; + + /** + * Height of the size. + * @type {number} + * @private + */ + this.height_ = height; + }; + + Size.prototype = { + /** @return {number} Width of the size. */ + get width() { + return this.width_; + }, + + /** @return {number} Height of the size. */ + get height() { + return this.height_; + }, + + /** + * @param {print_preview.Size} other Other size object to compare against. + * @return {boolean} Whether this size object is equal to another. + */ + equals: function(other) { + return other != null && + this.width_ == other.width_ && + this.height_ == other.height_; + } + }; + + // Export + return { + Size: Size + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/collate.js b/chrome/browser/resources/print_preview/data/ticket_items/collate.js new file mode 100644 index 0000000..10ddcf1 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/collate.js @@ -0,0 +1,57 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Collate ticket item whose value is a {@code boolean} that indicates whether + * collation is enabled. + * @param {!print_preview.CapabilitiesHolder} capabilitiesHolder Capabilities + * holder used to determine the default collate value and if the collate + * capability is available. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function Collate(capabilitiesHolder) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Capabilities holder used to determine the default collate value and if + * the collate capability is available. + * @type {!print_preview.CapabilitiesHolder} + * @private + */ + this.capabilitiesHolder_ = capabilitiesHolder; + }; + + Collate.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + return true; + }, + + /** @override */ + isCapabilityAvailable: function() { + return this.capabilitiesHolder_.get().hasCollateCapability; + }, + + /** @override */ + getDefaultValueInternal: function() { + return this.capabilitiesHolder_.get().defaultIsCollateEnabled; + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return false; + } + }; + + // Export + return { + Collate: Collate + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/color.js b/chrome/browser/resources/print_preview/data/ticket_items/color.js new file mode 100644 index 0000000..b03d407 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/color.js @@ -0,0 +1,71 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Color ticket item whose value is a {@code boolean} that indicates whether + * the document should be printed in color. + * @param {!print_preview.CapabilitiesHolder} capabilitiesHolder Capabilities + * holder used to determine the default color value and if the color + * capability is available. + * @param {!print_preview.DestinationStore} destinationStore Used to determine + * whether color printing should be available. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function Color(capabilitiesHolder, destinationStore) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Capabilities holder used to determine the default color value and if the + * color capability is available. + * @type {!print_preview.CapabilitiesHolder} + * @private + */ + this.capabilitiesHolder_ = capabilitiesHolder; + + /** + * Used to determine whether color printing should be available. + * @type {!print_preview.DestinationStore} + * @private + */ + this.destinationStore_ = destinationStore; + }; + + Color.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + return true; + }, + + /** @override */ + isCapabilityAvailable: function() { + return this.capabilitiesHolder_.get().hasColorCapability && + (!this.destinationStore_.selectedDestination || + this.destinationStore_.selectedDestination.id != + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF); + }, + + /** @override */ + getDefaultValueInternal: function() { + return this.capabilitiesHolder_.get().defaultIsColorEnabled; + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return this.destinationStore_.selectedDestination && + this.destinationStore_.selectedDestination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF; + } + }; + + // Export + return { + Color: Color + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/copies.js b/chrome/browser/resources/print_preview/data/ticket_items/copies.js new file mode 100644 index 0000000..7cd1620 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/copies.js @@ -0,0 +1,70 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Copies ticket item whose value is a {@code string} that indicates how many + * copies of the document should be printed. The ticket item is backed by a + * string since the user can textually input the copies value. + * @param {!print_preview.CapabilitiesHolder} capabilitiesHolder Capabilities + * holder used to determine the default number of copies and if the copies + * capability is available. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function Copies(capabilitiesHolder) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Capabilities holder used to determine the default number of copies and if + * the copies capability is available. + * @type {!print_preview.CapabilitiesHolder} + * @private + */ + this.capabilitiesHolder_ = capabilitiesHolder; + }; + + Copies.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + if (/[^\d]+/.test(value)) { + return false; + } + var copies = parseInt(value); + if (copies > 999 || copies < 1) { + return false; + } + return true; + }, + + /** @override */ + isCapabilityAvailable: function() { + return this.capabilitiesHolder_.get().hasCopiesCapability; + }, + + /** @return {number} The number of copies indicated by the ticket item. */ + getValueAsNumber: function() { + return parseInt(this.getValue()); + }, + + /** @override */ + getDefaultValueInternal: function() { + return this.capabilitiesHolder_.get().defaultCopiesStr; + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return '1'; + } + }; + + // Export + return { + Copies: Copies + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/custom_margins.js b/chrome/browser/resources/print_preview/data/ticket_items/custom_margins.js new file mode 100644 index 0000000..774e35b --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/custom_margins.js @@ -0,0 +1,171 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Custom page margins ticket item whose value is a + * {@code print_preview.Margins}. + * @param {!print_preview.DocumentInfo} documentInfo Information about the + * document to print. + * @param {!print_preview.MeasurementSystem} measurementSystem Used to convert + * from string input into measurements in points. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function CustomMargins(documentInfo, measurementSystem) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Information about the document to print. + * @type {!print_preview.DocumentInfo} + * @private + */ + this.documentInfo_ = documentInfo; + + /** + * Used to convert from string input to measurements in points. + * @type {!print_preview.MeasurementSystem} + * @private + */ + this.measurementSystem_ = measurementSystem; + }; + + /** + * Enumeration of the orientations of margins. + * @enum {string} + */ + CustomMargins.Orientation = { + TOP: 'top', + RIGHT: 'right', + BOTTOM: 'bottom', + LEFT: 'left' + }; + + /** + * Mapping of a margin orientation to its opposite. + * @type {object.<CustomMargins.Orientation, CustomMargins.Orientation>} + * @private + */ + CustomMargins.OppositeOrientation_ = {}; + CustomMargins.OppositeOrientation_[CustomMargins.Orientation.TOP] = + CustomMargins.Orientation.BOTTOM; + CustomMargins.OppositeOrientation_[CustomMargins.Orientation.RIGHT] = + CustomMargins.Orientation.LEFT; + CustomMargins.OppositeOrientation_[CustomMargins.Orientation.BOTTOM] = + CustomMargins.Orientation.TOP; + CustomMargins.OppositeOrientation_[CustomMargins.Orientation.LEFT] = + CustomMargins.Orientation.RIGHT; + + /** + * Minimum distance in points that two margins can be separated by. + * @type {number} + * @const + * @private + */ + CustomMargins.MINIMUM_MARGINS_DISTANCE_ = 72; // 1 inch. + + CustomMargins.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + var margins = /** @type {!print_preview.Margins} */ (value); + for (var key in CustomMargins.Orientation) { + var o = CustomMargins.Orientation[key]; + var max = this.getMarginMax_( + o, margins.get(CustomMargins.OppositeOrientation_[o])); + if (margins.get(o) > max || margins.get(o) < 0) { + return false; + } + } + return true; + }, + + /** @override */ + isCapabilityAvailable: function() { + return this.documentInfo_.isModifiable; + }, + + /** + * @param {print_preview.ticket_items.CustomMargins.Orientation} orientation + * Specifies the margin to get the maximum value for. + * @return {number} Maximum value in points of the specified margin. + */ + getMarginMax: function(orientation) { + var oppositeOrient = CustomMargins.OppositeOrientation_[orientation]; + var margins = /** @type {!print_preview.Margins} */ (this.getValue()); + return this.getMarginMax_(orientation, margins.get(oppositeOrient)); + }, + + /** @override */ + updateValue: function(value) { + var margins = /** @type {!InputMargins} */ (value); + if (margins != null) { + margins = new print_preview.Margins( + Math.round(margins.get(CustomMargins.Orientation.TOP)), + Math.round(margins.get(CustomMargins.Orientation.RIGHT)), + Math.round(margins.get(CustomMargins.Orientation.BOTTOM)), + Math.round(margins.get(CustomMargins.Orientation.LEFT))); + } + print_preview.ticket_items.TicketItem.prototype.updateValue.call( + this, margins); + }, + + /** + * Updates the specified margin in points while keeping the value within + * a maximum and minimum. + * @param {print_preview.ticket_items.CustomMargins.Orientation} orientation + * Specifies the margin to update. + * @param {number} value Updated margin value in points. + */ + updateMargin: function(orientation, value) { + var margins = /** @type {!print_preview.Margins} */ (this.getValue()); + var oppositeOrientation = CustomMargins.OppositeOrientation_[orientation]; + var max = + this.getMarginMax_(orientation, margins.get(oppositeOrientation)); + value = Math.max(0, Math.min(max, value)); + this.updateValue(margins.set(orientation, value)); + }, + + /** @override */ + getDefaultValueInternal: function() { + return this.documentInfo_.margins || + new print_preview.Margins(72, 72, 72, 72); + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return this.documentInfo_.margins || + new print_preview.Margins(72, 72, 72, 72); + }, + + /** + * @param {print_preview.ticket_items.CustomMargins.Orientation} orientation + * Specifies which margin to get the maximum value of. + * @param {number} oppositeMargin Value of the margin in points + * opposite the specified margin. + * @return {number} Maximum value in points of the specified margin. + * @private + */ + getMarginMax_: function(orientation, oppositeMargin) { + var max; + if (orientation == CustomMargins.Orientation.TOP || + orientation == CustomMargins.Orientation.BOTTOM) { + max = this.documentInfo_.pageSize.height - oppositeMargin - + CustomMargins.MINIMUM_MARGINS_DISTANCE_; + } else { + max = this.documentInfo_.pageSize.width - oppositeMargin - + CustomMargins.MINIMUM_MARGINS_DISTANCE_; + } + return Math.round(max); + } + }; + + // Export + return { + CustomMargins: CustomMargins + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/duplex.js b/chrome/browser/resources/print_preview/data/ticket_items/duplex.js new file mode 100644 index 0000000..c482982 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/duplex.js @@ -0,0 +1,57 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Duplex ticket item whose value is a {@code boolean} that indicates whether + * the document should be duplex printed. + * @param {!print_preview.CapabilitiesHolder} capabilitiesHolder Capabilities + * holder used to determine the default duplex value and if duplexing + * is available. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function Duplex(capabilitiesHolder) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Capabilities holder used to determine the default duplex value and if + * duplexing is available. + * @type {!print_preview.CapabilitiesHolder} + * @private + */ + this.capabilitiesHolder_ = capabilitiesHolder; + }; + + Duplex.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + return true; + }, + + /** @override */ + isCapabilityAvailable: function() { + return this.capabilitiesHolder_.get().hasDuplexCapability; + }, + + /** @override */ + getDefaultValueInternal: function() { + return this.capabilitiesHolder_.get().defaultIsDuplexEnabled; + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return false; + } + }; + + // Export + return { + Duplex: Duplex + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/fit_to_page.js b/chrome/browser/resources/print_preview/data/ticket_items/fit_to_page.js new file mode 100644 index 0000000..40d4a75 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/fit_to_page.js @@ -0,0 +1,69 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Fit-to-page ticket item whose value is a {@code boolean} that indicates + * whether to scale the document to fit the page. + * @param {!print_preview.DocumentInfo} documentInfo Information about the + * document to print. + * @param {!print_preview.DestinationStore} destinationStore Used to determine + * whether fit to page should be available. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function FitToPage(documentInfo, destinationStore) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Information about the document to print. + * @type {!print_preview.DocumentInfo} + * @private + */ + this.documentInfo_ = documentInfo; + + /** + * Used to determine whether fit to page should be available. + * @type {!print_preview.DestinationStore} + * @private + */ + this.destinationStore_ = destinationStore; + }; + + FitToPage.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + return true; + }, + + /** @override */ + isCapabilityAvailable: function() { + return !this.documentInfo_.isModifiable && + (!this.destinationStore_.selectedDestination || + this.destinationStore_.selectedDestination.id != + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF); + }, + + /** @override */ + getDefaultValueInternal: function() { + return true; + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return this.destinationStore_.selectedDestination && + this.destinationStore_.selectedDestination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF; + } + }; + + // Export + return { + FitToPage: FitToPage + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/header_footer.js b/chrome/browser/resources/print_preview/data/ticket_items/header_footer.js new file mode 100644 index 0000000..db8e6ff --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/header_footer.js @@ -0,0 +1,95 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Header-footer ticket item whose value is a {@code boolean} that indicates + * whether the document should be printed with headers and footers. + * @param {!print_preview.DocumentInfo} documentInfo Information about the + * document to print. + * @param {!print_preview.ticket_items.MarginsType} marginsType Ticket item + * that stores which predefined margins to print with. + * @param {!print_preview.ticket_items.CustomMargins} customMargins Ticket + * item that stores custom margin values. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function HeaderFooter(documentInfo, marginsType, customMargins) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Information about the document to print. + * @type {!print_preview.DocumentInfo} + * @private + */ + this.documentInfo_ = documentInfo; + + /** + * Ticket item that stores which predefined margins to print with. + * @type {!print_preview.ticket_items.MarginsType} + * @private + */ + this.marginsType_ = marginsType; + + /** + * Ticket item that stores custom margin values. + * @type {!print_preview.ticket_items.CustomMargins} + * @private + */ + this.customMargins_ = customMargins; + }; + + HeaderFooter.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + return true; + }, + + /** @override */ + isCapabilityAvailable: function() { + if (!this.documentInfo_.isModifiable) { + return false; + } else if (this.marginsType_.getValue() == + print_preview.ticket_items.MarginsType.Value.NO_MARGINS) { + return false; + } else if (this.marginsType_.getValue() == + print_preview.ticket_items.MarginsType.Value.MINIMUM) { + return true; + } + var margins; + if (this.marginsType_.getValue() == + print_preview.ticket_items.MarginsType.Value.CUSTOM) { + if (!this.customMargins_.isValid()) { + return false; + } + margins = this.customMargins_.getValue(); + } else { + margins = this.documentInfo_.margins; + } + var orientEnum = print_preview.ticket_items.CustomMargins.Orientation; + return margins == null || + margins.get(orientEnum.TOP) > 0 || + margins.get(orientEnum.BOTTOM) > 0; + }, + + /** @override */ + getDefaultValueInternal: function() { + return true; + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return false; + } + }; + + // Export + return { + HeaderFooter: HeaderFooter + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/landscape.js b/chrome/browser/resources/print_preview/data/ticket_items/landscape.js new file mode 100644 index 0000000..eac17bb --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/landscape.js @@ -0,0 +1,72 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Landscape ticket item whose value is a {@code boolean} that indicates + * whether the document should be printed in landscape orientation. + * @param {!print_preview.CapabilitiesHolder} capabilitiesHolder Capabilities + * holder used to determine the default landscape value and if landscape + * printing is available. + * @param {!print_preview.DocumentInfo} documentInfo Information about the + * document to print. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function Landscape(capabilitiesHolder, documentInfo) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Capabilities holder used to determine the default landscape value and if + * landscape printing is available. + * @type {!print_preview.CapabilitiesHolder} + * @private + */ + this.capabilitiesHolder_ = capabilitiesHolder; + + /** + * Information about the document to print. + * @type {!print_preview.DocumentInfo} + * @private + */ + this.documentInfo_ = documentInfo; + }; + + Landscape.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + return true; + }, + + /** @override */ + isCapabilityAvailable: function() { + // TODO(rltoscano): Technically, the print destination can still change + // the orientation of the print out (at least for cloud printers) if the + // document is not modifiable. But the preview wouldn't update in this + // case so it would be a bad user experience. + return this.documentInfo_.isModifiable && + !this.documentInfo_.hasCssMediaStyles && + this.capabilitiesHolder_.get().hasOrientationCapability; + }, + + /** @override */ + getDefaultValueInternal: function() { + return this.capabilitiesHolder_.get().defaultIsLandscapeEnabled; + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return false; + } + }; + + // Export + return { + Landscape: Landscape + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/margins_type.js b/chrome/browser/resources/print_preview/data/ticket_items/margins_type.js new file mode 100644 index 0000000..3fe95f4 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/margins_type.js @@ -0,0 +1,68 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Margins type ticket item whose value is a + * {@link print_preview.ticket_items.MarginsType.Value} that indicates what + * predefined margins type to use. + * @param {!print_preview.DocumentInfo} documentInfo Information about the + * document to print. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function MarginsType(documentInfo) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Information about the document to print. + * @type {!print_preview.DocumentInfo} + * @private + */ + this.documentInfo_ = documentInfo; + }; + + /** + * Enumeration of margin types. Matches enum MarginType in + * printing/print_job_constants.h. + * @enum {number} + */ + MarginsType.Value = { + DEFAULT: 0, + NO_MARGINS: 1, + MINIMUM: 2, + CUSTOM: 3 + }; + + MarginsType.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + return true; + }, + + /** @override */ + isCapabilityAvailable: function() { + return this.documentInfo_.isModifiable; + }, + + /** @override */ + getDefaultValueInternal: function() { + return MarginsType.Value.DEFAULT; + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return MarginsType.Value.DEFAULT; + } + }; + + // Export + return { + MarginsType: MarginsType + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/page_range.js b/chrome/browser/resources/print_preview/data/ticket_items/page_range.js new file mode 100644 index 0000000..2fc7152 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/page_range.js @@ -0,0 +1,70 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * Page range ticket item whose value is a {@code string} that represents + * which pages in the document should be printed. + * @param {!print_preview.DocumentInfo} documentInfo Information about the + * document to print. + * @constructor + * @extends {print_preview.ticket_items.TicketItem} + */ + function PageRange(documentInfo) { + print_preview.ticket_items.TicketItem.call(this); + + /** + * Information about the document to print. + * @type {!print_preview.DocumentInfo} + * @private + */ + this.documentInfo_ = documentInfo; + }; + + PageRange.prototype = { + __proto__: print_preview.ticket_items.TicketItem.prototype, + + /** @override */ + wouldValueBeValid: function(value) { + return value == '' || + isPageRangeTextValid(value, this.documentInfo_.pageCount); + }, + + /** + * @return {!print_preview.PageNumberSet} Set of page numbers defined by the + * page range string. + */ + getPageNumberSet: function() { + if (this.isValid()) { + return print_preview.PageNumberSet.parse( + this.getValue(), this.documentInfo_.pageCount); + } else { + return print_preview.PageNumberSet.parse( + this.getDefaultValueInternal(), this.documentInfo_.pageCount); + } + }, + + /** @override */ + isCapabilityAvailable: function() { + return true; + }, + + /** @override */ + getDefaultValueInternal: function() { + return ''; + }, + + /** @override */ + getCapabilityNotAvailableValueInternal: function() { + return ''; + } + }; + + // Export + return { + PageRange: PageRange + }; +}); diff --git a/chrome/browser/resources/print_preview/data/ticket_items/ticket_item.js b/chrome/browser/resources/print_preview/data/ticket_items/ticket_item.js new file mode 100644 index 0000000..1dffd20 --- /dev/null +++ b/chrome/browser/resources/print_preview/data/ticket_items/ticket_item.js @@ -0,0 +1,95 @@ +// Copyright (c) 2012 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.ticket_items', function() { + 'use strict'; + + /** + * An object that represents a user modifiable item in a print ticket. Each + * ticket item has a value which can be set by the user. Ticket items can also + * be unavailable for modifying if the print destination doesn't support it or + * if other ticket item constraints are not met. + * @constructor + */ + function TicketItem() { + /** + * Backing store of the print ticket item. + * @type {Object} + * @private + */ + this.value_ = null; + }; + + TicketItem.prototype = { + /** + * Determines whether a given value is valid for the ticket item. + * @param {Object} value The value to check for validity. + * @return {boolean} Whether the given value is valid for the ticket item. + */ + wouldValueBeValid: function(value) { + throw Error('Abstract method not overridden'); + }, + + /** + * @return {boolean} Whether the print destination capability is available. + */ + isCapabilityAvailable: function() { + throw Error('Abstract method not overridden'); + }, + + /** @return {object} The value of the ticket item. */ + getValue: function() { + if (this.isCapabilityAvailable()) { + if (this.value_ == null) { + return this.getDefaultValueInternal(); + } else { + return this.value_; + } + } else { + return this.getCapabilityNotAvailableValueInternal(); + } + }, + + /** @return {boolean} Whether the ticket item was modified by the user. */ + isUserEdited: function() { + return this.value_ != null; + }, + + /** @return {boolean} Whether the ticket item's value is valid. */ + isValid: function() { + if (!this.isUserEdited()) { + return true; + } + return this.wouldValueBeValid(this.value_); + }, + + /** @param {object} Value to set as the value of the ticket item. */ + updateValue: function(value) { + this.value_ = value; + }, + + /** + * @return {object} Default value of the ticket item if no value was set by + * the user. + * @protected + */ + getDefaultValueInternal: function() { + throw Error('Abstract method not overridden'); + }, + + /** + * @return {object} Default value of the ticket item if the capability is + * not available. + * @protected + */ + getCapabilityNotAvailableValueInternal: function() { + throw Error('Abstract method not overridden'); + } + }; + + // Export + return { + TicketItem: TicketItem + }; +}); diff --git a/chrome/browser/resources/print_preview/fit_to_page_settings.js b/chrome/browser/resources/print_preview/fit_to_page_settings.js deleted file mode 100644 index 6747127..0000000 --- a/chrome/browser/resources/print_preview/fit_to_page_settings.js +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Creates a |FitToPageSettings| object. This object encapsulates all - * settings and logic related to the fit to page checkbox. - * @constructor - */ - function FitToPageSettings() { - // @type {HTMLDivElement} This represents fit to page div element. - this.fitToPageOption_ = $('fit-to-page-option'); - - // @type {HTMLInputElement} This represents fit to page input element. - this.fitToPageCheckbox_ = $('fit-to-page'); - - // @type {boolean} True if fit to page option applies for the selected - // user options. Fit to Page options applies only if we are previewing - // a PDF and the current destination printer is actually a physcial - // printer. - this.fitToPageApplies_ = true; - - this.addEventListeners_(); - } - - cr.addSingletonGetter(FitToPageSettings); - - FitToPageSettings.prototype = { - /** - * Returns true if we need to fit the page contents to printable area. - * @return {boolean} true if Fit to page is checked. - */ - hasFitToPage: function() { - return previewModifiable || this.fitToPageCheckbox_.checked; - }, - - /** - * Updates |this.fitToPageApplies_| depending on the selected printer and - * preview data source type. - * @param {!string} printerName Selected printer name. - * @private - */ - resetState_: function(printerName) { - if (!previewModifiable) - isPrintReadyMetafileReady = false; - var printToPDF = printerName == PRINT_TO_PDF; - this.fitToPageApplies_ = !previewModifiable && !printToPDF; - }, - - /** - * Print scaling is disabled for preview source plugin. Uncheck the fit to - * page option. - */ - onPrintScalingDisabled: function() { - this.fitToPageCheckbox_.checked = false; - }, - - /** - * Adding listeners to fit to page control. - * @private - */ - addEventListeners_: function() { - this.fitToPageCheckbox_.onclick = - this.onFitToPageCheckboxClicked_.bind(this); - document.addEventListener(customEvents.PDF_LOADED, - this.onPDFLoaded_.bind(this)); - document.addEventListener(customEvents.PRINTER_SELECTION_CHANGED, - this.onPrinterSelectionChanged_.bind(this)); - }, - - /** - * Listener executing when a |customEvents.PRINTER_SELECTION_CHANGED| event - * occurs. - * @param {cr.Event} event The event that triggered this listener. - * @private - */ - onPrinterSelectionChanged_: function(event) { - this.resetState_(event.selectedPrinter); - this.updateVisibility_(); - }, - - /** - * Listener executing when the user selects or de-selects the fit to page - * option. - * @private - */ - onFitToPageCheckboxClicked_: function() { - requestPrintPreview(); - }, - - /** - * Listener executing when a |customEvents.PDF_LOADED| event occurs. - * @private - */ - onPDFLoaded_: function() { - this.updateVisibility_(); - }, - - /** - * Hides or shows |this.fitToPageOption_|. - * @private - */ - updateVisibility_: function() { - this.fitToPageOption_.style.display = - this.fitToPageApplies_ ? 'block' : 'none'; - } - }; - - return { - FitToPageSettings: FitToPageSettings - }; -}); diff --git a/chrome/browser/resources/print_preview/header_footer_settings.js b/chrome/browser/resources/print_preview/header_footer_settings.js deleted file mode 100644 index df74c67..0000000 --- a/chrome/browser/resources/print_preview/header_footer_settings.js +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Creates a HeaderFooterSettings object. This object encapsulates all - * settings and logic related to the headers and footers checkbox. - * @constructor - */ - function HeaderFooterSettings() { - this.headerFooterOption_ = $('header-footer-option'); - this.headerFooterCheckbox_ = $('header-footer'); - this.addEventListeners_(); - } - - cr.addSingletonGetter(HeaderFooterSettings); - - HeaderFooterSettings.prototype = { - /** - * The checkbox corresponding to the headers and footers option. - * @type {HTMLInputElement} - */ - get headerFooterCheckbox() { - return this.headerFooterCheckbox_; - }, - - /** - * Checks whether the Headers and Footers checkbox is checked or not. - * @return {boolean} true if Headers and Footers are checked. - */ - hasHeaderFooter: function() { - return previewModifiable && this.headerFooterCheckbox_.checked; - }, - - /** - * Sets the state of the headers footers checkbox. - * @param {boolean} checked True if the headers footers checkbox shoule be - * checked, false if not. - */ - setChecked: function(checked) { - this.headerFooterCheckbox_.checked = checked; - }, - - /** - * Checks the printable area and updates the visibility of header footer - * option based on the selected margins. - * @param {{contentWidth: number, contentHeight: number, marginLeft: number, - * marginRight: number, marginTop: number, marginBottom: number, - * printableAreaX: number, printableAreaY: number, - * printableAreaWidth: number, printableAreaHeight: number}} - * pageLayout Specifies default page layout details in points. - * @param {number} marginsType Specifies the selected margins type value. - */ - checkAndHideHeaderFooterOption: function(pageLayout, marginsType) { - var headerFooterApplies = true; - if (marginsType == - print_preview.MarginSettings.MARGINS_VALUE_NO_MARGINS || - !previewModifiable) { - headerFooterApplies = false; - } else if (marginsType != - print_preview.MarginSettings.MARGINS_VALUE_MINIMUM) { - if (cr.isLinux || cr.isChromeOS) { - headerFooterApplies = pageLayout.marginTop > 0 || - pageLayout.marginBottom > 0; - } else { - var pageHeight = pageLayout.marginTop + pageLayout.marginBottom + - pageLayout.contentHeight; - headerFooterApplies = - (pageLayout.marginTop > pageLayout.printableAreaY) || - (pageLayout.marginBottom > - (pageHeight - pageLayout.printableAreaY - - pageLayout.printableAreaHeight)); - } - } - this.setVisible_(headerFooterApplies); - var headerFooterEvent = new cr.Event( - customEvents.HEADER_FOOTER_VISIBILITY_CHANGED); - headerFooterEvent.headerFooterApplies = headerFooterApplies; - document.dispatchEvent(headerFooterEvent); - }, - - /** - * Adding listeners to header footer related controls. - * @private - */ - addEventListeners_: function() { - this.headerFooterCheckbox_.onclick = - this.onHeaderFooterChanged_.bind(this); - document.addEventListener(customEvents.PDF_LOADED, - this.onPDFLoaded_.bind(this)); - }, - - /** - * Listener executing when the user selects or de-selects the headers - * and footers option. - * @private - */ - onHeaderFooterChanged_: function() { - requestPrintPreview(); - }, - - /** - * Listener executing when a |customEvents.PDF_LOADED| event occurs. - * @private - */ - onPDFLoaded_: function() { - if (!previewModifiable) - this.setVisible_(false); - }, - - /** - * Hides or shows |this.headerFooterOption_|. - * @param {boolean} visible True if |this.headerFooterOption_| should be - * shown. - * @private - */ - setVisible_: function(visible) { - this.headerFooterOption_.style.display = visible ? 'block' : 'none'; - }, - }; - - return { - HeaderFooterSettings: HeaderFooterSettings - }; -}); diff --git a/chrome/browser/resources/print_preview/layout_settings.html b/chrome/browser/resources/print_preview/layout_settings.html deleted file mode 100644 index 8bdf80f..0000000 --- a/chrome/browser/resources/print_preview/layout_settings.html +++ /dev/null @@ -1,13 +0,0 @@ -<div id="layout-option" class="two-column visible"> - <h1 i18n-content="layoutLabel"></h1> - <div class="right-column"> - <div class="radio"><label> - <input id="portrait" type="radio" name="layout" checked> - <span i18n-content="optionPortrait"></span> - </label></div> - <div class="radio"><label> - <input id="landscape" type="radio" name="layout"> - <span i18n-content="optionLandscape"></span> - </label></div> - </div> -</div> diff --git a/chrome/browser/resources/print_preview/layout_settings.js b/chrome/browser/resources/print_preview/layout_settings.js deleted file mode 100644 index e6cc097..0000000 --- a/chrome/browser/resources/print_preview/layout_settings.js +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Creates a LayoutSettings object. This object encapsulates all settings and - * logic related to layout mode (portrait/landscape). - * @constructor - */ - function LayoutSettings() { - this.layoutOption_ = $('layout-option'); - this.portraitRadioButton_ = $('portrait'); - this.landscapeRadioButton_ = $('landscape'); - this.wasLandscape_ = false; - this.updateState(); - this.addEventListeners_(); - } - - cr.addSingletonGetter(LayoutSettings); - - LayoutSettings.prototype = { - /** - * The radio button corresponding to the portrait option. - * @type {HTMLInputElement} - */ - get portraitRadioButton() { - return this.portraitRadioButton_; - }, - - /** - * The radio button corresponding to the landscape option. - * @type {HTMLInputElement} - */ - get landscapeRadioButton() { - return this.landscapeRadioButton_; - }, - - /** - * @return {boolean} true if |this.landscapeRadioButton_| is checked, false - * if not. - */ - isLandscape: function() { - return this.landscapeRadioButton_.checked; - }, - - /** - * @return {boolean} true if the chosen layout mode has changed since last - * time the state was updated. - */ - hasChanged_: function() { - return this.isLandscape() != this.wasLandscape_; - }, - - /** - * Saves the currently selected layout mode. Used in |this.hasChanged_|. - */ - updateState: function() { - this.wasLandscape_ = this.isLandscape(); - }, - - /** - * Adding listeners to all layout related controls. The listeners take care - * of altering their behavior depending on |hasPendingPreviewRequest|. - * @private - */ - addEventListeners_: function() { - this.landscapeRadioButton_.onclick = this.onLayoutButtonClick_.bind(this); - this.portraitRadioButton_.onclick = this.onLayoutButtonClick_.bind(this); - document.addEventListener(customEvents.PDF_LOADED, - this.onPDFLoaded_.bind(this)); - document.addEventListener(customEvents.PRINTER_CAPABILITIES_UPDATED, - this.onPrinterCapabilitiesUpdated_.bind(this)); - }, - - /** - * Executes when a |customEvents.PRINTER_CAPABILITIES_UPDATED| event occurs. - * @private - */ - onPrinterCapabilitiesUpdated_: function(e) { - if (e.printerCapabilities.disableLandscapeOption) - this.fadeInOut_(e.printerCapabilities.disableLandscapeOption); - }, - - /** - * Listener executing when |this.landscapeRadioButton_| or - * |this.portraitRadioButton_| is clicked. - * @private - */ - onLayoutButtonClick_: function() { - // If the chosen layout is same as before, nothing needs to be done. - if (this.hasChanged_()) - setDefaultValuesAndRegeneratePreview(true); - }, - - /** - * Listener executing when a |customEvents.PDF_LOADED| event occurs. - * @private - */ - onPDFLoaded_: function() { - this.fadeInOut_(!previewModifiable || hasPageSizeStyle); - }, - - /** - * @param {boolean} fadeOut True if |this.layoutOption_| should be faded - * out, false if it should be faded in. - * @private - */ - fadeInOut_: function(fadeOut) { - fadeOut ? fadeOutOption(this.layoutOption_) : - fadeInOption(this.layoutOption_); - } - }; - - return { - LayoutSettings: LayoutSettings - }; -}); diff --git a/chrome/browser/resources/print_preview/margin_settings.js b/chrome/browser/resources/print_preview/margin_settings.js deleted file mode 100644 index 0ffca9f..0000000 --- a/chrome/browser/resources/print_preview/margin_settings.js +++ /dev/null @@ -1,705 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Creates a Margins object that holds four margin values. The units in which - * the values are expressed can be any numeric value. - * @constructor - * @param {number} left The left margin. - * @param {number} top The top margin. - * @param {number} right The right margin. - * @param {number} bottom The bottom margin. - */ - function Margins(left, top, right, bottom) { - this[MarginSettings.LEFT_GROUP] = left; - this[MarginSettings.TOP_GROUP] = top; - this[MarginSettings.RIGHT_GROUP] = right; - this[MarginSettings.BOTTOM_GROUP] = bottom; - } - - /** - * Rounds |value| keeping |precision| decimal numbers. Example: 32.76643 - * becomes 32.77. - * @param {number} value The number to round. - * @param {number} precision The desired precision. - * @return {number} The rounded number. - */ - Margins.roundToPrecision = function(value, precision) { - return Math.round(value * Math.pow(10, precision)) / - Math.pow(10, precision); - }; - - Margins.prototype = { - /** - * Checks if |rhs| is equal to |this|. - * @param {Margins} rhs The Margins object to compare against. - * @return {boolean} true if they are equal. - */ - isEqual: function(rhs) { - if (!rhs) - return false; - return this[MarginSettings.TOP_GROUP] === rhs[MarginSettings.TOP_GROUP] && - this[MarginSettings.LEFT_GROUP] === rhs[MarginSettings.LEFT_GROUP] && - this[MarginSettings.RIGHT_GROUP] === - rhs[MarginSettings.RIGHT_GROUP] && - this[MarginSettings.BOTTOM_GROUP] === - rhs[MarginSettings.BOTTOM_GROUP]; - }, - - clone: function() { - return new Margins(this[MarginSettings.LEFT_GROUP], - this[MarginSettings.TOP_GROUP], - this[MarginSettings.RIGHT_GROUP], - this[MarginSettings.BOTTOM_GROUP]); - }, - - /** - * Helper method returning an array of the string indices used for accessing - * all margins. - * @return {array} An array of string indices. - * @private - */ - indicesAsArray_: function() { - return [MarginSettings.LEFT_GROUP, MarginSettings.TOP_GROUP, - MarginSettings.RIGHT_GROUP, MarginSettings.BOTTOM_GROUP]; - }, - - /** - * Rounds |this| based on the precision used when displaying the margins in - * the user's prefered units. This is done by converting from points to - * those units (mm/inches) and back to points. - */ - roundToLocaleUnits: function() { - var indicesAsArray = this.indicesAsArray_(); - for (var i = 0; i < indicesAsArray.length; i++) { - this[indicesAsArray[i]] = - print_preview.convertPointsToLocaleUnitsAndBack( - this[indicesAsArray[i]]); - } - } - }; - - /** - * @constructor - * Class describing the layout of the page. - */ - function PageLayout(width, height, left, top, right, bottom) { - this.contentWidth_ = width; - this.contentHeight_ = height; - this.margins_ = new Margins(left, top, right, bottom); - this.margins_.roundToLocaleUnits(); - } - - PageLayout.prototype = { - /** - * @type {number} The width of the page. - */ - get pageWidth() { - return this.margins_.left + this.margins_.right + this.contentWidth_; - }, - - /** - * @type {number} The height of the page. - */ - get pageHeight() { - return this.margins_.top + this.margins_.bottom + this.contentHeight_; - } - }; - - /** - * Creates a MarginSettings object. This object encapsulates all settings and - * logic related to the margins mode. - * @constructor - */ - function MarginSettings() { - this.marginsOption_ = $('margins-option'); - this.marginList_ = $('margin-list'); - this.marginsUI_ = null; - - // Holds the custom margin values in points (if set). - this.customMargins_ = null; - // Holds the previous custom margin values in points. - this.previousCustomMargins_ = null; - // Holds the width of the page in points. - this.pageWidth_ = -1; - // Holds the height of the page in points. - this.pageHeight_ = -1; - // @type {boolean} True if the margins UI should be diplayed when the next - // |customEvents.PDF_LOADED| event occurs. - this.forceMarginsUIOnPDFLoad_ = false; - // Holds the currently updated default page layout values. - this.currentDefaultPageLayout = null; - - // True if the margins UI should be shown regardless of mouse position. - this.forceDisplayingMarginLines_ = true; - - // @type {EventTracker} Used to keep track of certain event listeners. - this.eventTracker_ = new EventTracker(); - - this.addEventListeners_(); - } - - // Number of points per inch. - MarginSettings.POINTS_PER_INCH = 72; - // Minimum allowed distance in points between top-bottom, left-right margins. - MarginSettings.MINIMUM_MARGINS_DISTANCE = 36; - // Margin list values. Matches enum MarginType in - // printing/print_job_constants.h. - MarginSettings.MARGINS_VALUE_DEFAULT = 0; - MarginSettings.MARGINS_VALUE_NO_MARGINS = 1; - MarginSettings.MARGINS_VALUE_MINIMUM = 2; - MarginSettings.MARGINS_VALUE_CUSTOM = 3; - // Default Margins option index. - MarginSettings.OPTION_INDEX_DEFAULT = 0; - // Group name corresponding to the top margin. - MarginSettings.TOP_GROUP = 'top'; - // Group name corresponding to the left margin. - MarginSettings.LEFT_GROUP = 'left'; - // Group name corresponding to the right margin. - MarginSettings.RIGHT_GROUP = 'right'; - // Group name corresponding to the bottom margin. - MarginSettings.BOTTOM_GROUP = 'bottom'; - - /** - * Extracts the number formatting and measurement system for the current - * locale. - * @param {string} numberFormat Is the formatted version of a sample number - * sent from the backend. - * @param {number} measurementSystem 0 for SI (aka metric system), 1 for the - * system used in the US. Note: Mathces UMeasurementSystem enum in - * third_party/icu/public/i18n/unicode/ulocdata.h. - */ - MarginSettings.setNumberFormatAndMeasurementSystem = function( - numberFormat, measurementSystem) { - var symbols = parseNumberFormat(numberFormat); - MarginSettings.thousandsPoint = symbols[0]; - MarginSettings.decimalPoint = symbols[1]; - MarginSettings.useMetricSystem = measurementSystem == 0; - }; - - cr.addSingletonGetter(MarginSettings); - - MarginSettings.prototype = { - /** - * Returns a dictionary containing the four custom margin values. - * @return {{marginLeft: number, marginTop: number, marginRight: number, - * marginBottom: number}} The dictionary. - */ - get customMargins() { - var margins = {}; - margins.marginLeft = this.customMargins_.left; - margins.marginTop = this.customMargins_.top; - margins.marginRight = this.customMargins_.right; - margins.marginBottom = this.customMargins_.bottom; - return margins; - }, - - /** - * Sets |this.customMargins_| according to |margins|. - * @param {{marginLeft: number, marginTop: number, marginRight: number, - * marginBottom: number}} margins An object holding the four margin - * values. - */ - set customMargins(margins) { - this.customMargins_.left = margins.marginLeft; - this.customMargins_.top = margins.marginTop; - this.customMargins_.right = margins.marginRight; - this.customMargins_.bottom = margins.marginBottom; - }, - - /** - * @return {number} The value of the selected margin option. - */ - get selectedMarginsValue() { - var val = this.marginList_.options[this.marginList_.selectedIndex].value; - return parseInt(val, 10); - }, - - /** - * Sets the current margin selection to |lastUsedMarginType|. - * @param {number} lastUsedMarginType An integer value identifying a margin - * type according to MarginType enum in printing/print_job_constants.h. - * @param {Object} lastUsedCustomMargins The last used custom margins. If - * custom margins have not been used before - * |margin{Top|Bottom|Left|Right}| attributes are missing. - */ - setLastUsedMargins: function(lastUsedMarginsSettings) { - var lastUsedMarginsType = lastUsedMarginsSettings['marginsType']; - this.forceMarginsUIOnPDFLoad_ = - lastUsedMarginsType == MarginSettings.MARGINS_VALUE_CUSTOM; - this.marginList_.selectedIndex = - this.getMarginOptionIndexByValue_(lastUsedMarginsType); - if (lastUsedMarginsSettings.hasOwnProperty('marginTop') && - lastUsedMarginsSettings.hasOwnProperty('marginBottom') && - lastUsedMarginsSettings.hasOwnProperty('marginRight') && - lastUsedMarginsSettings.hasOwnProperty('marginLeft')) { - this.customMargins_ = new Margins(-1, -1, -1 , -1); - this.customMargins = lastUsedMarginsSettings; - } - }, - - /** - * @return {number} The total width of the plugin in points. - */ - get totalWidthInPoints() { - var pageInformation = previewArea.pageLocationNormalized; - return this.pageWidth_ / pageInformation.width; - }, - - /** - * @return {number} The total height of the plugin in points. - */ - get totalHeightInPoints() { - var pageInformation = previewArea.pageLocationNormalized; - return this.pageHeight_ / pageInformation.height; - }, - - /** - * Maps margin type values to indices within |this.marginList_|. - * @param {number} marginTypeValue An integer value identifying a margin - * type according to MarginType enum in printing/print_job_constants.h. - * @return {number} The index within |this.marginList_| that corrsponds to - * |marginTypeValue|. - * @private - */ - getMarginOptionIndexByValue_: function(marginTypeValue) { - var options = this.marginList_.options; - for (var i = 0; i < options.length; i++) { - if (options[i].getAttribute('value') == marginTypeValue) - return i; - } - return MarginSettings.OPTION_INDEX_DEFAULT; - }, - - /** - * @return {boolean} True if default margins are selected. - */ - isDefaultMarginsSelected: function() { - return this.selectedMarginsValue == MarginSettings.MARGINS_VALUE_DEFAULT; - }, - - /** - * @return {boolean} True if no margins are selected. - */ - isNoMarginsSelected: function() { - return this.selectedMarginsValue == - MarginSettings.MARGINS_VALUE_NO_MARGINS; - }, - - /** - * @return {boolean} True if custom margins are selected. - */ - isCustomMarginsSelected: function() { - return this.selectedMarginsValue == MarginSettings.MARGINS_VALUE_CUSTOM; - }, - - /** - * @return {boolean} True if minimum margins are selected. - */ - isMinimumMarginsSelected: function() { - return this.selectedMarginsValue == MarginSettings.MARGINS_VALUE_MINIMUM; - }, - - /** - * If the custom margin values have changed then request a new preview based - * on the newly set margins. - * @private - */ - requestPreviewIfNeeded_: function() { - if (!this.areMarginSettingsValid()) - return; - - if (this.customMargins_.isEqual(this.previousCustomMargins_)) - return; - - this.previousCustomMargins_ = this.customMargins_.clone(); - setDefaultValuesAndRegeneratePreview(false); - }, - - /** - * Listener executed when the mouse is over the sidebar. If the custom - * margin lines are displayed, then, it fades them out. - * @private - */ - onSidebarMouseOver_: function(e) { - $('mainview').onmouseover = this.onMainviewMouseOver_.bind(this); - $('navbar-container').onmouseover = null; - if (!this.forceDisplayingMarginLines_) - this.marginsUI.hide(false); - }, - - /** - * Listener executed when the mouse is over the main view. If the custom - * margin lines are hidden, then, it fades them in. - * @private - */ - onMainviewMouseOver_: function() { - $('mainview').onmouseover = null; - $('navbar-container').onmouseover = this.onSidebarMouseOver_.bind(this); - this.forceDisplayingMarginLines_ = false; - this.marginsUI.show(); - }, - - /** - * Adds listeners to all margin related controls. - * @private - */ - addEventListeners_: function() { - this.marginList_.onchange = this.onMarginsChanged_.bind(this); - document.addEventListener(customEvents.PDF_LOADED, - this.onPDFLoaded_.bind(this)); - document.addEventListener(customEvents.PDF_GENERATION_ERROR, - this.onPDFGenerationError_.bind(this)); - }, - - /** - * Executes when a |customEvents.PDF_GENERATION_ERROR| event occurs. - * @private - */ - onPDFGenerationError_: function() { - if (this.isCustomMarginsSelected()) { - this.removeCustomMarginEventListeners_(); - this.marginsUI.hide(true); - } - }, - - /** - * Executes whenever a |customEvents.MARGIN_LINE_DRAG| occurs. - * @param {cr.Event} e The event that triggered this listener. - */ - onMarginsLineDrag_: function(e) { - var dragDeltaInPoints = this.convertDragDeltaToPoints_(e.dragDelta); - this.marginsUI.lastClickedMarginsUIPair.updateWhileDragging( - dragDeltaInPoints, e.destinationPoint); - }, - - /** - * @param {number} dragDelta The difference in pixels between the original - * and current postion of the last clicked margin line. - * @return {number} The difference in points. - * @private - */ - convertDragDeltaToPoints_: function(dragDelta) { - if (this.marginsUI.lastClickedMarginsUIPair.isTop_() || - this.marginsUI.lastClickedMarginsUIPair.isBottom_()) { - return dragDelta * this.totalHeightInPoints; - } else { - return dragDelta * this.totalWidthInPoints; - } - }, - - /** - * @return {boolean} True if the margin settings are valid. - */ - areMarginSettingsValid: function() { - if (!this.isCustomMarginsSelected() || !this.marginsUI_) - return true; - - var pairs = this.marginsUI.pairsAsList; - return pairs.every(function(pair) { return pair.box_.isValid; }); - }, - - /** - * Calculates the maximum allowable value of the selected margin text for - * every margin. - * @return {array} The maximum allowable value in points in order top, left, - * right, bottom. - * @private - */ - getMarginValueLimits_: function() { - var marginValueLimits = []; - marginValueLimits[0] = this.pageHeight_ - this.customMargins_.bottom - - MarginSettings.MINIMUM_MARGINS_DISTANCE; - marginValueLimits[1] = this.pageWidth_ - this.customMargins_.right - - MarginSettings.MINIMUM_MARGINS_DISTANCE; - marginValueLimits[2] = this.pageWidth_ - this.customMargins_.left - - MarginSettings.MINIMUM_MARGINS_DISTANCE; - marginValueLimits[3] = this.pageHeight_ - this.customMargins_.top - - MarginSettings.MINIMUM_MARGINS_DISTANCE; - - for (var i = 0; i < marginValueLimits.length; i++) { - marginValueLimits[i] = Math.max(marginValueLimits[i], 0); - marginValueLimits[i] = print_preview.convertPointsToLocaleUnitsAndBack( - marginValueLimits[i]); - } - return marginValueLimits; - }, - - /** - * @return {array} The margin value limits positions normalized to the total - * width and height of the plugin and with respect to the top left - * corner of the plugin. - */ - getMarginValueLimitsInPercent_: function() { - var pageInformation = previewArea.pageLocationNormalized; - var totalWidthInPoints = this.pageWidth_ / pageInformation.width; - var totalHeightInPoints = this.pageHeight_ / pageInformation.height; - var marginValueLimits = this.getMarginValueLimits_(); - var marginValueLimitsInPercent = []; - marginValueLimitsInPercent[0] = pageInformation.y + marginValueLimits[0] / - totalHeightInPoints; - marginValueLimitsInPercent[1] = pageInformation.x + marginValueLimits[1] / - totalWidthInPoints; - marginValueLimitsInPercent[2] = pageInformation.x + - pageInformation.width - marginValueLimits[2] / totalWidthInPoints; - marginValueLimitsInPercent[3] = pageInformation.y + - pageInformation.height - marginValueLimits[3] / totalHeightInPoints; - return marginValueLimitsInPercent; - }, - - /** - * When the user stops typing in the margin text box a new print preview is - * requested, only if - * 1) The input is compeletely valid (it can be parsed in its entirety). - * 2) The newly selected margins differ from the previously selected. - * @param {cr.Event} event The change event holding information about what - * changed. - * @private - */ - onMarginTextValueMayHaveChanged_: function(event) { - var marginBox = event.target; - var marginBoxValue = - print_preview.convertLocaleUnitsToPoints(marginBox.margin); - this.customMargins_[marginBox.marginGroup] = marginBoxValue; - this.requestPreviewIfNeeded_(); - }, - - /** - * @type {print_preview.MarginsUI} The object holding the UI for specifying - * custom margins. - */ - get marginsUI() { - if (!this.marginsUI_) { - this.marginsUI_ = new print_preview.MarginsUI(); - $('mainview').appendChild(this.marginsUI_); - this.marginsUI_.addObserver( - this.onMarginTextValueMayHaveChanged_.bind(this)); - } - return this.marginsUI_; - }, - - /** - * Adds listeners when the custom margins option is selected. - * @private - */ - addCustomMarginEventListeners_: function() { - $('mainview').onmouseover = this.onMainviewMouseOver_.bind(this); - $('navbar-container').onmouseover = this.onSidebarMouseOver_.bind(this); - this.eventTracker_.add(this.marginsUI, - customEvents.MARGIN_LINE_DRAG, - this.onMarginsLineDrag_.bind(this), - false); - this.eventTracker_.add(document, customEvents.MARGIN_TEXTBOX_FOCUSED, - this.onMarginTextboxFocused_.bind(this), false); - }, - - /** - * Removes the event listeners associated with the custom margins option. - * @private - */ - removeCustomMarginEventListeners_: function() { - if (!this.marginsUI_) - return; - $('mainview').onmouseover = null; - $('navbar-container').onmouseover = null; - this.eventTracker_.remove(this.marginsUI, customEvents.MARGIN_LINE_DRAG); - this.eventTracker_.remove(document, customEvents.MARGIN_TEXTBOX_FOCUSED); - this.marginsUI.hide(true); - }, - - /** - * Updates |this.marginsUI| depending on the specified margins and the - * position of the page within the plugin. - * @private - */ - drawCustomMarginsUI_: function() { - // TODO(dpapad): find out why passing |!this.areMarginsSettingsValid()| - // directly produces the opposite value even though - // |this.getMarginsRectangleInPercent_()| and - // |this.getMarginValueLimits_()| have no side effects. - previewArea.update(); - var keepDisplayedValue = !this.areMarginSettingsValid(); - this.marginsUI.update(this.getMarginsRectangleInPercent_(), - this.customMargins_, - this.getMarginValueLimits_(), - keepDisplayedValue, - this.getMarginValueLimitsInPercent_()); - this.marginsUI.draw(); - }, - - /** - * Called when there is change in the preview position or size. - */ - onPreviewPositionChanged: function() { - if (!previewArea.pdfPlugin) - return; - if (this.isCustomMarginsSelected() && previewArea.pdfLoaded && - pageSettings.totalPageCount != undefined) { - this.updatePageData_(); - this.drawCustomMarginsUI_(); - } - }, - - /** - * Executes when a margin textbox is focused. Used for improved - * accessibility. - * @private - */ - onMarginTextboxFocused_: function() { - this.forceDisplayingMarginLines_ = true; - this.marginsUI.show(); - }, - - /** - * Executes when user selects a different margin option, ie, - * |this.marginList_.selectedIndex| is changed. - * @private - */ - onMarginsChanged_: function() { - if (this.isDefaultMarginsSelected() || this.isMinimumMarginsSelected() || - this.isNoMarginsSelected()) - this.onDefaultMinimumNoMarginsSelected_(); - else if (this.isCustomMarginsSelected()) - this.onCustomMarginsSelected_(); - }, - - /** - * Executes when the default or minimum or no margins option is selected. - * @private - */ - onDefaultMinimumNoMarginsSelected_: function() { - this.removeCustomMarginEventListeners_(); - this.forceDisplayingMarginLines_ = true; - this.previousCustomMargins_ = null; - setDefaultValuesAndRegeneratePreview(false); - }, - - /** - * Executes when the custom margins option is selected. - * @private - */ - onCustomMarginsSelected_: function() { - if (!previewArea.pdfPlugin) { - this.forceMarginsUIOnPDFLoad_ = true; - return; - } - var customMarginsNotSpecified = !this.customMargins_; - this.updatePageData_(); - - if (customMarginsNotSpecified) { - this.previousCustomMargins_ = this.customMargins_.clone(); - this.drawCustomMarginsUI_(); - this.addCustomMarginEventListeners_(); - this.marginsUI.show(); - } else { - this.forceMarginsUIOnPDFLoad_ = true; - this.requestPreviewIfNeeded_(); - } - }, - - /** - * Calculates the coordinates of the four margin lines. These are the - * coordinates where the margin lines should be displayed. The coordinates - * are expressed in terms of percentages with respect to the total width - * and height of the plugin. - * @return {print_preview.Rect} A rectnangle that describes the position of - * the four margin lines. - * @private - */ - getMarginsRectangleInPercent_: function() { - var pageLocation = previewArea.pageLocationNormalized; - var marginsInPercent = this.getMarginsInPercent_(); - var leftX = pageLocation.x + marginsInPercent.left; - var topY = pageLocation.y + marginsInPercent.top; - var contentWidth = pageLocation.width - (marginsInPercent.left + - marginsInPercent.right); - var contentHeight = pageLocation.height - (marginsInPercent.top + - marginsInPercent.bottom); - return new print_preview.Rect( - leftX, topY, contentWidth, contentHeight); - }, - - /** - * @return {print_preview.Margins} The currently selected margin values - * normalized to the total width and height of the plugin. - * @private - */ - getMarginsInPercent_: function() { - return this.convertMarginsInPointsToPercent(this.customMargins_); - }, - - /** - * Converts |marginsToConvert| to points and normalizes it to the height and - * width of the plugin. - * @return {print_preview.Margins} The margins in percent. - * @private - */ - convertMarginsInPointsToPercent: function(marginsToConvert) { - var pageInformation = previewArea.pageLocationNormalized; - var totalWidthInPoints = this.pageWidth_ / pageInformation.width; - var totalHeightInPoints = this.pageHeight_ / pageInformation.height; - var marginsInPercent = new Margins( - marginsToConvert.left / totalWidthInPoints, - marginsToConvert.top / totalHeightInPoints, - marginsToConvert.right / totalWidthInPoints, - marginsToConvert.bottom / totalHeightInPoints); - return marginsInPercent; - }, - - /** - * If custom margins is the currently selected option then change to the - * default margins option. - * @private - */ - resetMarginsIfNeeded: function() { - if (this.isCustomMarginsSelected()) { - this.marginList_.options[ - MarginSettings.OPTION_INDEX_DEFAULT].selected = true; - this.removeCustomMarginEventListeners_(); - this.forceDisplayingMarginLines_ = true; - this.customMargins_ = null; - this.previousCustomMargins_ = null; - } - }, - - /** - * Executes when a |customEvents.PDF_LOADED| event occurs. - * @private - */ - onPDFLoaded_: function() { - if (!previewModifiable) { - fadeOutOption(this.marginsOption_); - return; - } - - if (this.forceMarginsUIOnPDFLoad_) { - this.updatePageData_(); - this.drawCustomMarginsUI_(); - this.addCustomMarginEventListeners_(); - this.marginsUI.show(); - this.forceMarginsUIOnPDFLoad_ = false; - } - }, - - /** - * Updates |this.customMargins_|, |this.pageWidth_|, |this.pageHeight_|. - * @private - */ - updatePageData_: function() { - if (!this.customMargins_) - this.customMargins_ = this.currentDefaultPageLayout.margins_.clone(); - - this.pageWidth_ = this.currentDefaultPageLayout.pageWidth; - this.pageHeight_ = this.currentDefaultPageLayout.pageHeight; - } - }; - - return { - MarginSettings: MarginSettings, - PageLayout: PageLayout - }; -}); diff --git a/chrome/browser/resources/print_preview/margin_textbox.js b/chrome/browser/resources/print_preview/margin_textbox.js deleted file mode 100644 index f0496f5..0000000 --- a/chrome/browser/resources/print_preview/margin_textbox.js +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - function MarginTextbox(groupName) { - var box = document.createElement('input'); - box.__proto__ = MarginTextbox.prototype; - box.setAttribute('type', 'text'); - box.className = MarginTextbox.CSS_CLASS_MARGIN_TEXTBOX; - box.value = '0'; - box.setAttribute('aria-label', localStrings.getString(groupName)); - - // @type {string} Specifies which margin this line refers to. - box.marginGroup = groupName; - // @type {boolean} True if the displayed value is valid. - box.isValid = true; - // @type {number} Timer used to detect when the user stops typing. - box.timerId_ = null; - // @type {number} The last valid value in points. - box.lastValidValueInPoints = 0; - // @type {number} The upper allowed limit for the corresponding margin. - box.valueLimit = null; - - box.addEventListeners_(); - return box; - } - - MarginTextbox.CSS_CLASS_MARGIN_TEXTBOX = 'margin-box'; - // Keycode for the "Escape" key. - MarginTextbox.ESCAPE_KEYCODE = 27; - - MarginTextbox.prototype = { - __proto__: HTMLInputElement.prototype, - - /** - * @type {number} The margin value currently in the textbox. - */ - get margin() { - return print_preview.extractMarginValue(this.value); - }, - - /** - * Sets the contents of the textbox. - * @param {number} newValueInPoints The value to be displayed in points. - * @private - */ - setValue_: function(newValueInPoints) { - this.value = - print_preview.convertPointsToLocaleUnitsText(newValueInPoints); - }, - - /** - * Updates the state of |this|. - * @param {number} value The margin value in points. - * @param {number} valueLimit The upper allowed value for the margin. - * @param {boolean} keepDisplayedValue True if the currently displayed value - * should not be updated. - */ - update: function(value, valueLimit, keepDisplayedValue) { - this.lastValidValueInPoints = value; - if (!keepDisplayedValue) - this.setValue_(this.lastValidValueInPoints); - - this.valueLimit = valueLimit; - this.validate(); - }, - - /** - * Updates |this| while dragging is in progress. - * @param {number} dragDeltaInPoints The difference in points between the - * margin value before dragging started and now. - */ - updateWhileDragging: function(dragDeltaInPoints) { - var validity = this.validateDelta(dragDeltaInPoints); - - if (validity == print_preview.marginValidationStates.WITHIN_RANGE) - this.setValue_(this.lastValidValueInPoints + dragDeltaInPoints); - else if (validity == print_preview.marginValidationStates.TOO_SMALL) - this.setValue_(0); - else if (validity == print_preview.marginValidationStates.TOO_BIG) - this.setValue_(this.valueLimit); - - this.validate(); - this.updateColor(); - }, - - /** - * @param {number} dragDeltaInPoints The difference in points between the - * margin value before dragging started and now. - * @return {number} An appropriate value from enum |marginValidationStates|. - */ - validateDelta: function(dragDeltaInPoints) { - var newValue = this.lastValidValueInPoints + dragDeltaInPoints; - return print_preview.validateMarginValue(newValue, this.valueLimit); - }, - - /** - * Updates |this.isValid|. - */ - validate: function() { - this.isValid = - print_preview.validateMarginText(this.value, this.valueLimit) == - print_preview.marginValidationStates.WITHIN_RANGE; - }, - - /** - * Updates the background color depending on |isValid| by adding/removing - * the appropriate CSS class. - * @param {boolean} isValid True if the margin is valid. - */ - updateColor: function() { - this.isValid ? this.classList.remove('invalid') : - this.classList.add('invalid'); - }, - - /** - * Draws this textbox. - */ - draw: function() { - this.updateColor(); - }, - - /** - * Adds event listeners for various events. - * @private - */ - addEventListeners_: function() { - this.oninput = this.resetTimer_.bind(this); - this.onblur = this.onBlur_.bind(this); - this.onkeypress = this.onKeyPressed_.bind(this); - this.onkeyup = this.onKeyUp_.bind(this); - this.onfocus = function() { - cr.dispatchSimpleEvent(document, customEvents.MARGIN_TEXTBOX_FOCUSED); - }; - }, - - /** - * Executes whenever a blur event occurs. - * @private - */ - onBlur_: function() { - clearTimeout(this.timerId_); - this.validate(); - if (!this.isValid) { - this.setValue_(this.lastValidValueInPoints); - this.validate(); - } - - this.updateColor(); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_PRINT_BUTTON); - cr.dispatchSimpleEvent(this, customEvents.MARGINS_MAY_HAVE_CHANGED); - }, - - /** - * Executes whenever a keypressed event occurs. Note: Only the "Enter" key - * event is handled. The "Escape" key does not result in such event, - * therefore it is handled by |this.onKeyUp_|. - * @param {KeyboardEvent} e The event that triggered this listener. - * @private - */ - onKeyPressed_: function(e) { - if (e.keyIdentifier == 'Enter') - this.blur(); - }, - - /** - * Executes whenever a keyup event occurs. Note: Only the "Escape" - * key event is handled. - * @param {KeyboardEvent} e The event that triggered this listener. - * @private - */ - onKeyUp_: function(e) { - if (e.keyCode == MarginTextbox.ESCAPE_KEYCODE) { - this.setValue_(this.lastValidValueInPoints); - this.validate(); - this.updateColor(); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_PRINT_BUTTON); - } - }, - - /** - * Resetting the timer used to detect when the user stops typing in order - * to update the print preview. - * @private - */ - resetTimer_: function() { - clearTimeout(this.timerId_); - this.timerId_ = window.setTimeout( - this.onTextValueMayHaveChanged.bind(this), 1000); - }, - - /** - * Executes whenever the user stops typing or when a drag session associated - * with |this| ends. - */ - onTextValueMayHaveChanged: function() { - this.validate(); - this.updateColor(); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_PRINT_BUTTON); - - if (!this.isValid) - return; - cr.dispatchSimpleEvent(this, customEvents.MARGINS_MAY_HAVE_CHANGED); - } - - }; - - return { - MarginTextbox: MarginTextbox - }; -}); diff --git a/chrome/browser/resources/print_preview/margin_utils.js b/chrome/browser/resources/print_preview/margin_utils.js deleted file mode 100644 index a93ef0e..0000000 --- a/chrome/browser/resources/print_preview/margin_utils.js +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Checks if |text| has a valid margin value format. A valid format is - * parsable as a number and is greater than zero. - * Example: "1.00", "1", ".5", "1.1" are valid values. - * Example: "1.4dsf", "-1" are invalid. - * Note: The inch symbol (") at the end of |text| is allowed. - * - * @param {string} text The text to check. - * @return {number} The margin value represented by |text| or null if |text| - * does not represent a valid number. - */ - function extractMarginValue(text) { - // Removing whitespace anywhere in the string. - text = text.replace(/\s*/g, ''); - if (text.length == 0) - return -1; - var validationRegex = getValidationRegExp(); - if (validationRegex.test(text)) { - // Replacing decimal point with the dot symbol in order to use - // parseFloat() properly. - var replacementRegex = new RegExp('\\' + - print_preview.MarginSettings.decimalPoint + '{1}'); - text = text.replace(replacementRegex, '.'); - return parseFloat(text); - } - return -1; - } - - /** - * @return {RegExp} A regular expression for validating the input of the user. - * It takes into account the user's locale. - */ - function getValidationRegExp() { - var regex = new RegExp('(^\\d+)(\\' + - print_preview.MarginSettings.thousandsPoint + '\\d{3})*(\\' + - print_preview.MarginSettings.decimalPoint + '\\d+)*' + - (!print_preview.MarginSettings.useMetricSystem ? '"?' : '(mm)?') + '$'); - return regex; - } - - var marginValidationStates = { - TOO_SMALL: 0, - WITHIN_RANGE: 1, - TOO_BIG: 2, - NOT_A_NUMBER: 3 - }; - - /** - * Checks whether |value| is within range [0, limit]. - * @return {number} An appropriate value from enum |marginValidationStates|. - */ - function validateMarginValue(value, limit) { - if (value <= limit && value >= 0) - return marginValidationStates.WITHIN_RANGE; - else if (value < 0) - return marginValidationStates.TOO_SMALL; - else - return marginValidationStates.TOO_BIG; - } - - /** - * @param {string} text The text to check in user's locale units. - * @param {number} limit The upper bound of the valid margin range (in - * points). - * @return {number} An appropriate value from enum |marginValidationStates|. - */ - function validateMarginText(text, limit) { - var value = extractMarginValue(text); - if (value == -1) - return marginValidationStates.NOT_A_NUMBER; - value = print_preview.convertLocaleUnitsToPoints(value); - return validateMarginValue(value, limit); - } - - /** - * @param {number} value The value to convert in points. - * @return {number} The corresponding value after converting to user's locale - * units. - */ - function convertPointsToLocaleUnits(value) { - return print_preview.MarginSettings.useMetricSystem ? - convertPointsToMillimeters(value) : convertPointsToInches(value); - } - - /** - * @param {number} value The value to convert in user's locale units. - * @return {number} The corresponding value after converting to points. - */ - function convertLocaleUnitsToPoints(value) { - return print_preview.MarginSettings.useMetricSystem ? - convertMillimetersToPoints(value) : convertInchesToPoints(value); - } - - /** - * Converts |value| to user's locale units and then back to points. Note: - * Because of the precision the return value might be different than |value|. - * @param {number} value The value in points to convert. - * @return {number} The value in points after converting to user's locale - * units with a certain precision and back. - */ - function convertPointsToLocaleUnitsAndBack(value) { - var inLocaleUnits = - convertPointsToLocaleUnits(value).toFixed(getDesiredPrecision()); - return convertLocaleUnitsToPoints(inLocaleUnits); - } - - /** - * @return {number} The number of decimal digits to keep based on the - * measurement system used. - */ - function getDesiredPrecision() { - return print_preview.MarginSettings.useMetricSystem ? 0 : 2; - } - - /** - * @param {number} value The value to convert in points. - * @return {string} The equivalent text in user locale units with precision - * of two digits. - */ - function convertPointsToLocaleUnitsText(value) { - var inLocaleUnits = - convertPointsToLocaleUnits(value).toFixed(getDesiredPrecision()); - var inLocaleUnitsText = inLocaleUnits.toString(10).replace( - /\./g, print_preview.MarginSettings.decimalPoint); - return !print_preview.MarginSettings.useMetricSystem ? - inLocaleUnitsText + '"' : inLocaleUnitsText + 'mm'; - } - - /** - * Creates a Rect object. This object describes a rectangle in a 2D plane. The - * units of |x|, |y|, |width|, |height| are chosen by clients of this class. - * @constructor - */ - function Rect(x, y, width, height) { - // @type {number} Horizontal distance of the upper left corner from origin. - this.x = x; - // @type {number} Vertical distance of the upper left corner from origin. - this.y = y; - // @type {number} Width of |this| rectangle. - this.width = width; - // @type {number} Height of |this| rectangle. - this.height = height; - }; - - Rect.prototype = { - /** - * @type {number} The x coordinate of the right-most point. - */ - get right() { - return this.x + this.width; - }, - - /** - * @type {number} The y coordinate of the lower-most point. - */ - get bottom() { - return this.y + this.height; - }, - - /** - * Clones |this| and returns the cloned object. - * @return {Rect} A copy of |this|. - */ - clone: function() { - return new Rect(this.x, this.y, this.width, this.height); - } - }; - - return { - convertPointsToLocaleUnitsAndBack: - convertPointsToLocaleUnitsAndBack, - convertPointsToLocaleUnitsText: convertPointsToLocaleUnitsText, - convertLocaleUnitsToPoints: convertLocaleUnitsToPoints, - extractMarginValue: extractMarginValue, - marginValidationStates: marginValidationStates, - Rect: Rect, - validateMarginText: validateMarginText, - validateMarginValue: validateMarginValue - }; -}); diff --git a/chrome/browser/resources/print_preview/margins.css b/chrome/browser/resources/print_preview/margins.css deleted file mode 100644 index f2fd789..0000000 --- a/chrome/browser/resources/print_preview/margins.css +++ /dev/null @@ -1,100 +0,0 @@ -/* Copyright (c) 2012 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. */ - -#print-preview .margin-box { - background-color: #2a2a2a; - border: 1px solid #888; - box-sizing: border-box; - color: white; - cursor: auto; - font-family: arial; - font-size: 0.8em; - height: 25px; - left: 50%; - padding: 5px 10px; - position: absolute; - text-align: center; - top: 50%; - width: 60px; -} - -.margins-ui-pair.top .margin-box, -.margins-ui-pair.bottom .margin-box { - /* -width / 2 + 1 for the margin border */ - margin-left: -30px; -} - -.margins-ui-pair.left .margin-box, -.margins-ui-pair.right .margin-box { - /* -height / 2 */ - margin-top: -12px; -} - -.margins-ui-pair.bottom .margin-box { - /* -height + 1 */ - margin-top: -23px; -} - -.margins-ui-pair.right .margin-box { - /* -width - 2 */ - margin-left: -58px; -} - -.margin-box.invalid { - background-color: rgb(193, 27, 23); -} - -.margins-ui-pair { - background-color: transparent; - border-color: transparent; - position: absolute; -} - -.margins-ui-pair.right, -.margins-ui-pair.left { - cursor: ew-resize; -} - -.margins-ui-pair.top, -.margins-ui-pair.bottom { - cursor: ns-resize; -} - -.margins-ui-pair.dragging { - z-index: 1; -} - -.margin-line { - border-color: rgb(64, 128, 250); - border-style: dashed; - border-width: 1px; - pointer-events: none; - position: absolute; -} - -.margins-ui-pair.top .margin-line, -.margins-ui-pair.bottom .margin-line { - height: 0; - left: 0; - top: 50%; - width: 100%; -} - -.margins-ui-pair.left .margin-line, -.margins-ui-pair.right .margin-line { - height: 100%; - left: 50%; - top: 0; - width: 0; -} - -#customized-margins { - position: absolute; - top: 0; -} - -#customized-margins.invisible { - opacity: 0; - pointer-events: none; -} diff --git a/chrome/browser/resources/print_preview/margins_ui.js b/chrome/browser/resources/print_preview/margins_ui.js deleted file mode 100644 index c2d5309..0000000 --- a/chrome/browser/resources/print_preview/margins_ui.js +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - function MarginsUI() { - var marginsUI = document.createElement('div'); - marginsUI.__proto__ = MarginsUI.prototype; - marginsUI.id = 'customized-margins'; - - // @type {print_preview.MarginsUIPair} The object corresponding to the top - // margin. - marginsUI.topPair_ = new print_preview.MarginsUIPair( - print_preview.MarginSettings.TOP_GROUP); - // @type {print_preview.MarginsUIPair} The object corresponding to the left - // margin. - marginsUI.leftPair_ = new print_preview.MarginsUIPair( - print_preview.MarginSettings.LEFT_GROUP); - // @type {print_preview.MarginsUIPair} The object corresponding to the right - // margin. - marginsUI.rightPair_ = new print_preview.MarginsUIPair( - print_preview.MarginSettings.RIGHT_GROUP); - // @type {print_preview.MarginsUIPair} The object corresponding to the - // bottom margin. - marginsUI.bottomPair_ = new print_preview.MarginsUIPair( - print_preview.MarginSettings.BOTTOM_GROUP); - - var uiPairs = marginsUI.pairsAsList; - for (var i = 0; i < uiPairs.length; i++) - marginsUI.appendChild(uiPairs[i]); - - // @type {print_preview.MarginsUIPair} The object that is being dragged. - // null if no drag session is in progress. - marginsUI.lastClickedMarginsUIPair = null; - - // @type {EventTracker} Used to keep track of certain event listeners. - marginsUI.eventTracker_ = new EventTracker(); - - marginsUI.addEventListeners_(); - return marginsUI; - } - - /** - * @param {{x: number, y: number}} point The point with respect to the top - * left corner of the webpage. - * @return {{x: number, y: number}} The point with respect to the top left - * corner of the plugin area. - */ - MarginsUI.convert = function(point) { - var mainview = $('mainview'); - return { x: point.x - mainview.offsetLeft, - y: point.y - mainview.offsetTop }; - } - - MarginsUI.prototype = { - __proto__: HTMLDivElement.prototype, - - /** - * Adds an observer for |customEvents.MARGINS_MAY_HAVE_CHANGED| event. - * @param {function} func A callback function to be called when - * |customEvents.MARGINS_MAY_HAVE_CHANGED| event occurs. - */ - addObserver: function(func) { - var uiPairs = this.pairsAsList; - for (var i = 0; i < uiPairs.length; i++) { - uiPairs[i].box_.addEventListener( - customEvents.MARGINS_MAY_HAVE_CHANGED, func); - } - }, - - /** - * @return {array} An array including all |MarginUIPair| objects. - */ - get pairsAsList() { - return [this.topPair_, this.leftPair_, this.rightPair_, this.bottomPair_]; - }, - - /** - * Updates the state of the margins UI. - * @param {print_preview.Rect} marginsRectangle A rectangle describing the - * four margins. - * @param {Margins} marginValues The margin values in points. - * @param {Array.<number>} valueLimits The maximum allowed margins for each - * side in points. - * @param {boolean} keepDisplayedValue True if the currently displayed - * margin values should not be updated. - * @param {Array.<number>} valueLimitsInPercent The maximum allowed margins - * for each side in percentages. - */ - update: function(marginsRectangle, marginValues, valueLimits, - keepDisplayedValue, valueLimitsInPercent) { - var uiPairs = this.pairsAsList; - var order = ['top', 'left', 'right', 'bottom']; - for (var i = 0; i < uiPairs.length; i++) { - uiPairs[i].update(marginsRectangle, - marginValues[order[i]], - valueLimits[i], - keepDisplayedValue, - valueLimitsInPercent[i]); - } - }, - - /** - * Draws |this| based on the latest state. - */ - draw: function() { - this.applyClippingMask_(); - this.pairsAsList.forEach(function(pair) { pair.draw(); }); - }, - - /** - * Shows the margins UI. - */ - show: function() { - this.hidden = false; - this.classList.remove('invisible'); - }, - - /** - * Hides the margins UI and removes from the rendering flow if requested. - * @param {boolean} removeFromFlow True if |this| should also be removed - * from the rendering flow (in order to not interfere with the tab - * order). - */ - hide: function(removeFromFlow) { - removeFromFlow ? this.hidden = true : this.classList.add('invisible'); - }, - - /** - * Applies a clipping mask on |this| so that it does not paint on top of the - * scrollbars (if any). - */ - applyClippingMask_: function() { - var bottom = previewArea.height; - var right = previewArea.width; - this.style.clip = 'rect(0, ' + right + 'px, ' + bottom + 'px, 0)'; - }, - - /** - * Adds event listeners for various events. - * @private - */ - addEventListeners_: function() { - var uiPairs = this.pairsAsList; - for (var i = 0; i < uiPairs.length; i++) { - uiPairs[i].addEventListener(customEvents.MARGIN_LINE_MOUSE_DOWN, - this.onMarginLineMouseDown.bind(this)); - } - // After snapping to min/max the MarginUIPair might not receive the - // mouseup event since it is not under the mouse pointer, so it is handled - // here. - window.document.addEventListener('mouseup', - this.onMarginLineMouseUp.bind(this)); - }, - - /** - * Executes when a "MarginLineMouseDown" event occurs. - * @param {cr.Event} e The event that triggered this listener. - */ - onMarginLineMouseDown: function(e) { - this.lastClickedMarginsUIPair = e.target; - this.bringToFront(this.lastClickedMarginsUIPair); - // Note: Capturing mouse events at a higher level in the DOM than |this|, - // so that the plugin can still receive mouse events. - this.eventTracker_.add( - window.document, 'mousemove', this.onMouseMove_.bind(this), false); - }, - - /** - * Executes when a "MarginLineMouseUp" event occurs. - * @param {cr.Event} e The event that triggered this listener. - */ - onMarginLineMouseUp: function(e) { - if (this.lastClickedMarginsUIPair == null) - return; - this.lastClickedMarginsUIPair.onMouseUp(); - this.lastClickedMarginsUIPair = null; - this.eventTracker_.remove(window.document, 'mousemove'); - }, - - /** - * Brings |uiPair| in front of the other pairs. Used to make sure that the - * dragged pair is visible when overlapping with a not dragged pair. - * @param {print_preview.MarginsUIPair} uiPair The pair to bring in front. - */ - bringToFront: function(uiPair) { - this.pairsAsList.forEach(function(pair) { - pair.classList.remove('dragging'); - }); - uiPair.classList.add('dragging'); - }, - - /** - * Executes when a mousemove event occurs. - * @param {MouseEvent} e The event that triggered this listener. - */ - onMouseMove_: function(e) { - var point = MarginsUI.convert({ x: e.x, y: e.y }); - - var dragEvent = new cr.Event(customEvents.MARGIN_LINE_DRAG); - dragEvent.dragDelta = - this.lastClickedMarginsUIPair.getDragDisplacementFrom(point); - dragEvent.destinationPoint = point; - this.dispatchEvent(dragEvent); - } - }; - - return { - MarginsUI: MarginsUI - }; -}); diff --git a/chrome/browser/resources/print_preview/margins_ui_pair.js b/chrome/browser/resources/print_preview/margins_ui_pair.js deleted file mode 100644 index e35e9f2..0000000 --- a/chrome/browser/resources/print_preview/margins_ui_pair.js +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * @constructor - * This class represents a margin line and a textbox corresponding to that - * margin. - */ - function MarginsUIPair(groupName) { - var marginsUIPair = document.createElement('div'); - marginsUIPair.__proto__ = MarginsUIPair.prototype; - marginsUIPair.className = MarginsUIPair.CSS_CLASS; - // @type {string} Specifies which margin this line refers to. - marginsUIPair.marginGroup = groupName; - marginsUIPair.classList.add(groupName); - - // @type {print_preview.Rect} A rectangle describing the dimensions of - // the draggable area. - marginsUIPair.rectangle = null; - // @type {print_preview.Rect} A rectangle describing the four margins. - marginsUIPair.marginsRectangle = null; - // @type {HTMLDivElement} The line representing the margin. - marginsUIPair.line_ = document.createElement('div'); - marginsUIPair.line_.className = 'margin-line'; - // @type {print_preview.MarginTextbox} The textbox corresponding to this - // margin. - marginsUIPair.box_ = new print_preview.MarginTextbox(groupName); - // @type {{x: number, y: number}} The point where mousedown occured within - // the draggable area with respect the top-left corner of |this|. - marginsUIPair.mousePointerOffset = null; - // @type {{x: number, y:number}} The position of the origin before any - // dragging in progress occured. - marginsUIPair.originBeforeDragging = null; - - marginsUIPair.appendChild(marginsUIPair.line_); - marginsUIPair.appendChild(marginsUIPair.box_); - - marginsUIPair.addEventListeners_(); - return marginsUIPair; - } - - MarginsUIPair.CSS_CLASS = 'margins-ui-pair'; - // Width of the clickable region around each margin line in screen pixels. - MarginsUIPair.CLICKABLE_REGION = 20; - - MarginsUIPair.prototype = { - __proto__: HTMLDivElement.prototype, - - /** - * Updates the state. - */ - update: function(marginsRectangle, value, valueLimit, keepDisplayedValue, - valueLimitInPercent) { - this.marginsRectangle = marginsRectangle; - this.valueLimitInPercent = valueLimitInPercent; - this.rectangle = this.getCoordinates_(this.marginsRectangle); - this.box_.update(value, valueLimit, keepDisplayedValue); - }, - - /** - * Draws |this| based on the state. - */ - draw: function() { - this.drawDraggableArea_(); - this.box_.draw(); - }, - - /** - * Draws the area that can be clicked in order to start dragging. - * @private - */ - drawDraggableArea_: function() { - var width = previewArea.pdfPlugin_.offsetWidth; - var height = previewArea.pdfPlugin_.offsetHeight; - - this.style.left = Math.round(this.rectangle.x * width) + 'px'; - this.style.top = Math.round(this.rectangle.y * height) + 'px'; - this.style.width = Math.round(this.rectangle.width * width) + 'px'; - this.style.height = Math.round(this.rectangle.height * height) + 'px'; - }, - - /** - * Calculates the coordinates and size of |this|. - * @param {print_preview.Rect} marginsRectangle A rectangle describing the - * selected margins values in percentages. - * @private - */ - getCoordinates_: function(marginsRectangle) { - var pageLocation = previewArea.pageLocationNormalized; - var totalWidth = previewArea.pdfPlugin_.offsetWidth; - var totalHeight = previewArea.pdfPlugin_.offsetHeight; - var offsetY = (MarginsUIPair.CLICKABLE_REGION / 2) / totalHeight; - var offsetX = (MarginsUIPair.CLICKABLE_REGION / 2) / totalWidth; - - if (this.isTop_()) { - var lineCoordinates = new print_preview.Rect( - pageLocation.x, - marginsRectangle.y - offsetY, - pageLocation.width, - MarginsUIPair.CLICKABLE_REGION / totalHeight); - } else if (this.isBottom_()) { - var lineCoordinates = new print_preview.Rect( - pageLocation.x, - marginsRectangle.bottom - offsetY, - pageLocation.width, - MarginsUIPair.CLICKABLE_REGION / totalHeight); - } else if (this.isRight_()) { - var lineCoordinates = new print_preview.Rect( - marginsRectangle.right - offsetX, - pageLocation.y, - MarginsUIPair.CLICKABLE_REGION / totalWidth, - pageLocation.height); - } else if (this.isLeft_()) { - var lineCoordinates = new print_preview.Rect( - marginsRectangle.x - offsetX, - pageLocation.y, - MarginsUIPair.CLICKABLE_REGION / totalWidth, - pageLocation.height); - } - return lineCoordinates; - }, - - /** - * @return {boolean} True if |this| refers to the top margin. - * @private - */ - isTop_: function() { - return this.marginGroup == print_preview.MarginSettings.TOP_GROUP; - }, - - /** - * @return {boolean} True if |this| refers to the bottom margin. - * @private - */ - isBottom_: function() { - return this.marginGroup == print_preview.MarginSettings.BOTTOM_GROUP; - }, - - /** - * @return {boolean} True if |this| refers to the left margin. - * @private - */ - isLeft_: function() { - return this.marginGroup == print_preview.MarginSettings.LEFT_GROUP; - }, - - /** - * @return {boolean} True if |this| refers to the bottom margin. - * @private - */ - isRight_: function() { - return this.marginGroup == print_preview.MarginSettings.RIGHT_GROUP; - }, - - /** - * Adds event listeners for events related to dragging. - */ - addEventListeners_: function() { - this.onmousedown = this.onMouseDown_.bind(this); - }, - - /** - * Executes whenever a mousedown event occurs on |this| or its child nodes. - * @param {MouseEvent} e The event that occured. - */ - onMouseDown_: function(e) { - if (e.button != 0) - return; - if (e.target != this) - return; - var point = print_preview.MarginsUI.convert({x: e.x, y: e.y}); - this.originBeforeDragging = { x: this.offsetLeft, y: this.offsetTop }; - this.mousePointerOffset = - { x: point.x - this.offsetLeft, y: point.y - this.offsetTop }; - cr.dispatchSimpleEvent(this, customEvents.MARGIN_LINE_MOUSE_DOWN); - }, - - /** - * Executes whenever a mouseup event occurs while |this| is dragged. - */ - onMouseUp: function() { - this.box_.onTextValueMayHaveChanged(); - }, - - /** - * Moves |this| including all its children to |point|. - * @param {{x: number, y: number}} point The point where |this| should be - * moved. - */ - moveTo: function(point) { - if (this.isTop_() || this.isBottom_()) - this.style.top = point.y - this.mousePointerOffset.y + 'px'; - else - this.style.left = point.x - this.mousePointerOffset.x + 'px'; - }, - - /** - * @param {{x: number, y: number}} rhs The point to compare with. - * @return {number} The horizontal or vertical displacement in pixels - * between |this.originBeforeDragging| and |rhs|. Note: Bottom margin - * grows upwards, right margin grows when going to the left. - */ - getDragDisplacementFrom: function(rhs) { - var dragDisplacement = 0; - if (this.isTop_() || this.isBottom_()) { - dragDisplacement = (rhs.y - this.originBeforeDragging.y - - this.mousePointerOffset.y) / previewArea.height; - } else { - dragDisplacement = (rhs.x - this.originBeforeDragging.x - - this.mousePointerOffset.x) / previewArea.width; - } - - if (this.isBottom_() || this.isRight_()) - dragDisplacement *= -1; - return dragDisplacement; - }, - - /** - * Updates |this| while dragging is in progress. Takes care of rejecting - * invalid margin values. - * @param {number} dragDeltaInPoints The difference in points between the - * currently applied margin and the margin indicated by - * |destinationPoint|. - * @param {{x: number, y: number}} destinationPoint The point where the - * margin line should be drawn if |dragDeltaInPoints| is applied. - */ - updateWhileDragging: function(dragDeltaInPoints, destinationPoint) { - this.box_.updateWhileDragging(dragDeltaInPoints); - var validity = this.box_.validateDelta(dragDeltaInPoints); - if (validity == print_preview.marginValidationStates.WITHIN_RANGE) - this.moveTo(destinationPoint); - else if (validity == print_preview.marginValidationStates.TOO_SMALL) - this.snapToMinValue_(); - else if (validity == print_preview.marginValidationStates.TOO_BIG) - this.snapToMaxValue_(); - }, - - /** - * Snaps |this| to the minimum allowed margin value (0). Happens whenever - * the user drags the margin line to a smaller value than the minimum - * allowed. - * @private - */ - snapToMinValue_: function() { - var pageInformation = previewArea.pageLocationNormalized; - var newMarginsRectangle = this.marginsRectangle.clone(); - if (this.isTop_()) { - newMarginsRectangle.y = pageInformation.y; - } else if (this.isLeft_()) { - newMarginsRectangle.x = pageInformation.x; - } else if (this.isRight_()) { - newMarginsRectangle.x = pageInformation.x; - newMarginsRectangle.width = pageInformation.width; - } else if (this.isBottom_()) { - newMarginsRectangle.y = pageInformation.y; - newMarginsRectangle.height = pageInformation.height; - } - - this.rectangle = this.getCoordinates_(newMarginsRectangle); - this.drawDraggableArea_(); - }, - - /** - * Snaps |this| to the maximum allowed margin value. Happens whenever - * the user drags the margin line to a larger value than the maximum - * allowed. - * @private - */ - snapToMaxValue_: function() { - var newMarginsRectangle = this.marginsRectangle.clone(); - - if (this.isTop_()) { - newMarginsRectangle.y = this.valueLimitInPercent; - } else if (this.isLeft_()) { - newMarginsRectangle.x = this.valueLimitInPercent; - } else if (this.isRight_()) { - newMarginsRectangle.x = 0; - newMarginsRectangle.width = this.valueLimitInPercent; - } else if (this.isBottom_()) { - newMarginsRectangle.y = 0; - newMarginsRectangle.height = this.valueLimitInPercent; - } - - this.rectangle = this.getCoordinates_(newMarginsRectangle); - this.drawDraggableArea_(); - } - }; - - return { - MarginsUIPair: MarginsUIPair - }; -}); diff --git a/chrome/browser/resources/print_preview/more_options.html b/chrome/browser/resources/print_preview/more_options.html deleted file mode 100644 index c6032554..0000000 --- a/chrome/browser/resources/print_preview/more_options.html +++ /dev/null @@ -1,17 +0,0 @@ -<div id="more-options" class="two-column visible"> - <h1 i18n-content="optionsLabel"></h1> - <div class="right-column"> - <div id="header-footer-option"> - <label> - <input id="header-footer" type="checkbox" checked> - <span i18n-content="optionHeaderFooter"></span> - </label> - </div> - <div id="fit-to-page-option"> - <label> - <input id="fit-to-page" type="checkbox" checked> - <span i18n-content="optionFitToPage"></span> - </label> - </div> - </div> -</div> diff --git a/chrome/browser/resources/print_preview/more_options.js b/chrome/browser/resources/print_preview/more_options.js deleted file mode 100644 index 68957af..0000000 --- a/chrome/browser/resources/print_preview/more_options.js +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Creates a MoreOptions object. This object encapsulates all - * settings and logic related to the more options section. - * @constructor - */ - function MoreOptions() { - // @type {HTMLDivElement} HTML element representing more options. - this.moreOptions_ = $('more-options'); - - // @type {boolean} True if header footer option is hidden. - this.hideHeaderFooterOption_ = true; - - // @type {boolean} True if fit to page option should be hidden. - this.hideFitToPageOption_ = true; - - this.addEventListeners_(); - } - - cr.addSingletonGetter(MoreOptions); - - MoreOptions.prototype = { - /** - * Adding listeners to more options section. - * @private - */ - addEventListeners_: function() { - document.addEventListener(customEvents.PDF_LOADED, - this.onPDFLoaded_.bind(this)); - document.addEventListener( - customEvents.HEADER_FOOTER_VISIBILITY_CHANGED, - this.onHeaderFooterVisibilityChanged_.bind(this)); - document.addEventListener(customEvents.PRINTER_SELECTION_CHANGED, - this.onPrinterSelectionChanged_.bind(this)); - }, - - /** - * Listener executing when a |customEvents.HEADER_FOOTER_VISIBILITY_CHANGED| - * event occurs. - * @param {cr.Event} event The event that triggered this listener. - * @private - */ - onHeaderFooterVisibilityChanged_: function(event) { - this.hideHeaderFooterOption_ = !event.headerFooterApplies; - this.updateVisibility_(); - }, - - /** - * Listener executing when a |customEvents.PRINTER_SEELCTION_CHANGED| event - * occurs. - * @param {cr.Event} event The event that triggered this listener. - * @private - */ - onPrinterSelectionChanged_: function(event) { - if (previewModifiable) - return; - this.hideFitToPageOption_ = event.selectedPrinter == PRINT_TO_PDF; - this.updateVisibility_(); - }, - - /** - * Listener executing when a |customEvents.PDF_LOADED| event occurs. - * @private - */ - onPDFLoaded_: function() { - if (previewModifiable) - this.hideHeaderFooterOption_ = false; - else - this.hideFitToPageOption_ = false; - }, - - /** - * Hides or shows |this.moreOptions_|. - * @private - */ - updateVisibility_: function() { - if (this.hideFitToPageOption_ && this.hideHeaderFooterOption_) - fadeOutOption(this.moreOptions_); - else - fadeInOption(this.moreOptions_); - } - }; - - return { - MoreOptions: MoreOptions - }; -}); diff --git a/chrome/browser/resources/print_preview/native_layer.js b/chrome/browser/resources/print_preview/native_layer.js new file mode 100644 index 0000000..8acec69 --- /dev/null +++ b/chrome/browser/resources/print_preview/native_layer.js @@ -0,0 +1,727 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * An interface to the native Chromium printing system layer. + * @constructor + * @extends {cr.EventTarget} + */ + function NativeLayer() { + cr.EventTarget.call(this); + + // Bind global handlers + global['setInitialSettings'] = this.onSetInitialSettings_.bind(this); + global['setUseCloudPrint'] = this.onSetUseCloudPrint_.bind(this); + global['setPrinters'] = this.onSetPrinters_.bind(this); + global['updateWithPrinterCapabilities'] = + this.onUpdateWithPrinterCapabilities_.bind(this); + global['reloadPrintersList'] = this.onReloadPrintersList_.bind(this); + global['printToCloud'] = this.onPrintToCloud_.bind(this); + global['fileSelectionCancelled'] = + this.onFileSelectionCancelled_.bind(this); + global['fileSelectionCompleted'] = + this.onFileSelectionCompleted_.bind(this); + global['printPreviewFailed'] = this.onPrintPreviewFailed_.bind(this); + global['invalidPrinterSettings'] = + this.onInvalidPrinterSettings_.bind(this); + global['onDidGetDefaultPageLayout'] = + this.onDidGetDefaultPageLayout_.bind(this); + global['onDidGetPreviewPageCount'] = + this.onDidGetPreviewPageCount_.bind(this); + global['reloadPreviewPages'] = this.onReloadPreviewPages_.bind(this); + global['onDidPreviewPage'] = this.onDidPreviewPage_.bind(this); + global['updatePrintPreview'] = this.onUpdatePrintPreview_.bind(this); + global['printScalingDisabledForSourcePDF'] = + this.onPrintScalingDisabledForSourcePDF_.bind(this); + }; + + /** + * Event types dispatched from the Chromium native layer. + * @enum {string} + * @const + */ + NativeLayer.EventType = { + CAPABILITIES_SET: 'print_preview.NativeLayer.CAPABILITIES_SET', + CLOUD_PRINT_ENABLE: 'print_preview.NativeLayer.CLOUD_PRINT_ENABLE', + DESTINATIONS_RELOAD: 'print_preview.NativeLayer.DESTINATIONS_RELOAD', + DISABLE_SCALING: 'print_preview.NativeLayer.DISABLE_SCALING', + FILE_SELECTION_CANCEL: 'print_preview.NativeLayer.FILE_SELECTION_CANCEL', + FILE_SELECTION_COMPLETE: + 'print_preview.NativeLayer.FILE_SELECTION_COMPLETE', + INITIAL_SETTINGS_SET: 'print_preview.NativeLayer.INITIAL_SETTINGS_SET', + LOCAL_DESTINATIONS_SET: 'print_preview.NativeLayer.LOCAL_DESTINATIONS_SET', + PAGE_COUNT_READY: 'print_preview.NativeLayer.PAGE_COUNT_READY', + PAGE_LAYOUT_READY: 'print_preview.NativeLayer.PAGE_LAYOUT_READY', + PAGE_PREVIEW_READY: 'print_preview.NativeLayer.PAGE_PREVIEW_READY', + PREVIEW_GENERATION_DONE: + 'print_preview.NativeLayer.PREVIEW_GENERATION_DONE', + PREVIEW_GENERATION_FAIL: + 'print_preview.NativeLayer.PREVIEW_GENERATION_FAIL', + PREVIEW_RELOAD: 'print_preview.NativeLayer.PREVIEW_RELOAD', + PRINT_TO_CLOUD: 'print_preview.NativeLayer.PRINT_TO_CLOUD', + SETTINGS_INVALID: 'print_preview.NativeLayer.SETTINGS_INVALID' + }; + + /** + * Constant values matching printing::DuplexMode enum. + * @enum {number} + */ + NativeLayer.DuplexMode = { + SIMPLEX: 0, + LONG_EDGE: 1, + UNKNOWN_DUPLEX_MODE: -1 + }; + + /** + * Enumeration of color modes used by Chromium. + * @enum {number} + * @private + */ + NativeLayer.ColorMode_ = { + GRAY: 1, + COLOR: 2 + }; + + NativeLayer.prototype = { + __proto__: cr.EventTarget.prototype, + + /** Gets the initial settings to initialize the print preview with. */ + startGetInitialSettings: function() { + chrome.send('getInitialSettings'); + }, + + /** + * Requests the system's local print destinations. A LOCAL_DESTINATIONS_SET + * event will be dispatched in response. + */ + startGetLocalDestinations: function() { + chrome.send('getPrinters'); + }, + + /** + * Requests the destination's printing capabilities. A CAPABILITIES_SET + * event will be dispatched in response. + * @param {string} destinationId ID of the destination. + */ + startGetLocalDestinationCapabilities: function(destinationId) { + chrome.send('getPrinterCapabilities', [destinationId]); + }, + + /** + * Requests that a preview be generated. The following events may be + * dispatched in response: + * - PAGE_COUNT_READY + * - PAGE_LAYOUT_READY + * - PAGE_PREVIEW_READY + * - PREVIEW_GENERATION_DONE + * - PREVIEW_GENERATION_FAIL + * - PREVIEW_RELOAD + * @param {print_preview.Destination} destination Destination to print to. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to get the + * state of the print ticket. + * @param {number} ID of the preview request. + */ + startGetPreview: function(destination, printTicketStore, requestId) { + assert(printTicketStore.isTicketValidForPreview(), + 'Trying to generate preview when ticket is not valid'); + + var pageRanges = []; + if (requestId > 0 && + !printTicketStore.isDocumentModifiable && + printTicketStore.hasPageRangeCapability()) { + pageRanges = printTicketStore.getPageNumberSet().getPageRanges(); + } + + var ticket = { + 'pageRange': pageRanges, // pageRanges, + 'landscape': printTicketStore.isLandscapeEnabled(), + 'color': printTicketStore.isColorEnabled() ? + NativeLayer.ColorMode_.COLOR : NativeLayer.ColorMode_.GRAY, + 'headerFooterEnabled': printTicketStore.isHeaderFooterEnabled(), + 'marginsType': printTicketStore.getMarginsType(), + 'isFirstRequest': requestId == 0, + 'requestID': requestId, + 'previewModifiable': printTicketStore.isDocumentModifiable, + 'printToPDF': + destination != null && + destination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF, + 'printWithCloudPrint': destination != null && !destination.isLocal, + 'deviceName': destination == null ? 'foo' : destination.id, + 'cloudPrintID': destination == null ? 'foo' : destination.id, + 'generateDraftData': printTicketStore.isDocumentModifiable, + 'fitToPageEnabled': printTicketStore.isFitToPageEnabled(), + + // NOTE: Even though the following fields don't directly relate to the + // preview, they still need to be included. + 'duplex': printTicketStore.isDuplexEnabled() ? + NativeLayer.DuplexMode.LONG_EDGE : NativeLayer.DuplexMode.SIMPLEX, + 'copies': printTicketStore.getCopies(), + 'collate': printTicketStore.isCollateEnabled() + }; + + if (printTicketStore.hasMarginsCapability() && + printTicketStore.getMarginsType() == + print_preview.ticket_items.MarginsType.Value.CUSTOM) { + var customMargins = printTicketStore.getCustomMargins(); + var orientationEnum = + print_preview.ticket_items.CustomMargins.Orientation; + ticket['marginsCustom'] = { + 'marginTop': customMargins.get(orientationEnum.TOP), + 'marginRight': customMargins.get(orientationEnum.RIGHT), + 'marginBottom': customMargins.get(orientationEnum.BOTTOM), + 'marginLeft': customMargins.get(orientationEnum.LEFT) + }; + } + + chrome.send( + 'getPreview', + [JSON.stringify(ticket), -1, printTicketStore.isDocumentModifiable]); + }, + + /** + * Persists the selected destination and print ticket for the next print + * session. + * @param {!print_preview.Destination} destination Destination to save. + * @param {!print_preview.PrintTicketStore} printTicketStore Used for + * generating the serialized print ticket to persist. + */ + startSaveDestinationAndTicket: function(destination, printTicketStore) { + chrome.send('saveLastPrinter', [destination.id, '' /*TODO(rltoscano)*/]); + }, + + /** + * Requests that the document be printed. + * @param {!print_preview.Destination} destination Destination to print to. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to get the + * state of the print ticket. + * @param {print_preview.CloudPrintInterface} cloudPrintInterface Interface + * to Google Cloud Print. + * @param {boolean=} opt_isOpenPdfInPreview Whether to open the PDF in the + * system's preview application. + */ + startPrint: function(destination, printTicketStore, cloudPrintInterface, + opt_isOpenPdfInPreview) { + assert(printTicketStore.isTicketValid(), + 'Trying to print when ticket is not valid'); + + var ticket = { + 'pageRange': printTicketStore.hasPageRangeCapability() ? + printTicketStore.getPageNumberSet().getPageRanges() : [], + 'landscape': printTicketStore.isLandscapeEnabled(), + 'color': printTicketStore.isColorEnabled() ? + NativeLayer.ColorMode_.COLOR : NativeLayer.ColorMode_.GRAY, + 'headerFooterEnabled': printTicketStore.isHeaderFooterEnabled(), + 'marginsType': printTicketStore.getMarginsType(), + 'generateDraftData': true, // TODO(rltoscano): What should this be? + 'duplex': printTicketStore.isDuplexEnabled() ? + NativeLayer.DuplexMode.LONG_EDGE : NativeLayer.DuplexMode.SIMPLEX, + 'copies': printTicketStore.getCopies(), + 'collate': printTicketStore.isCollateEnabled(), + 'previewModifiable': printTicketStore.isDocumentModifiable, + 'printToPDF': destination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF, + 'printWithCloudPrint': !destination.isLocal, + 'deviceName': destination.id, + 'isFirstRequest': false, + 'requestID': -1, + 'fitToPageEnabled': printTicketStore.isFitToPageEnabled() + }; + + if (!destination.isLocal && !destination.isPrintWithCloudPrint) { + // We can't set cloudPrintID if the destination is "Print with Cloud + // Print" because the native system will try to print to Google Cloud + // Print with this ID instead of opening a Google Cloud Print dialog. + ticket['cloudPrintID'] = destination.id; + } + + if (printTicketStore.hasMarginsCapability() && + printTicketStore.getMarginsType() == + print_preview.ticket_items.MarginsType.Value.CUSTOM) { + var customMargins = printTicketStore.getCustomMargins(); + var orientationEnum = + print_preview.ticket_items.CustomMargins.Orientation; + ticket['marginsCustom'] = { + 'marginTop': customMargins.get(orientationEnum.TOP), + 'marginRight': customMargins.get(orientationEnum.RIGHT), + 'marginBottom': customMargins.get(orientationEnum.BOTTOM), + 'marginLeft': customMargins.get(orientationEnum.LEFT) + }; + } + + if (opt_isOpenPdfInPreview) { + ticket['OpenPDFInPreview'] = true; + } + + var cloudTicket = null; + if (!destination.isLocal) { + assert(cloudPrintInterface != null, + 'Trying to print to a cloud destination but Google Cloud ' + + 'Print integration is disabled'); + cloudTicket = cloudPrintInterface.createPrintTicket( + destination, printTicketStore); + cloudTicket = JSON.stringify(cloudTicket); + } + + chrome.send('print', [JSON.stringify(ticket), cloudTicket]); + }, + + /** Requests that the current pending print request be cancelled. */ + startCancelPendingPrint: function() { + chrome.send('cancelPendingPrintRequest'); + }, + + /** Shows the system's native printing dialog. */ + startShowSystemDialog: function() { + chrome.send('showSystemDialog'); + }, + + /** Closes the print preview dialog. */ + startCloseDialog: function() { + chrome.send('closePrintPreviewTab'); + chrome.send('DialogClose'); + }, + + /** Hide the print preview dialog and allow the native layer to close it. */ + startHideDialog: function() { + chrome.send('hidePreview'); + }, + + /** + * Opens the Google Cloud Print sign-in dialog. The DESTINATIONS_RELOAD + * event will be dispatched in response. + */ + startCloudPrintSignIn: function() { + chrome.send('signIn'); + }, + + /** Navigates the user to the system printer settings interface. */ + startManageLocalPrinters: function() { + chrome.send('manageLocalPrinters'); + }, + + /** Navigates the user to the Google Cloud Print management page. */ + startManageCloudPrinters: function() { + chrome.send('manageCloudPrinters'); + }, + + /** + * @param {object} initialSettings Object containing all initial settings. + */ + onSetInitialSettings_: function(initialSettings) { + // TODO(rltoscano): Use initialSettings['cloudPrintData'] to prepopulate + // destination and initial print ticket. + var numberFormatSymbols = + print_preview.MeasurementSystem.parseNumberFormat( + initialSettings['numberFormat']); + var unitType = print_preview.MeasurementSystem.UnitType.IMPERIAL; + if (initialSettings['measurementSystem'] != null) { + unitType = initialSettings['measurementSystem']; + } + var measurementSystem = new print_preview.MeasurementSystem( + numberFormatSymbols[0], + numberFormatSymbols[1], + unitType); + + var customMargins = null; + if (initialSettings.hasOwnProperty('marginTop') && + initialSettings.hasOwnProperty('marginRight') && + initialSettings.hasOwnProperty('marginBottom') && + initialSettings.hasOwnProperty('marginLeft')) { + customMargins = new print_preview.Margins( + initialSettings['marginTop'] || 0, + initialSettings['marginRight'] || 0, + initialSettings['marginBottom'] || 0, + initialSettings['marginLeft'] || 0); + } + + var marginsType = null; + if (initialSettings.hasOwnProperty('marginsType')) { + marginsType = initialSettings['marginsType']; + } + + var nativeInitialSettings = new print_preview.NativeInitialSettings( + initialSettings['printAutomaticallyInKioskMode'] || false, + numberFormatSymbols[0] || ',', + numberFormatSymbols[1] || '.', + unitType, + initialSettings['previewModifiable'] || false, + marginsType, + customMargins, + initialSettings['duplex'] || false, + initialSettings['headerFooterEnabled'] || false, + initialSettings['printerName'] || null); + + var initialSettingsSetEvent = new cr.Event( + NativeLayer.EventType.INITIAL_SETTINGS_SET); + initialSettingsSetEvent.initialSettings = nativeInitialSettings; + this.dispatchEvent(initialSettingsSetEvent); + }, + + /** + * Turn on the integration of Cloud Print. + * @param {string} cloudPrintURL The URL to use for cloud print servers. + * @private + */ + onSetUseCloudPrint_: function(cloudPrintURL) { + var cloudPrintEnableEvent = new cr.Event( + NativeLayer.EventType.CLOUD_PRINT_ENABLE); + cloudPrintEnableEvent.baseCloudPrintUrl = cloudPrintURL; + this.dispatchEvent(cloudPrintEnableEvent); + }, + + /** + * Updates the print preview with local printers. + * Called from PrintPreviewHandler::SetupPrinterList(). + * @param {Array} printers Array of printer info objects. + * @private + */ + onSetPrinters_: function(printers) { + var localDestsSetEvent = new cr.Event( + NativeLayer.EventType.LOCAL_DESTINATIONS_SET); + localDestsSetEvent.destinationInfos = printers; + this.dispatchEvent(localDestsSetEvent); + }, + + /** + * Called when native layer gets settings information for a requested local + * destination. + * @param {Object} settingsInfo printer setting information. + * @private + */ + onUpdateWithPrinterCapabilities_: function(settingsInfo) { + var capsSetEvent = new cr.Event(NativeLayer.EventType.CAPABILITIES_SET); + capsSetEvent.settingsInfo = settingsInfo; + this.dispatchEvent(capsSetEvent); + }, + + /** Reloads the printer list. */ + onReloadPrintersList_: function() { + cr.dispatchSimpleEvent(this, NativeLayer.EventType.DESTINATIONS_RELOAD); + }, + + /** + * Called from the C++ layer. + * Take the PDF data handed to us and submit it to the cloud, closing the + * print preview tab once the upload is successful. + * @param {string} data Data to send as the print job. + * @private + */ + onPrintToCloud_: function(data) { + var printToCloudEvent = new cr.Event( + NativeLayer.EventType.PRINT_TO_CLOUD); + printToCloudEvent.data = data; + this.dispatchEvent(printToCloudEvent); + }, + + /** + * Called from PrintPreviewUI::OnFileSelectionCancelled to notify the print + * preview tab regarding the file selection cancel event. + * @private + */ + onFileSelectionCancelled_: function() { + cr.dispatchSimpleEvent(this, NativeLayer.EventType.FILE_SELECTION_CANCEL); + }, + + /** + * Called from PrintPreviewUI::OnFileSelectionCompleted to notify the print + * preview tab regarding the file selection completed event. + * @private + */ + onFileSelectionCompleted_: function() { + // If the file selection is completed and the tab is not already closed it + // means that a pending print to pdf request exists. + cr.dispatchSimpleEvent( + this, NativeLayer.EventType.FILE_SELECTION_COMPLETE); + }, + + /** + * Display an error message when print preview fails. + * Called from PrintPreviewMessageHandler::OnPrintPreviewFailed(). + * @private + */ + onPrintPreviewFailed_: function() { + cr.dispatchSimpleEvent( + this, NativeLayer.EventType.PREVIEW_GENERATION_FAIL); + }, + + /** + * Display an error message when encountered invalid printer settings. + * Called from PrintPreviewMessageHandler::OnInvalidPrinterSettings(). + * @private + */ + onInvalidPrinterSettings_: function() { + cr.dispatchSimpleEvent(this, NativeLayer.EventType.SETTINGS_INVALID); + }, + + /** + * @param {{contentWidth: number, contentHeight: number, marginLeft: number, + * marginRight: number, marginTop: number, marginBottom: number, + * printableAreaX: number, printableAreaY: number, + * printableAreaWidth: number, printableAreaHeight: number}} + * pageLayout Specifies default page layout details in points. + * @param {boolean} hasCustomPageSizeStyle Indicates whether the previewed + * document has a custom page size style. + * @private + */ + onDidGetDefaultPageLayout_: function(pageLayout, hasCustomPageSizeStyle) { + var pageLayoutChangeEvent = new cr.Event( + NativeLayer.EventType.PAGE_LAYOUT_READY); + pageLayoutChangeEvent.pageLayout = pageLayout; + pageLayoutChangeEvent.hasCustomPageSizeStyle = hasCustomPageSizeStyle; + this.dispatchEvent(pageLayoutChangeEvent); + }, + + /** + * Update the page count and check the page range. + * Called from PrintPreviewUI::OnDidGetPreviewPageCount(). + * @param {number} pageCount The number of pages. + * @param {number} previewResponseId The preview request id that resulted in + * this response. + * @private + */ + onDidGetPreviewPageCount_: function(pageCount, previewResponseId) { + var pageCountChangeEvent = new cr.Event( + NativeLayer.EventType.PAGE_COUNT_READY); + pageCountChangeEvent.pageCount = pageCount; + pageCountChangeEvent.previewResponseId = previewResponseId; + this.dispatchEvent(pageCountChangeEvent); + }, + + /** + * Called when no pipelining previewed pages. + * @param {string} previewUid Preview unique identifier. + * @param {number} previewResponseId The preview request id that resulted in + * this response. + * @private + */ + onReloadPreviewPages_: function(previewUid, previewResponseId) { + var previewReloadEvent = new cr.Event( + NativeLayer.EventType.PREVIEW_RELOAD); + previewReloadEvent.previewUid = previewUid; + previewReloadEvent.previewResponseId = previewResponseId; + this.dispatchEvent(previewReloadEvent); + }, + + /** + * Notification that a print preview page has been rendered. + * Check if the settings have changed and request a regeneration if needed. + * Called from PrintPreviewUI::OnDidPreviewPage(). + * @param {number} pageNumber The page number, 0-based. + * @param {string} previewUid Preview unique identifier. + * @param {number} previewResponseId The preview request id that resulted in + * this response. + * @private + */ + onDidPreviewPage_: function(pageNumber, previewUid, previewResponseId) { + var pagePreviewGenEvent = new cr.Event( + NativeLayer.EventType.PAGE_PREVIEW_READY); + pagePreviewGenEvent.pageIndex = pageNumber; + pagePreviewGenEvent.previewUid = previewUid; + pagePreviewGenEvent.previewResponseId = previewResponseId; + this.dispatchEvent(pagePreviewGenEvent); + }, + + /** + * Update the print preview when new preview data is available. + * Create the PDF plugin as needed. + * Called from PrintPreviewUI::PreviewDataIsAvailable(). + * @param {string} previewUid Preview unique identifier. + * @param {number} previewResponseId The preview request id that resulted in + * this response. + * @private + */ + onUpdatePrintPreview_: function(previewUid, previewResponseId) { + var previewGenDoneEvent = new cr.Event( + NativeLayer.EventType.PREVIEW_GENERATION_DONE); + previewGenDoneEvent.previewUid = previewUid; + previewGenDoneEvent.previewResponseId = previewResponseId; + this.dispatchEvent(previewGenDoneEvent); + }, + + /** + * Updates the fit to page option state based on the print scaling option of + * source pdf. PDF's have an option to enable/disable print scaling. When we + * find out that the print scaling option is disabled for the source pdf, we + * uncheck the fitToPage_ to page checkbox. This function is called from C++ + * code. + * @private + */ + onPrintScalingDisabledForSourcePDF_: function() { + cr.dispatchSimpleEvent(this, NativeLayer.EventType.DISABLE_SCALING); + } + }; + + /** + * Initial settings retrieved from the native layer. + * @param {boolean} isInKioskAutoPrintMode Whether the print preview should be + * in auto-print mode. + * @param {string} thousandsDelimeter Character delimeter of thousands digits. + * @param {string} decimalDelimeter Character delimeter of the decimal point. + * @param {print_preview.MeasurementSystem.UnitType} unitType Unit type of + * local machine's measurement system. + * @param {boolean} isDocumentModifiable Whether the document to print is + * modifiable. + * @param {?print_preview.ticket_items.MarginsType.Value} marginsType Initial + * margins type. + * @param {print_preview.Margins} customMargins Initial custom margins. + * @param {boolean} isDuplexEnabled Whether duplexing is initially enabled. + * @param {boolean} isHeaderFooterEnabled Whether the header-footer is + * initially enabled. + * @param {?string} initialDestinationId ID of the destination to initially + * select. + * @constructor + */ + function NativeInitialSettings( + isInKioskAutoPrintMode, + thousandsDelimeter, + decimalDelimeter, + unitType, + isDocumentModifiable, + marginsType, + customMargins, + isDuplexEnabled, + isHeaderFooterEnabled, + initialDestinationId) { + + /** + * Whether the print preview should be in auto-print mode. + * @type {boolean} + * @private + */ + this.isInKioskAutoPrintMode_ = isInKioskAutoPrintMode; + + /** + * Character delimeter of thousands digits. + * @type {string} + * @private + */ + this.thousandsDelimeter_ = thousandsDelimeter; + + /** + * Character delimeter of the decimal point. + * @type {string} + * @private + */ + this.decimalDelimeter_ = decimalDelimeter; + + /** + * Unit type of local machine's measurement system. + * @type {string} + * @private + */ + this.unitType_ = unitType; + + /** + * Whether the document to print is modifiable. + * @type {boolean} + * @private + */ + this.isDocumentModifiable_ = isDocumentModifiable; + + /** + * Initial margins type. + * @type {?print_preview.ticket_items.MarginsType.Value} + * @private + */ + this.marginsType_ = marginsType; + + /** + * Initial custom margins. + * @type {print_preview.Margins} + * @private + */ + this.customMargins_ = customMargins; + + /** + * Whether duplexing is initially enabled. + * @type {boolean} + * @private + */ + this.isDuplexEnabled_ = isDuplexEnabled; + + /** + * Whether the header-footer is initially enabled. + * @type {boolean} + * @private + */ + this.isHeaderFooterEnabled_ = isHeaderFooterEnabled; + + /** + * ID of the initially selected destination. + * @type {?string} + * @private + */ + this.initialDestinationId_ = initialDestinationId; + }; + + NativeInitialSettings.prototype = { + /** + * @return {boolean} Whether the print preview should be in auto-print mode. + */ + get isInKioskAutoPrintMode() { + return this.isInKioskAutoPrintMode_; + }, + + /** @return {string} Character delimeter of thousands digits. */ + get thousandsDelimeter() { + return this.thousandsDelimeter_; + }, + + /** @return {string} Character delimeter of the decimal point. */ + get decimalDelimeter() { + return this.decimalDelimeter_; + }, + + /** + * @return {print_preview.MeasurementSystem.UnitType} Unit type of local + * machine's measurement system. + */ + get unitType() { + return this.unitType_; + }, + + /** @return {boolean} Whether the document to print is modifiable. */ + get isDocumentModifiable() { + return this.isDocumentModifiable_; + }, + + /** + * @return {?print_preview.ticket_items.MarginsType.Value} Initial margins + * type or {@code null} if not initially set. + */ + get marginsType() { + return this.marginsType_; + }, + + /** @return {print_preview.Margins} Initial custom margins. */ + get customMargins() { + return this.customMargins_; + }, + + /** @return {boolean} Whether duplexing is initially enabled. */ + get isDuplexEnabled() { + return this.isDuplexEnabled_; + }, + + /** @return {boolean} Whether the header-footer is initially enabled. */ + get isHeaderFooterEnabled() { + return this.isHeaderFooterEnabled_; + }, + + /** @return {?string} ID of the initially selected destination. */ + get initialDestinationId() { + return this.initialDestinationId_; + } + }; + + // Export + return { + NativeInitialSettings: NativeInitialSettings, + NativeLayer: NativeLayer + }; +}); diff --git a/chrome/browser/resources/print_preview/page_settings.html b/chrome/browser/resources/print_preview/page_settings.html deleted file mode 100644 index d4e55d2..0000000 --- a/chrome/browser/resources/print_preview/page_settings.html +++ /dev/null @@ -1,21 +0,0 @@ -<div class="two-column visible"> - <h1 i18n-content="pagesLabel"></h1> - <div class="right-column"> - <div class="radio"><label> - <input id="all-pages" name="pages" checked type="radio"> - <span i18n-content="optionAllPages"></span> - </label></div> - <div> - <div id="print-pages-div"> - <input id="print-pages" name="pages" type="radio" - i18n-values="aria-label:printPagesLabel;"> - <input id="individual-pages" type="text" - i18n-values="placeholder:examplePageRangeText"> - </div> - <span id="individual-pages-hint" class="hint" - i18n-content="pageRangeInstruction" aria-hidden="true" - aria-live="polite"> - </span> - </div> - </div> -</div> diff --git a/chrome/browser/resources/print_preview/page_settings.js b/chrome/browser/resources/print_preview/page_settings.js deleted file mode 100644 index b03306c..0000000 --- a/chrome/browser/resources/print_preview/page_settings.js +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Creates a PageSettings object. This object encapsulates all settings and - * logic related to page selection. - * @constructor - */ - function PageSettings() { - this.allPagesRadioButton_ = $('all-pages'); - this.selectedPagesRadioButton_ = $('print-pages'); - this.selectedPagesTextfield_ = $('individual-pages'); - this.selectedPagesHint_ = $('individual-pages-hint'); - - // Timer id of |this.selectedPagesTextfield|. It is used to reset the timer - // whenever needed. - this.timerId_; - - // Contains the previously selected pages (pages requested by last - // preview request). It is used in - // |this.onSelectedPagesMayHaveChanged_()| to make sure that a new preview - // is not requested more often than necessary. - this.previouslySelectedPages_ = []; - - // The total page count of the previewed document regardless of which pages - // the user has selected. - this.totalPageCount_ = undefined; - this.addEventListeners_(); - } - - cr.addSingletonGetter(PageSettings); - - PageSettings.prototype = { - /** - * The text that is currently in |this.selectedPagesTextfield|. - * @type {string} - */ - get selectedPagesText() { - return this.selectedPagesTextfield_.value; - }, - - /** - * The radio button corresponding to "all pages selected". - * @type {HTMLInputElement} - */ - get allPagesRadioButton() { - return this.allPagesRadioButton_; - }, - - /** - * The radio button corresponding to "specific pages selected". - * @type {HTMLInputElement} - */ - get selectedPagesRadioButton() { - return this.selectedPagesRadioButton_; - }, - - /** - * The textfield containing page ranges as specified by the user. - * @type {HTMLInputElement} - */ - get selectedPagesTextfield() { - return this.selectedPagesTextfield_; - }, - - /** - * The span element containing the hint shown to the user when page - * selection is not valid. - * @type {HTMLElement} - */ - get selectedPagesHint() { - return this.selectedPagesHint_; - }, - - /** - * The total page count of the previewed document regardless of which pages - * the user has selected. If the total count is not known this value must - * be undefined. - * @type {number} - */ - get totalPageCount() { - return this.totalPageCount_; - }, - - /** - * @param {number} count The number to assing to |this.totalPageCount_|. - */ - set totalPageCount(count) { - this.totalPageCount_ = count; - }, - - /** - * Returns the selected pages in ascending order without any duplicates. - * - * @return {Array.<number>} The selected pages. - */ - get selectedPagesSet() { - var selectedPagesText = this.selectedPagesText; - - if (this.allPagesRadioButton.checked || selectedPagesText.length == 0) - selectedPagesText = '1-' + this.totalPageCount_; - - var pageList = pageRangeTextToPageList(selectedPagesText, - this.totalPageCount_); - return pageListToPageSet(pageList); - }, - - /** - * Returns the previously selected pages in ascending order without any - * duplicates. - * - * @return {Array.<number>} The previously selected pages. - */ - get previouslySelectedPages() { - return this.previouslySelectedPages_; - }, - - /** - * Returns an array of objects describing the selected page ranges. See - * documentation of pageSetToPageRanges() for more details. - * @return {Array.<{from: number, to: number}>} An array of page range - * objects. - */ - get selectedPageRanges() { - return pageSetToPageRanges(this.selectedPagesSet); - }, - - /** - * Invalidates |this.totalPageCount_| to indicate that the total number of - * pages is not known. - * @private - */ - invalidateTotalPageCount_: function() { - this.totalPageCount_ = undefined; - }, - - /** - * Invlidates |this.previouslySelectedPages_| to indicate that this value - * does no longer apply. - * @private - */ - invalidatePreviouslySelectedPages_: function() { - this.previouslySelectedPages_.length = 0; - }, - - /** - * Resets all the state variables of this object and hides - * |this.selectedPagesHint|. - */ - resetState: function() { - this.selectedPagesTextfield.classList.remove('invalid'); - fadeOutElement(this.selectedPagesHint_); - this.invalidateTotalPageCount_(); - this.invalidatePreviouslySelectedPages_(); - }, - - /** - * Updates |this.totalPageCount_| and |this.previouslySelectedPages_|, - * only if they have been previously invalidated. - * @param {number} newTotalPageCount The new total page count. - */ - updateState: function(newTotalPageCount) { - if (!this.totalPageCount_) - this.totalPageCount_ = newTotalPageCount; - - if (this.previouslySelectedPages_.length == 0) { - for (var i = 0; i < this.totalPageCount_; i++) - this.previouslySelectedPages_.push(i + 1); - } - - if (!this.isPageSelectionValid()) - this.onSelectedPagesTextfieldChanged(); - }, - - /** - * Updates |this.previouslySelectedPages_| with the currently selected - * pages. - */ - updatePageSelection: function() { - this.previouslySelectedPages_ = this.selectedPagesSet; - }, - - /** - * @private - * @return {boolean} true if currently selected pages differ from - * |this.previouslySelectesPages_|. - */ - hasPageSelectionChanged_: function() { - return !areArraysEqual(this.previouslySelectedPages_, - this.selectedPagesSet); - }, - - /** - * Checks if the page selection has changed and is valid. - * @return {boolean} true if the page selection is changed and is valid. - */ - hasPageSelectionChangedAndIsValid: function() { - return this.isPageSelectionValid() && this.hasPageSelectionChanged_(); - }, - - /** - * Validates the contents of |this.selectedPagesTextfield|. - * - * @return {boolean} true if the text is valid. - */ - isPageSelectionValid: function() { - if (this.allPagesRadioButton_.checked || - this.selectedPagesText.length == 0) { - return true; - } - return isPageRangeTextValid(this.selectedPagesText, this.totalPageCount_); - }, - - /** - * Checks all page selection related settings and requests a new print - * previw if needed. - * @return {boolean} true if a new preview was requested. - */ - requestPrintPreviewIfNeeded: function() { - if (this.hasPageSelectionChangedAndIsValid()) { - this.updatePageSelection(); - requestPrintPreview(); - return true; - } - if (!this.isPageSelectionValid()) - this.onSelectedPagesTextfieldChanged(); - return false; - }, - - /** - * Validates the selected pages and updates the hint accordingly. - * @private - */ - validateSelectedPages_: function() { - if (this.isPageSelectionValid()) { - this.selectedPagesTextfield.classList.remove('invalid'); - fadeOutElement(this.selectedPagesHint_); - this.selectedPagesHint.setAttribute('aria-hidden', 'true'); - } else { - this.selectedPagesTextfield.classList.add('invalid'); - this.selectedPagesHint.classList.remove('suggestion'); - this.selectedPagesHint.setAttribute('aria-hidden', 'false'); - this.selectedPagesHint.innerHTML = - localStrings.getStringF('pageRangeInstruction', - localStrings.getString( - 'examplePageRangeText')); - fadeInElement(this.selectedPagesHint); - } - }, - - /** - * Executes whenever a blur event occurs on |this.selectedPagesTextfield| - * or when the timer expires. It takes care of - * 1) showing/hiding warnings/suggestions - * 2) updating print button/summary - */ - onSelectedPagesTextfieldChanged: function() { - this.validateSelectedPages_(); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_PRINT_BUTTON); - }, - - /** - * When the user stops typing in |this.selectedPagesTextfield| or clicks on - * |allPagesRadioButton|, a new print preview is requested, only if - * 1) The input is compeletely valid (it can be parsed in its entirety). - * 2) The newly selected pages differ from |this.previouslySelectedPages_|. - * @private - */ - onSelectedPagesMayHaveChanged_: function() { - if (this.selectedPagesRadioButton_.checked) - this.onSelectedPagesTextfieldChanged(); - - // Toggling between "all pages"/"some pages" radio buttons while having an - // invalid entry in the page selection textfield still requires updating - // the print summary and print button. - if (!this.isPageSelectionValid() || !this.hasPageSelectionChanged_()) { - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_PRINT_BUTTON); - return; - } - requestPrintPreview(); - }, - - /** - * Whenever |this.selectedPagesTextfield| gains focus we add a timer to - * detect when the user stops typing in order to update the print preview. - * @private - */ - addTimerToSelectedPagesTextfield_: function() { - this.timerId_ = window.setTimeout( - this.onSelectedPagesMayHaveChanged_.bind(this), 1000); - }, - - /** - * As the user types in |this.selectedPagesTextfield|, we need to reset - * this timer, since the page ranges are still being edited. - * @private - */ - resetSelectedPagesTextfieldTimer_: function() { - clearTimeout(this.timerId_); - this.addTimerToSelectedPagesTextfield_(); - }, - - /** - * Handles the blur event of |this.selectedPagesTextfield|. Un-checks - * |this.selectedPagesRadioButton| if the input field is empty. - * @private - */ - onSelectedPagesTextfieldBlur_: function() { - clearTimeout(this.timerId_); - if (!this.selectedPagesText.length) { - this.allPagesRadioButton_.checked = true; - this.validateSelectedPages_(); - } - this.onSelectedPagesMayHaveChanged_(); - }, - - /** - * Gives focus to |this.selectedPagesTextfield| when - * |this.selectedPagesRadioButton| is clicked. - * @private - */ - onSelectedPagesRadioButtonChecked_: function() { - this.selectedPagesTextfield_.focus(); - }, - - /** - * Listener executing when an input event occurs in - * |this.selectedPagesTextfield|. Ensures that - * |this.selectedPagesTextfield| is non-empty before checking - * |this.selectedPagesRadioButton|. - * @private - */ - onSelectedPagesTextfieldInput_: function() { - if (this.selectedPagesText.length) - this.selectedPagesRadioButton.checked = true; - this.resetSelectedPagesTextfieldTimer_(); - }, - - /** - * Listener executing whenever a keyup events occurs in the pages textfield. - * @param {!KeyboardEvent} e The event that triggered this listener. - * @private - */ - onKeyUp_: function(e) { - if (e.keyIdentifier == 'Enter') - printHeader.onPrintRequested(); - }, - - /** - * Adding listeners to all pages related controls. The listeners take care - * of altering their behavior depending on |hasPendingPreviewRequest|. - * @private - */ - addEventListeners_: function() { - this.allPagesRadioButton.onclick = - this.onSelectedPagesMayHaveChanged_.bind(this); - this.selectedPagesRadioButton.onclick = - this.onSelectedPagesMayHaveChanged_.bind(this); - this.selectedPagesTextfield.oninput = - this.onSelectedPagesTextfieldInput_.bind(this); - this.selectedPagesTextfield.onfocus = - this.addTimerToSelectedPagesTextfield_.bind(this); - this.selectedPagesTextfield.onblur = - this.onSelectedPagesTextfieldBlur_.bind(this); - this.selectedPagesTextfield.onkeyup = this.onKeyUp_.bind(this); - } - }; - - return { - PageSettings: PageSettings - }; -}); diff --git a/chrome/browser/resources/print_preview/preview_area.js b/chrome/browser/resources/print_preview/preview_area.js deleted file mode 100644 index ed5e92c..0000000 --- a/chrome/browser/resources/print_preview/preview_area.js +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright (c) 2012 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() { - 'use strict'; - - /** - * Creates a PreviewArea object. It represents the area where the preview - * document is displayed. - * @constructor - */ - function PreviewArea() { - // The embedded pdf plugin object. - this.pdfPlugin_ = null; - - // @type {HTMLDivElement} A layer on top of |this.pdfPlugin_| used for - // displaying messages to the user. - this.overlayLayer = $('overlay-layer'); - // @type {HTMLDivElement} Contains text displayed to the user followed by - // three animated dots. - this.customMessageWithDots_ = $('custom-message-with-dots'); - // @type {HTMLDivElement} Contains text displayed to the user. - this.customMessage_ = $('custom-message'); - // @type {HTMLInputElement} Button associated with a displayed error - // message. - this.errorButton = $('error-button'); - // @type {HTMLDivElement} Contains three animated (dancing) dots. - this.dancingDotsText = $('dancing-dots-text'); - - // True if the pdf document is loaded in the preview area. - this.pdfLoaded_ = false; - - // Contains the zoom level just before a new preview is requested so the - // same zoom level can be restored. - this.zoomLevel_ = null; - // @type {{x: number, y: number}} Contains the page offset values just - // before a new preview is requested so that the scroll amount can be - // restored later. - this.pageOffset_ = null; - // @type {print_preview.Rect} A rectangle describing the postion of the - // most visible page normalized with respect to the total height and width - // of the plugin. - this.pageLocationNormalized = null; - - // @type {EventTracker} Used to keep track of certain event listeners. - this.eventTracker = new EventTracker(); - - this.addEventListeners_(); - } - - cr.addSingletonGetter(PreviewArea); - - PreviewArea.prototype = { - /** - * The width of the plugin area in pixels, excluding any visible scrollbars, - * @type {number} - */ - get width() { - return this.widthPercent * this.pdfPlugin_.offsetWidth; - }, - - /** - * The height of the plugin area in pixels, excluding any visible - * scrollbars. - * @type {number} - */ - get height() { - return this.heightPercent * this.pdfPlugin_.offsetHeight; - }, - - /** - * The width of the plugin area in percent, excluding any visible - * scrollbars. - * @type {number} - */ - get widthPercent() { - var width = this.pdfPlugin_.getWidth(); - var scrollbarWidth = this.pdfPlugin_.getVerticalScrollbarThickness(); - return (width - scrollbarWidth) / width; - }, - - /** - * The height of the plugin area in percent, excluding any visible - * scrollbars. - * @type {number} - */ - get heightPercent() { - var height = this.pdfPlugin_.getHeight(); - var scrollbarHeight = this.pdfPlugin_.getHorizontalScrollbarThickness(); - return (height - scrollbarHeight) / height; - }, - - get pdfPlugin() { - return this.pdfPlugin_; - }, - - get pdfLoaded() { - return this.pdfLoaded_; - }, - - set pdfLoaded(pdfLoaded) { - this.pdfLoaded_ = pdfLoaded; - }, - - /** - * Initializes the PDF plugin and places it on the page. - * @param {!string} srcURL The URL of the document to be loaded in the - * plugin. - * @private - */ - createPDFPlugin_: function(srcURL) { - this.pdfPlugin_ = document.createElement('embed'); - this.pdfPlugin_.setAttribute('id', 'pdf-viewer'); - this.pdfPlugin_.setAttribute( - 'type', 'application/x-google-chrome-print-preview-pdf'); - this.pdfPlugin_.setAttribute('src', srcURL); - this.pdfPlugin_.setAttribute('aria-live', 'polite'); - this.pdfPlugin_.setAttribute('aria-atomic', 'true'); - $('mainview').appendChild(this.pdfPlugin_); - - this.pdfPlugin_.onload('onPDFLoad()'); - this.pdfPlugin_.onScroll('onPreviewPositionChanged()'); - this.pdfPlugin_.onPluginSizeChanged('onPreviewPositionChanged()'); - this.pdfPlugin_.removePrintButton(); - this.pdfPlugin_.grayscale(true); - }, - - /** - * Reloads the plugin with a new url. - * @param {string} srcURL The URL to load in the plugin. - * @private - */ - reloadPDFPlugin_: function(srcURL) { - // Need to call this before the reload(), where the plugin resets its - // internal page count. - this.pdfPlugin_.goToPage('0'); - this.pdfPlugin_.resetPrintPreviewUrl(srcURL); - this.pdfPlugin_.reload(); - this.pdfPlugin_.grayscale( - colorSettings.colorMode == print_preview.ColorSettings.GRAY); - }, - - /** - * Creates the PDF plugin or reloads the existing one. - * @param {number} srcDataIndex Preview data source index. - */ - createOrReloadPDFPlugin: function(srcDataIndex) { - var srcURL = getPageSrcURL(currentPreviewUid, srcDataIndex); - this.pdfPlugin_ ? this.reloadPDFPlugin_(srcURL) : - this.createPDFPlugin_(srcURL); - }, - - /** - * Queries the plugin for the location of the most visible page and updates - * |this.pageLocationNormalized|. - */ - update: function() { - if (!this.pdfLoaded_) - return; - var pluginLocation = - this.pdfPlugin_.getPageLocationNormalized().split(';'); - this.pageLocationNormalized = new print_preview.Rect( - parseFloat(pluginLocation[0]), - parseFloat(pluginLocation[1]), - parseFloat(pluginLocation[2]), - parseFloat(pluginLocation[3])); - }, - - /** - * Resets the state variables of |this|. - */ - resetState: function() { - if (this.pdfPlugin_ && previewModifiable) { - this.zoomLevel_ = this.pdfPlugin_.getZoomLevel(); - this.pageOffset_ = { - x: this.pdfPlugin_.pageXOffset(), - y: this.pdfPlugin_.pageYOffset() - }; - } - this.pdfLoaded_ = false; - }, - - /** - * Adds event listeners for various events. - * @private - */ - addEventListeners_: function() { - document.addEventListener(customEvents.PDF_LOADED, - this.onPDFLoaded_.bind(this)); - }, - - /** - * Listener executing when a |customEvents.PDF_LOADED| event occurs. - * @private - */ - onPDFLoaded_: function() { - this.pdfPlugin_ = $('pdf-viewer'); - this.pdfLoaded_ = true; - if (this.zoomLevel_ != null && this.pageOffset_ != null) { - this.pdfPlugin_.setZoomLevel(this.zoomLevel_); - this.pdfPlugin_.setPageXOffset(this.pageOffset_.x); - this.pdfPlugin_.setPageYOffset(this.pageOffset_.y); - } else { - this.pdfPlugin_.fitToHeight(); - } - }, - - /** - * Hides the |this.overlayLayer| and any messages currently displayed. - */ - hideOverlayLayer: function() { - this.eventTracker.add(this.overlayLayer, 'webkitTransitionEnd', - this.hideOverlayLayerCleanup_.bind(this), false); - if (this.pdfPlugin_) - this.pdfPlugin_.classList.remove('invisible'); - this.overlayLayer.classList.add('invisible'); - }, - - /** - * Displays the "Preview loading..." animation. - */ - showLoadingAnimation: function() { - this.showCustomMessage(localStrings.getString('loading')); - }, - - /** - * Necessary cleanup so that the dancing dots animation is not being - * rendered in the background when not displayed. - */ - hideOverlayLayerCleanup_: function() { - this.customMessageWithDots_.hidden = true; - this.eventTracker.remove(this.overlayLayer, 'webkitTransitionEnd'); - }, - - /** - * Displays |message| followed by three dancing dots animation. - * @param {string} message The message to be displayed. - */ - showCustomMessage: function(message) { - this.customMessageWithDots_.innerHTML = message + - this.dancingDotsText.innerHTML; - this.customMessageWithDots_.hidden = false; - if (this.pdfPlugin_) - this.pdfPlugin_.classList.add('invisible'); - this.overlayLayer.classList.remove('invisible'); - }, - - /** - * Clears the custom message with dots animation. - */ - clearCustomMessageWithDots: function() { - this.customMessageWithDots_.innerHTML = ''; - this.customMessageWithDots_.hidden = true; - }, - - /** - * Display an error message in the center of the preview area. - * @param {string} errorMessage The error message to be displayed. - */ - displayErrorMessageAndNotify: function(errorMessage) { - this.overlayLayer.classList.remove('invisible'); - this.customMessage_.textContent = errorMessage; - this.customMessage_.hidden = false; - this.customMessageWithDots_.innerHTML = ''; - this.customMessageWithDots_.hidden = true; - if (this.pdfPlugin_) { - $('mainview').removeChild(this.pdfPlugin_); - this.pdfPlugin_ = null; - } - cr.dispatchSimpleEvent(document, customEvents.PDF_GENERATION_ERROR); - }, - - /** - * Display an error message in the center of the preview area followed by a - * button. - * @param {string} errorMessage The error message to be displayed. - * @param {string} buttonText The text to be displayed within the button. - * @param {string} buttonListener The listener to be executed when the - * button is clicked. - */ - displayErrorMessageWithButtonAndNotify: function( - errorMessage, buttonText, buttonListener) { - this.errorButton.disabled = false; - this.errorButton.textContent = buttonText; - this.errorButton.onclick = buttonListener; - this.errorButton.hidden = false; - $('system-dialog-throbber').hidden = true; - $('native-print-dialog-throbber').hidden = true; - if (cr.isMac) - $('open-preview-app-throbber').hidden = true; - this.displayErrorMessageAndNotify(errorMessage); - } - }; - - return { - PreviewArea: PreviewArea - }; -}); diff --git a/chrome/browser/resources/print_preview/preview_generator.js b/chrome/browser/resources/print_preview/preview_generator.js new file mode 100644 index 0000000..4787dc4 --- /dev/null +++ b/chrome/browser/resources/print_preview/preview_generator.js @@ -0,0 +1,392 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Interface to the Chromium print preview generator. + * @param {!print_preview.DestinationStore} destinationStore Used to get the + * currently selected destination. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to read the + * state of the ticket and write document information. + * @param {!print_preview.NativeLayer} nativeLayer Used to communicate to + * Chromium's preview rendering system. + * @constructor + * @extends {cr.EventTarget} + */ + function PreviewGenerator(destinationStore, printTicketStore, nativeLayer) { + cr.EventTarget.call(this); + + /** + * Used to get the currently selected destination. + * @type {!print_preview.DestinationStore} + * @private + */ + this.destinationStore_ = destinationStore; + + /** + * Used to read the state of the ticket and write document information. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = printTicketStore; + + /** + * Interface to the Chromium native layer. + * @type {!print_preview.NativeLayer} + * @private + */ + this.nativeLayer_ = nativeLayer; + + /** + * ID of current in-flight request. Requests that do not share this ID will + * be ignored. + * @type {number} + * @private + */ + this.inFlightRequestId_ = -1; + + /** + * Whether the previews are being generated in landscape mode. + * @type {boolean} + * @private + */ + this.isLandscapeEnabled_ = false; + + /** + * Whether the previews are being generated with a header and footer. + * @type {boolean} + * @private + */ + this.isHeaderFooterEnabled_ = false; + + /** + * Whether the previews are being generated in color. + * @type {boolean} + * @private + */ + this.isColorEnabled_ = false; + + /** + * Whether the document should be fitted to the page. + * @type {boolean} + * @private + */ + this.isFitToPageEnabled_ = false; + + /** + * Page number set used to generate the last preview. + * @type {print_preview.PageNumberSet} + * @private + */ + this.pageNumberSet_ = null; + + /** + * Margins type used to generate the last preview. + * @type {print_preview.ticket_items.MarginsType.Value} + * @private + */ + this.marginsType_ = print_preview.ticket_items.MarginsType.Value.DEFAULT; + + /** + * Destination that was selected for the last preview. + * @type {print_preview.Destination} + * @private + */ + this.selectedDestination_ = null; + + /** + * Event tracker used to keep track of native layer events. + * @type {!EventTracker} + * @private + */ + this.tracker_ = new EventTracker(); + + this.addEventListeners_(); + }; + + /** + * Event types dispatched by the preview generator. + * @enum {string} + */ + PreviewGenerator.EventType = { + // Dispatched when the document can be printed. + DOCUMENT_READY: 'print_preview.PreviewGenerator.DOCUMENT_READY', + + // Dispatched when a page preview is ready. The previewIndex field of the + // event is the index of the page in the modified document, not the + // original. So page 4 of the original document might be previewIndex = 0 of + // the modified document. + PAGE_READY: 'print_preview.PreviewGenerator.PAGE_READY', + + // Dispatched when the document preview starts to be generated. + PREVIEW_START: 'print_preview.PreviewGenerator.PREVIEW_START', + + // Dispatched when the current print preview request fails. + FAIL: 'print_preview.PreviewGenerator.FAIL' + }; + + PreviewGenerator.prototype = { + __proto__: cr.EventTarget.prototype, + + /** + * Request that new preview be generated. A preview request will not be + * generated if the print ticket has not changed sufficiently. + * @return {boolean} Whether a new preview was actually requested. + */ + requestPreview: function() { + if (!this.printTicketStore_.isTicketValidForPreview()) { + return false; + } + if (!this.hasPreviewChanged_()) { + // Changes to these ticket items might not trigger a new preview, but + // they still need to be recorded. + this.marginsType_ = this.printTicketStore_.getMarginsType(); + return false; + } + this.isLandscapeEnabled_ = this.printTicketStore_.isLandscapeEnabled(); + this.isHeaderFooterEnabled_ = + this.printTicketStore_.isHeaderFooterEnabled(); + this.isColorEnabled_ = this.printTicketStore_.isColorEnabled(); + this.isFitToPageEnabled_ = this.printTicketStore_.isFitToPageEnabled(); + this.pageNumberSet_ = this.printTicketStore_.getPageNumberSet(); + this.marginsType_ = this.printTicketStore_.getMarginsType(); + this.selectedDestination_ = this.destinationStore_.selectedDestination; + + this.inFlightRequestId_++; + this.nativeLayer_.startGetPreview( + this.destinationStore_.selectedDestination, + this.printTicketStore_, + this.inFlightRequestId_); + return true; + }, + + /** Removes all event listeners that the preview generator has attached. */ + removeEventListeners: function() { + this.tracker_.removeAll(); + }, + + /** + * Adds event listeners to the relevant native layer events. + * @private + */ + addEventListeners_: function() { + this.tracker_.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.PAGE_LAYOUT_READY, + this.onPageLayoutReady_.bind(this)); + this.tracker_.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.PAGE_COUNT_READY, + this.onPageCountReady_.bind(this)); + this.tracker_.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.PREVIEW_RELOAD, + this.onPreviewReload_.bind(this)); + this.tracker_.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.PAGE_PREVIEW_READY, + this.onPagePreviewReady_.bind(this)); + this.tracker_.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.PREVIEW_GENERATION_DONE, + this.onPreviewGenerationDone_.bind(this)); + this.tracker_.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.PREVIEW_GENERATION_FAIL, + this.onPreviewGenerationFail_.bind(this)); + }, + + /** + * Dispatches a PAGE_READY event to signal that a page preview is ready. + * @param {number} previewIndex Index of the page with respect to the pages + * shown in the preview. E.g an index of 0 is the first displayed page, + * but not necessarily the first original document page. + * @param {number} pageNumber Number of the page with respect to the + * document. A value of 3 means it's the third page of the original + * document. + * @param {string} previewUid Unique identifier of the preview. + * @private + */ + dispatchPageReadyEvent_: function(previewIndex, pageNumber, previewUid) { + var pageGenEvent = new cr.Event(PreviewGenerator.EventType.PAGE_READY); + pageGenEvent.previewIndex = previewIndex; + pageGenEvent.previewUrl = + 'chrome://print/' + previewUid + '/' + (pageNumber - 1) + + '/print.pdf'; + this.dispatchEvent(pageGenEvent); + }, + + /** + * Dispatches a PREVIEW_START event. Signals that the preview should be + * reloaded. + * @param {string} previewUid Unique identifier of the preview. + * @private + */ + dispatchPreviewStartEvent_: function(previewUid) { + var previewStartEvent = new cr.Event( + PreviewGenerator.EventType.PREVIEW_START); + var index = -1; + if (this.printTicketStore_.isDocumentModifiable) { + index = 0; + } + previewStartEvent.previewUrl = + 'chrome://print/' + previewUid + '/' + index + '/print.pdf'; + this.dispatchEvent(previewStartEvent); + }, + + /** + * @return {boolean} Whether the print ticket has changed sufficiently to + * determine whether a new preview request should be issued. + * @private + */ + hasPreviewChanged_: function() { + var ticketStore = this.printTicketStore_; + return this.inFlightRequestId_ == -1 || + ticketStore.isLandscapeEnabled() != this.isLandscapeEnabled_ || + ticketStore.isHeaderFooterEnabled() != this.isHeaderFooterEnabled_ || + ticketStore.isColorEnabled() != this.isColorEnabled_ || + ticketStore.isFitToPageEnabled() != this.isFitToPageEnabled_ || + !ticketStore.getPageNumberSet().equals(this.pageNumberSet_) || + (ticketStore.getMarginsType() != this.marginsType_ && + ticketStore.getMarginsType() != + print_preview.ticket_items.MarginsType.Value.CUSTOM) || + (ticketStore.getMarginsType() == + print_preview.ticket_items.MarginsType.Value.CUSTOM && + !ticketStore.getCustomMargins().equals( + ticketStore.getDocumentMargins())) || + (this.selectedDestination_ != + this.destinationStore_.selectedDestination && + (this.destinationStore_.selectedDestination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF || + this.selectedDestination_.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF)); + }, + + /** + * Called when the page layout of the document is ready. Always occurs + * as a result of a preview request. + * @param {cr.Event} event Contains layout info about the document. + * @private + */ + onPageLayoutReady_: function(event) { + // NOTE: A request ID is not specified, so assuming its for the current + // in-flight request. + + var origin = new print_preview.Coordinate2d( + event.pageLayout.printableAreaX, + event.pageLayout.printableAreaY); + var size = new print_preview.Size( + event.pageLayout.printableAreaWidth, + event.pageLayout.printableAreaHeight); + + var margins = new print_preview.Margins( + Math.round(event.pageLayout.marginTop), + Math.round(event.pageLayout.marginRight), + Math.round(event.pageLayout.marginBottom), + Math.round(event.pageLayout.marginLeft)); + + var o = print_preview.ticket_items.CustomMargins.Orientation; + var pageSize = new print_preview.Size( + event.pageLayout.contentWidth + + margins.get(o.LEFT) + margins.get(o.RIGHT), + event.pageLayout.contentHeight + + margins.get(o.TOP) + margins.get(o.BOTTOM)); + + this.printTicketStore_.updateDocumentPageInfo( + new print_preview.PrintableArea(origin, size), + pageSize, + event.hasCustomPageSizeStyle, + margins); + }, + + /** + * Called when the document page count is received from the native layer. + * Always occurs as a result of a preview request. + * @param {cr.Event} event Contains the document's page count. + * @private + */ + onPageCountReady_: function(event) { + if (this.inFlightRequestId_ != event.previewResponseId) { + return; // Ignore old response. + } + this.printTicketStore_.updatePageCount(event.pageCount); + this.pageNumberSet_ = this.printTicketStore_.getPageNumberSet(); + }, + + /** + * Called when the print preview should be reloaded. + * @param {cr.Event} event Contains the preview UID and request ID. + * @private + */ + onPreviewReload_: function(event) { + if (this.inFlightRequestId_ != event.previewResponseId) { + return; // Ignore old response. + } + this.dispatchPreviewStartEvent_(event.previewUid); + var pageNumberSet = this.printTicketStore_.getPageNumberSet(); + for (var i = 0; i < pageNumberSet.size; i++) { + var pageNumber = pageNumberSet.getPageNumberAt(i); + this.dispatchPageReadyEvent_(i, pageNumber, event.previewUid); + } + cr.dispatchSimpleEvent(this, PreviewGenerator.EventType.DOCUMENT_READY); + }, + + /** + * Called when a page's preview has been generated. Dispatches a + * PAGE_READY event. + * @param {cr.Event} event Contains the page index and preview UID. + * @private + */ + onPagePreviewReady_: function(event) { + if (this.inFlightRequestId_ != event.previewResponseId) { + return; // Ignore old response. + } + var pageNumber = event.pageIndex + 1; + if (this.printTicketStore_.getPageNumberSet().hasPageNumber(pageNumber)) { + var previewIndex = this.printTicketStore_.getPageNumberSet() + .getPageNumberIndex(pageNumber); + if (previewIndex == 0) { + this.dispatchPreviewStartEvent_(event.previewUid); + } + this.dispatchPageReadyEvent_( + previewIndex, pageNumber, event.previewUid); + } + }, + + /** + * Called when the preview generation is complete. Dispatches a + * DOCUMENT_READY event. + * @param {cr.Event} event Contains the preview UID and response ID. + * @private + */ + onPreviewGenerationDone_: function(event) { + if (this.inFlightRequestId_ != event.previewResponseId) { + return; // Ignore old response. + } + // Dispatch a PREVIEW_START event since non-modifiable documents don't + // trigger PAGE_READY events. + if (!this.printTicketStore_.isDocumentModifiable) { + this.dispatchPreviewStartEvent_(event.previewUid); + } + cr.dispatchSimpleEvent(this, PreviewGenerator.EventType.DOCUMENT_READY); + }, + + /** + * Called when the preview generation fails. + * @private + */ + onPreviewGenerationFail_: function() { + // NOTE: No request ID is returned from Chromium so its assumed its the + // current one. + cr.dispatchSimpleEvent(this, PreviewGenerator.EventType.FAIL); + } + }; + + // Export + return { + PreviewGenerator: PreviewGenerator + }; +}); diff --git a/chrome/browser/resources/print_preview/previewarea/margin_control.css b/chrome/browser/resources/print_preview/previewarea/margin_control.css new file mode 100644 index 0000000..38c5834 --- /dev/null +++ b/chrome/browser/resources/print_preview/previewarea/margin_control.css @@ -0,0 +1,91 @@ +/* Copyright (c) 2012 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. + */ + +#preview-area .margin-control { + -webkit-transition: opacity 150ms linear; + position: absolute; +} + +#preview-area .margin-control.invisible { + opacity: 0; + pointer-events: none; +} + +#preview-area .margin-control.margin-control-top, +#preview-area .margin-control.margin-control-bottom { + cursor: ns-resize; + padding: 8px 0; + width: 100%; +} + +#preview-area .margin-control.margin-control-left, +#preview-area .margin-control.margin-control-right { + cursor: ew-resize; + height: 100%; + padding: 0 8px; +} + +#preview-area .margin-control.margin-control-disabled { + cursor: default; +} + +#preview-area .margin-control-line { + border-color: rgb(64, 128, 250); + border-style: dashed; + border-width: 1px; +} + +#preview-area .margin-control-disabled .margin-control-line { + border-color: gray; +} + +#preview-area .margin-control-top .margin-control-line, +#preview-area .margin-control-bottom .margin-control-line { + width: 100%; +} + +#preview-area .margin-control-left .margin-control-line, +#preview-area .margin-control-right .margin-control-line { + height: 100%; +} + +#preview-area .margin-control-textbox { + background-color: #2a2a2a; + border: 1px solid #888; + box-sizing: border-box; + color: white; + cursor: auto; + font-family: arial; + font-size: 0.8em; + height: 25px; + padding: 5px 0; + position: absolute; + text-align: center; + width: 60px; +} + +#preview-area .margin-control-textbox.invalid { + background-color: rgb(193, 27, 23); +} + +#preview-area .margin-control-top .margin-control-textbox { + left: 50%; + top: 8px; +} + +#preview-area .margin-control-right .margin-control-textbox { + right: 8px; + top: 50%; +} + +#preview-area .margin-control-bottom .margin-control-textbox { + bottom: 8px; + right: 50%; +} + +#preview-area .margin-control-left .margin-control-textbox { + bottom: 50%; + left: 8px; +} diff --git a/chrome/browser/resources/print_preview/previewarea/margin_control.html b/chrome/browser/resources/print_preview/previewarea/margin_control.html new file mode 100644 index 0000000..58bbdbab --- /dev/null +++ b/chrome/browser/resources/print_preview/previewarea/margin_control.html @@ -0,0 +1,6 @@ +<div id="margin-control-template" + class="margin-control invisible" + style="display: none;"> + <div class="margin-control-line"></div> + <input class="margin-control-textbox" type="text"/> +</div> diff --git a/chrome/browser/resources/print_preview/previewarea/margin_control.js b/chrome/browser/resources/print_preview/previewarea/margin_control.js new file mode 100644 index 0000000..476371a --- /dev/null +++ b/chrome/browser/resources/print_preview/previewarea/margin_control.js @@ -0,0 +1,467 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Draggable control for setting a page margin. + * @param {print_preview.ticket_items.CustomMargins.Orientation} orientation + * Orientation of the margin control that determines where the margin + * textbox will be placed. + * @constructor + * @extends {print_preview.Component} + */ + function MarginControl(orientation) { + print_preview.Component.call(this); + + /** + * Determines where the margin textbox will be placed. + * @type {print_preview.ticket_items.CustomMargins.Orientation} + * @private + */ + this.orientation_ = orientation; + + /** + * Position of the margin control in points. + * @type {number} + * @private + */ + this.positionInPts_ = 0; + + /** + * Page size of the document to print. + * @type {!print_preview.Size} + * @private + */ + this.pageSize_ = new print_preview.Size(0, 0); + + /** + * Amount to scale pixel values by to convert to pixel space. + * @type {number} + * @private + */ + this.scaleTransform_ = 1; + + /** + * Amount to translate values in pixel space. + * @type {!print_preview.Coordinate2d} + * @private + */ + this.translateTransform_ = new print_preview.Coordinate2d(0, 0); + + /** + * Position of the margin control when dragging starts. + * @type {print_preview.Coordinate2d} + * @private + */ + this.marginStartPositionInPixels_ = null; + + /** + * Position of the mouse when the dragging starts. + * @type {print_preview.Coordinate2d} + * @private + */ + this.mouseStartPositionInPixels_ = null; + + /** + * Processing timeout for the textbox. + * @type {Object} + * @private + */ + this.textTimeout_ = null; + + /** + * Value of the textbox when the timeout was started. + * @type {?string} + * @private + */ + this.preTimeoutValue_ = null; + + /** + * Textbox used to display and receive the value of the margin. + * @type {HTMLInputElement} + * @private + */ + this.textbox_ = null; + + /** + * Element of the margin control line. + * @type {HTMLElement} + * @private + */ + this.marginLineEl_ = null; + + /** + * Whether this margin control's textbox has keyboard focus. + * @type {boolean} + * @private + */ + this.isFocused_ = false; + + /** + * Whether the margin control is in an error state. + * @type {boolean} + * @private + */ + this.isInError_ = false; + }; + + /** + * Event types dispatched by the margin control. + * @enum {string} + */ + MarginControl.EventType = { + // Dispatched when the margin control starts dragging. + DRAG_START: 'print_preview.MarginControl.DRAG_START', + + // Dispatched when the text in the margin control's textbox changes. + TEXT_CHANGE: 'print_preview.MarginControl.TEXT_CHANGE' + }; + + /** + * CSS classes used by this component. + * @enum {string} + * @private + */ + MarginControl.Classes_ = { + TOP: 'margin-control-top', + RIGHT: 'margin-control-right', + BOTTOM: 'margin-control-bottom', + LEFT: 'margin-control-left', + TEXTBOX: 'margin-control-textbox', + INVALID: 'invalid', + INVISIBLE: 'invisible', + DISABLED: 'margin-control-disabled', + DRAGGING: 'margin-control-dragging', + LINE: 'margin-control-line' + }; + + /** + * Map from orientation to CSS class name. + * @type {object.< + * print_preview.ticket_items.CustomMargins.Orientation, + * MarginControl.Classes_>} + * @private + */ + MarginControl.OrientationToClass_ = {}; + MarginControl.OrientationToClass_[ + print_preview.ticket_items.CustomMargins.Orientation.TOP] = + MarginControl.Classes_.TOP; + MarginControl.OrientationToClass_[ + print_preview.ticket_items.CustomMargins.Orientation.RIGHT] = + MarginControl.Classes_.RIGHT; + MarginControl.OrientationToClass_[ + print_preview.ticket_items.CustomMargins.Orientation.BOTTOM] = + MarginControl.Classes_.BOTTOM; + MarginControl.OrientationToClass_[ + print_preview.ticket_items.CustomMargins.Orientation.LEFT] = + MarginControl.Classes_.LEFT; + + /** + * Radius of the margin control in pixels. Padding of control + 1 for border. + * @type {number} + * @const + * @private + */ + MarginControl.RADIUS_ = 9; + + /** + * Timeout after a text change after which the text in the textbox is saved to + * the print ticket. Value in milliseconds. + * @type {number} + * @const + * @private + */ + MarginControl.TEXTBOX_TIMEOUT_ = 1000; + + MarginControl.prototype = { + __proto__: print_preview.Component.prototype, + + /** @return {boolean} Whether this margin control is in focus. */ + getIsFocused: function() { + return this.isFocused_; + }, + + /** + * @return {print_preview.ticket_items.CustomMargins.Orientation} + * Orientation of the margin control. + */ + getOrientation: function() { + return this.orientation_; + }, + + /** + * @param {number} scaleTransform New scale transform of the margin control. + */ + setScaleTransform: function(scaleTransform) { + this.scaleTransform_ = scaleTransform; + // Reset position + this.setPositionInPts(this.positionInPts_); + }, + + /** + * @param {!print_preview.Coordinate2d} translateTransform New translate + * transform of the margin control. + */ + setTranslateTransform: function(translateTransform) { + this.translateTransform_ = translateTransform; + // Reset position + this.setPositionInPts(this.positionInPts_); + }, + + /** + * @param {!print_preview.Size} pageSize New size of the document's pages. + */ + setPageSize: function(pageSize) { + this.pageSize_ = pageSize; + this.setPositionInPts(this.positionInPts_); + }, + + /** @param {boolean} isVisible Whether the margin control is visible. */ + setIsVisible: function(isVisible) { + if (isVisible) { + this.getElement().classList.remove(MarginControl.Classes_.INVISIBLE); + } else { + this.getElement().classList.add(MarginControl.Classes_.INVISIBLE); + } + }, + + /** @return {boolean} Whether the margin control is in an error state. */ + getIsInError: function() { + return this.isInError_; + }, + + /** + * @param {boolean} isInError Whether the margin control is in an error + * state. + */ + setIsInError: function(isInError) { + this.isInError_ = isInError; + if (isInError) { + this.textbox_.classList.add(MarginControl.Classes_.INVALID); + } else { + this.textbox_.classList.remove(MarginControl.Classes_.INVALID); + } + }, + + /** @param {boolean} isEnabled Whether to enable the margin control. */ + setIsEnabled: function(isEnabled) { + this.textbox_.disabled = !isEnabled; + if (isEnabled) { + this.getElement().classList.remove(MarginControl.Classes_.DISABLED); + } else { + this.getElement().classList.add(MarginControl.Classes_.DISABLED); + } + }, + + /** @return {number} Current position of the margin control in points. */ + getPositionInPts: function() { + return this.positionInPts_; + }, + + /** + * @param {number} posInPts New position of the margin control in points. + */ + setPositionInPts: function(posInPts) { + this.positionInPts_ = posInPts; + var orientationEnum = + print_preview.ticket_items.CustomMargins.Orientation; + var x = this.translateTransform_.x; + var y = this.translateTransform_.y; + var width = null, height = null; + if (this.orientation_ == orientationEnum.TOP) { + y = this.scaleTransform_ * posInPts + this.translateTransform_.y - + MarginControl.RADIUS_; + width = this.scaleTransform_ * this.pageSize_.width; + } else if (this.orientation_ == orientationEnum.RIGHT) { + x = this.scaleTransform_ * (this.pageSize_.width - posInPts) + + this.translateTransform_.x - MarginControl.RADIUS_; + height = this.scaleTransform_ * this.pageSize_.height; + } else if (this.orientation_ == orientationEnum.BOTTOM) { + y = this.scaleTransform_ * (this.pageSize_.height - posInPts) + + this.translateTransform_.y - MarginControl.RADIUS_; + width = this.scaleTransform_ * this.pageSize_.width; + } else { + x = this.scaleTransform_ * posInPts + this.translateTransform_.x - + MarginControl.RADIUS_; + height = this.scaleTransform_ * this.pageSize_.height; + } + this.getElement().style.left = Math.round(x) + 'px'; + this.getElement().style.top = Math.round(y) + 'px'; + if (width != null) { + this.getElement().style.width = Math.round(width) + 'px'; + } + if (height != null) { + this.getElement().style.height = Math.round(height) + 'px'; + } + }, + + /** @return {string} The value in the margin control's textbox. */ + getTextboxValue: function() { + return this.textbox_.value; + }, + + /** @param {string} value New value of the margin control's textbox. */ + setTextboxValue: function(value) { + if (this.textbox_.value != value) { + this.textbox_.value = value; + } + }, + + /** + * Converts a value in pixels to points. + * @param {number} Pixel value to convert. + * @return {number} Given value expressed in points. + */ + convertPixelsToPts: function(pixels) { + var pts; + var orientationEnum = + print_preview.ticket_items.CustomMargins.Orientation; + if (this.orientation_ == orientationEnum.TOP) { + pts = pixels - this.translateTransform_.y + MarginControl.RADIUS_; + pts /= this.scaleTransform_; + } else if (this.orientation_ == orientationEnum.RIGHT) { + pts = pixels - this.translateTransform_.x + MarginControl.RADIUS_; + pts /= this.scaleTransform_; + pts = this.pageSize_.width - pts; + } else if (this.orientation_ == orientationEnum.BOTTOM) { + pts = pixels - this.translateTransform_.y + MarginControl.RADIUS_; + pts /= this.scaleTransform_; + pts = this.pageSize_.height - pts; + } else { + pts = pixels - this.translateTransform_.x + MarginControl.RADIUS_; + pts /= this.scaleTransform_; + } + return pts; + }, + + /** + * Translates the position of the margin control relative to the mouse + * position in pixels. + * @param {!print_preview.Coordinate2d} mousePosition New position of + * the mouse. + * @return {!print_preview.Coordinate2d} New position of the margin control. + */ + translateMouseToPositionInPixels: function(mousePosition) { + return new print_preview.Coordinate2d( + mousePosition.x - this.mouseStartPositionInPixels_.x + + this.marginStartPositionInPixels_.x, + mousePosition.y - this.mouseStartPositionInPixels_.y + + this.marginStartPositionInPixels_.y); + }, + + /** @override */ + createDom: function() { + this.setElementInternal(this.cloneTemplateInternal( + 'margin-control-template')); + this.getElement().classList.add(MarginControl.OrientationToClass_[ + this.orientation_]); + this.textbox_ = this.getElement().getElementsByClassName( + MarginControl.Classes_.TEXTBOX)[0]; + this.marginLineEl_ = this.getElement().getElementsByClassName( + MarginControl.Classes_.LINE)[0]; + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + this.tracker.add( + this.getElement(), 'mousedown', this.onMouseDown_.bind(this)); + this.tracker.add( + this.textbox_, 'keydown', this.onTextboxKeyDown_.bind(this)); + this.tracker.add( + this.textbox_, 'focus', this.setIsFocused_.bind(this, true)); + this.tracker.add(this.textbox_, 'blur', this.onTexboxBlur_.bind(this)); + }, + + /** @override */ + exitDocument: function() { + print_preview.Component.prototype.exitDocument.call(this); + this.textbox_ = null; + this.marginLineEl_ = null; + }, + + /** + * @param {boolean} isFocused Whether the margin control is in focus. + * @private + */ + setIsFocused_: function(isFocused) { + this.isFocused_ = isFocused; + }, + + /** + * Called whenever a mousedown event occurs on the component. + * @param {MouseEvent} event The event that occured. + * @private + */ + onMouseDown_: function(event) { + if (!this.textbox_.disabled && + event.button == 0 && + (event.target == this.getElement() || + event.target == this.marginLineEl_)) { + this.mouseStartPositionInPixels_ = + new print_preview.Coordinate2d(event.x, event.y); + this.marginStartPositionInPixels_ = new print_preview.Coordinate2d( + this.getElement().offsetLeft, this.getElement().offsetTop); + this.setIsInError(false); + cr.dispatchSimpleEvent(this, MarginControl.EventType.DRAG_START); + } + }, + + /** + * Called when a key down event occurs on the textbox. Dispatches a + * TEXT_CHANGE event if the "Enter" key was pressed. + * @param {Event} event Contains the key that was pressed. + * @private + */ + onTextboxKeyDown_: function(event) { + if (this.textTimeout_) { + clearTimeout(this.textTimeout_); + this.textTimeout_ = null; + } + if (event.keyIdentifier == 'Enter') { + this.preTimeoutValue_ = null; + cr.dispatchSimpleEvent(this, MarginControl.EventType.TEXT_CHANGE); + } else { + if (this.preTimeoutValue_ == null) { + this.preTimeoutValue_ = this.textbox_.value; + } + this.textTimeout_ = setTimeout( + this.onTextboxTimeout_.bind(this), MarginControl.TEXTBOX_TIMEOUT_); + } + }, + + /** + * Called after a timeout after the text in the textbox has changed. Saves + * the textbox's value to the print ticket. + * @private + */ + onTextboxTimeout_: function() { + this.textTimeout_ = null; + if (this.textbox_.value != this.preTimeoutValue_) { + cr.dispatchSimpleEvent(this, MarginControl.EventType.TEXT_CHANGE); + } + this.preTimeoutValue_ = null; + }, + + /** + * Called when the textbox loses focus. Dispatches a TEXT_CHANGE event. + */ + onTexboxBlur_: function() { + if (this.textTimeout_) { + clearTimeout(this.textTimeout_); + this.textTimeout_ = null; + this.preTimeoutValue_ = null; + } + this.setIsFocused_(false); + cr.dispatchSimpleEvent(this, MarginControl.EventType.TEXT_CHANGE); + } + }; + + // Export + return { + MarginControl: MarginControl + }; +}); diff --git a/chrome/browser/resources/print_preview/previewarea/margin_control_container.css b/chrome/browser/resources/print_preview/previewarea/margin_control_container.css new file mode 100644 index 0000000..bfb1935 --- /dev/null +++ b/chrome/browser/resources/print_preview/previewarea/margin_control_container.css @@ -0,0 +1,12 @@ +/* Copyright (c) 2012 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. + */ + +.margin-control-container-dragging-vertical { + cursor: ns-resize; +} + +.margin-control-container-dragging-horizontal { + cursor: ew-resize; +} diff --git a/chrome/browser/resources/print_preview/previewarea/margin_control_container.js b/chrome/browser/resources/print_preview/previewarea/margin_control_container.js new file mode 100644 index 0000000..1209da9 --- /dev/null +++ b/chrome/browser/resources/print_preview/previewarea/margin_control_container.js @@ -0,0 +1,446 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * UI component used for setting custom print margins. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to read and + * write custom margin values. + * @constructor + * @extends {print_preview.Component} + */ + function MarginControlContainer(printTicketStore) { + print_preview.Component.call(this); + + /** + * Used to read and write custom margin values. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = printTicketStore; + + /** + * Used to convert between the system's local units and points. + * @type {!print_preview.MeasurementSystem} + * @private + */ + this.measurementSystem_ = printTicketStore.measurementSystem; + + /** + * Convenience array that contains all of the margin controls. + * @type {!Object< + * print_preview.ticket_items.CustomMargins.Orientation, + * !print_preview.MarginControl>} + * @private + */ + this.controls_ = {}; + for (var key in print_preview.ticket_items.CustomMargins.Orientation) { + var orientation = print_preview.ticket_items.CustomMargins.Orientation[ + key]; + var control = new print_preview.MarginControl(orientation); + this.controls_[orientation] = control; + this.addChild(control); + } + + /** + * Margin control currently being dragged. Null if no control is being + * dragged. + * @type {print_preview.MarginControl} + * @private + */ + this.draggedControl_ = null; + + /** + * Translation transformation in pixels to translate from the origin of the + * custom margins component to the top-left corner of the most visible + * preview page. + * @type {!print_preview.Coordinate2d} + * @private + */ + this.translateTransform_ = new print_preview.Coordinate2d(0, 0); + + /** + * Scaling transformation to scale from pixels to the units which the + * print preview is in. The scaling factor is the same in both dimensions, + * so this field is just a single number. + * @type {number} + * @private + */ + this.scaleTransform_ = 1; + + /** + * Clipping size for clipping the margin controls. + * @type {print_preview.Size} + * @private + */ + this.clippingSize_ = null; + }; + + /** + * CSS classes used by the custom margins component. + * @enum {string} + * @private + */ + MarginControlContainer.Classes_ = { + DRAGGING_HORIZONTAL: 'margin-control-container-dragging-horizontal', + DRAGGING_VERTICAL: 'margin-control-container-dragging-vertical' + }; + + /** + * @param {print_preview.ticket_items.CustomMargins.Orientation} orientation + * Orientation value to test. + * @return {boolean} Whether the given orientation is TOP or BOTTOM. + * @private + */ + MarginControlContainer.isTopOrBottom_ = function(orientation) { + return orientation == + print_preview.ticket_items.CustomMargins.Orientation.TOP || + orientation == + print_preview.ticket_items.CustomMargins.Orientation.BOTTOM; + }; + + MarginControlContainer.prototype = { + __proto__: print_preview.Component.prototype, + + /** + * Updates the translation transformation that translates pixel values in + * the space of the HTML DOM. + * @param {print_preview.Coordinate2d} translateTransform Updated value of + * the translation transformation. + */ + updateTranslationTransform: function(translateTransform) { + if (!translateTransform.equals(this.translateTransform_)) { + this.translateTransform_ = translateTransform; + for (var orientation in this.controls_) { + this.controls_[orientation].setTranslateTransform(translateTransform); + } + } + }, + + /** + * Updates the scaling transform that scales pixels values to point values. + * @param {number} scaleTransform Updated value of the scale transform. + */ + updateScaleTransform: function(scaleTransform) { + if (scaleTransform != this.scaleTransform_) { + this.scaleTransform_ = scaleTransform; + for (var orientation in this.controls_) { + this.controls_[orientation].setScaleTransform(scaleTransform); + } + } + }, + + /** + * Clips margin controls to the given clip size in pixels. + * @param {print_preview.Size} Size to clip the margin controls to. + */ + updateClippingMask: function(clipSize) { + if (!clipSize) { + return; + } + this.clippingSize_ = clipSize; + for (var orientation in this.controls_) { + var el = this.controls_[orientation].getElement(); + el.style.clip = 'rect(' + + (-el.offsetTop) + 'px, ' + + (clipSize.width - el.offsetLeft) + 'px, ' + + (clipSize.height - el.offsetTop) + 'px, ' + + (-el.offsetLeft) + 'px)'; + } + }, + + /** Shows the margin controls if the need to be shown. */ + showMarginControlsIfNeeded: function() { + if (this.printTicketStore_.getMarginsType() == + print_preview.ticket_items.MarginsType.Value.CUSTOM) { + this.setIsMarginControlsVisible_(true); + } + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + + // We want to respond to mouse up events even beyond the component's + // element. + this.tracker.add(window, 'mouseup', this.onMouseUp_.bind(this)); + this.tracker.add(window, 'mousemove', this.onMouseMove_.bind(this)); + this.tracker.add( + this.getElement(), 'mouseover', this.onMouseOver_.bind(this)); + this.tracker.add( + this.getElement(), 'mouseout', this.onMouseOut_.bind(this)); + + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.INITIALIZE, + this.onTicketChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.TICKET_CHANGE, + this.onTicketChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE, + this.onTicketChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, + this.onTicketChange_.bind(this)); + + for (var orientation in this.controls_) { + this.tracker.add( + this.controls_[orientation], + print_preview.MarginControl.EventType.DRAG_START, + this.onControlDragStart_.bind(this, this.controls_[orientation])); + this.tracker.add( + this.controls_[orientation], + print_preview.MarginControl.EventType.TEXT_CHANGE, + this.onControlTextChange_.bind(this, this.controls_[orientation])); + } + }, + + /** @override */ + decorateInternal: function() { + for (var orientation in this.controls_) { + this.controls_[orientation].render(this.getElement()); + } + }, + + /** + * @param {boolean} isVisible Whether the margin controls are visible. + * @private + */ + setIsMarginControlsVisible_: function(isVisible) { + for (var orientation in this.controls_) { + this.controls_[orientation].setIsVisible(isVisible); + } + }, + + /** + * Moves the position of the given control to the desired position in + * pixels within some constraint minimum and maximum. + * @param {!print_preview.MarginControl} control Control to move. + * @param {!print_preview.Coordinate2d} posInPixels Desired position to move + * to in pixels. + * @private + */ + moveControlWithConstraints_: function(control, posInPixels) { + var newPosInPts; + if (MarginControlContainer.isTopOrBottom_(control.getOrientation())) { + newPosInPts = control.convertPixelsToPts(posInPixels.y); + } else { + newPosInPts = control.convertPixelsToPts(posInPixels.x); + } + newPosInPts = Math.min( + this.printTicketStore_.getCustomMarginMax(control.getOrientation()), + newPosInPts); + newPosInPts = Math.max(0, newPosInPts); + newPosInPts = Math.round(newPosInPts); + control.setPositionInPts(newPosInPts); + control.setTextboxValue(this.serializeValueFromPts_(newPosInPts)); + }, + + /** + * @param {string} value Value to parse to points. E.g. '3.40"' or '200mm'. + * @return {number} Value in points represented by the input value. + * @private + */ + parseValueToPts_: function(value) { + // Removing whitespace anywhere in the string. + value = value.replace(/\s*/g, ''); + if (value.length == 0) { + return null; + } + var validationRegex = new RegExp('^(^-?)(\\d)+(\\' + + this.measurementSystem_.thousandsDelimeter + '\\d{3})*(\\' + + this.measurementSystem_.decimalDelimeter + '\\d*)?' + + '(' + this.measurementSystem_.unitSymbol + ')?$'); + if (validationRegex.test(value)) { + // Replacing decimal point with the dot symbol in order to use + // parseFloat() properly. + var replacementRegex = + new RegExp('\\' + this.measurementSystem_.decimalDelimeter + '{1}'); + value = value.replace(replacementRegex, '.'); + return this.measurementSystem_.convertToPoints(parseFloat(value)); + } + return null; + }, + + /** + * @param {number} value Value in points to serialize. + * @return {string} String representation of the value in the system's local + * units. + * @private + */ + serializeValueFromPts_: function(value) { + value = this.measurementSystem_.convertFromPoints(value); + value = this.measurementSystem_.roundValue(value); + return value + this.measurementSystem_.unitSymbol; + }, + + /** + * Called when a margin control starts to drag. + * @param {print_preview.MarginControl} control The control which started to + * drag. + * @private + */ + onControlDragStart_: function(control) { + this.draggedControl_ = control; + this.getElement().classList.add( + MarginControlContainer.isTopOrBottom_(control.getOrientation()) ? + MarginControlContainer.Classes_.DRAGGING_VERTICAL : + MarginControlContainer.Classes_.DRAGGING_HORIZONTAL); + }, + + /** + * Called when the mouse moves in the custom margins component. Moves the + * dragged margin control. + * @param {MouseEvent} event Contains the position of the mouse. + * @private + */ + onMouseMove_: function(event) { + if (this.draggedControl_) { + this.moveControlWithConstraints_( + this.draggedControl_, + this.draggedControl_.translateMouseToPositionInPixels( + new print_preview.Coordinate2d(event.x, event.y))); + this.updateClippingMask(this.clippingSize_); + } + }, + + /** + * Called when the mouse is released in the custom margins component. + * Releases the dragged margin control. + * @param {MouseEvent} event Contains the position of the mouse. + * @private + */ + onMouseUp_: function(event) { + if (this.draggedControl_) { + this.getElement().classList.remove( + MarginControlContainer.Classes_.DRAGGING_VERTICAL); + this.getElement().classList.remove( + MarginControlContainer.Classes_.DRAGGING_HORIZONTAL); + if (event) { + var posInPixels = + this.draggedControl_.translateMouseToPositionInPixels( + new print_preview.Coordinate2d(event.x, event.y)); + this.moveControlWithConstraints_(this.draggedControl_, posInPixels); + } + this.updateClippingMask(this.clippingSize_); + this.printTicketStore_.updateCustomMargin( + this.draggedControl_.getOrientation(), + this.draggedControl_.getPositionInPts()); + this.draggedControl_ = null; + } + }, + + /** + * Called when the mouse moves onto the component. Shows the margin + * controls. + * @private + */ + onMouseOver_: function() { + var fromElement = event.fromElement; + while (fromElement != null) { + if (fromElement == this.getElement()) { + return; + } + fromElement = fromElement.parentElement; + } + if (this.printTicketStore_.hasMarginsCapability() && + this.printTicketStore_.getMarginsType() == + print_preview.ticket_items.MarginsType.Value.CUSTOM) { + this.setIsMarginControlsVisible_(true); + } + }, + + /** + * Called when the mouse moves off of the component. Hides the margin + * controls. + * @private + */ + onMouseOut_: function(event) { + var toElement = event.toElement; + while (toElement != null) { + if (toElement == this.getElement()) { + return; + } + toElement = toElement.parentElement; + } + if (this.draggedControl_ != null) { + return; + } + for (var orientation in this.controls_) { + if (this.controls_[orientation].getIsFocused() || + this.controls_[orientation].getIsInError()) { + return; + } + } + this.setIsMarginControlsVisible_(false); + }, + + /** + * Called when the print ticket changes. Updates the position of the margin + * controls. + * @private + */ + onTicketChange_: function() { + var margins = this.printTicketStore_.getCustomMargins(); + for (var orientation in this.controls_) { + var control = this.controls_[orientation]; + control.setPageSize(this.printTicketStore_.pageSize); + control.setTextboxValue( + this.serializeValueFromPts_(margins.get(orientation))); + control.setPositionInPts(margins.get(orientation)); + control.setIsInError(false); + control.setIsEnabled(true); + } + this.updateClippingMask(this.clippingSize_); + if (this.printTicketStore_.getMarginsType() != + print_preview.ticket_items.MarginsType.Value.CUSTOM) { + this.setIsMarginControlsVisible_(false); + } + }, + + /** + * Called when the text in a textbox of a margin control changes or the + * textbox loses focus. + * Updates the print ticket store. + * @param {!print_preview.MarginControl} control Updated control. + * @private + */ + onControlTextChange_: function(control) { + var marginValue = this.parseValueToPts_(control.getTextboxValue()); + if (marginValue != null) { + this.printTicketStore_.updateCustomMargin( + control.getOrientation(), marginValue); + } else { + var enableOtherControls; + if (!control.getIsFocused()) { + // If control no longer in focus, revert to previous valid value. + control.setTextboxValue( + this.serializeValueFromPts_(control.getPositionInPts())); + control.setIsInError(false); + enableOtherControls = true; + } else { + control.setIsInError(true); + enableOtherControls = false; + } + // Enable other controls. + for (var o in this.controls_) { + if (control.getOrientation() != o) { + this.controls_[o].setIsEnabled(enableOtherControls); + } + } + } + } + }; + + // Export + return { + MarginControlContainer: MarginControlContainer + }; +}); diff --git a/chrome/browser/resources/print_preview/previewarea/preview_area.css b/chrome/browser/resources/print_preview/previewarea/preview_area.css new file mode 100644 index 0000000..137f914 --- /dev/null +++ b/chrome/browser/resources/print_preview/previewarea/preview_area.css @@ -0,0 +1,75 @@ +/* Copyright (c) 2012 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. + */ + +#preview-area.preview-area { + -webkit-box-flex: 1; + -webkit-user-select: none; + background-color: #ccc; + height: 100%; + overflow: hidden; + position: relative; +} + +#preview-area .preview-area-plugin { + /* pluginFadeInTransitionDuration = 200ms */ + -webkit-transition: opacity 200ms linear; + /* pluginFadeInTransitionDelay = overlayFadeOutTransitionDuration = 100ms */ + -webkit-transition-delay: 100ms; + cursor: inherit; + height: 100%; + opacity: 1; + width: 100%; +} + +#preview-area .preview-area-plugin.invisible { + /* pluginFadeOutTransitionDuration = 100ms */ + -webkit-transition: opacity 100ms linear; + /* pluginFadeOutTransitionDelay = 250ms */ + -webkit-transition-delay: 250ms; + opacity: 0; +} + +#preview-area .preview-area-overlay-layer { + -webkit-transition: opacity 200ms linear; + /* overlayFadeInTransitionDelay = pluginFadeOutTransitionDelay + + * pluginFadeOutTransitionDuration = 350ms */ + -webkit-transition-delay: 350ms; + -webkit-user-select: none; + background: #ccc; + height: 100%; + margin: 0; + opacity: 1; + position: absolute; + width: 100%; + z-index: 1; +} + +#preview-area .preview-area-overlay-layer.invisible { + /* overlayFadeOutTransitionDuration = 100ms */ + -webkit-transition: opacity 100ms linear; + opacity: 0; + pointer-events: none; +} + +#preview-area .preview-area-messages { + height: 100%; +} + +#preview-area .preview-area-message { + color: #404040; + font-size: 1.1em; + position: relative; + text-align: center; + text-shadow: 0 1px 0 rgba(255, 255, 255, .5); + top: 50%; +} + +#preview-area .preview-area-no-plugin-action-area { + margin-top: 12px; +} + +#preview-area .preview-area-open-system-dialog-button-throbber { + vertical-align: middle; +} diff --git a/chrome/browser/resources/print_preview/previewarea/preview_area.html b/chrome/browser/resources/print_preview/previewarea/preview_area.html new file mode 100644 index 0000000..39b9d81 --- /dev/null +++ b/chrome/browser/resources/print_preview/previewarea/preview_area.html @@ -0,0 +1,41 @@ +<div id="preview-area" class="preview-area"> + <div class="preview-area-overlay-layer"> + <div class="preview-area-messages"> + + <div class="preview-area-loading-message preview-area-message"> + <span i18n-content="loading"></span> + <span class="preview-area-loading-message-jumping-dots jumping-dots" + ><span>.</span><span>.</span><span>.</span></span> + </div> + + <div class="preview-area-custom-message preview-area-message" + style="display: none;"> + <div class="preview-area-custom-message-text"></div> + <div class="preview-area-custom-action-area"> + <button class="preview-area-open-system-dialog-button" + i18n-content="launchNativeDialog"></button> + <div class="preview-area-open-system-dialog-button-throbber throbber" + style="display: none;"></div> + </div> + </div> + + <div class="preview-area-preview-failed-message preview-area-message" + i18n-content="previewFailed" + style="display: none;"></div> + + <div class="preview-area-print-failed preview-area-message" style="display: none;"> + <div i18n-content="invalidPrinterSettings"></div> + <div class="preview-area-print-failed-action-area"> + <button class="preview-area-open-system-dialog-button" + i18n-content="launchNativeDialog"></button> + <div class="preview-area-open-system-dialog-button-throbber throbber" + style="display: none;"></div> + </div> + </div> + + </div> + </div> + <object class="preview-area-compatibility-object" + type="application/x-google-chrome-print-preview-pdf" + data="chrome://print/dummy.pdf"></object> +</div> diff --git a/chrome/browser/resources/print_preview/previewarea/preview_area.js b/chrome/browser/resources/print_preview/previewarea/preview_area.js new file mode 100644 index 0000000..1ede80a --- /dev/null +++ b/chrome/browser/resources/print_preview/previewarea/preview_area.js @@ -0,0 +1,592 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Creates a PreviewArea object. It represents the area where the preview + * document is displayed. + * @param {!print_preview.DestinationStore} destinationStore Used to get the + * currently selected destination. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to get + * information about how the preview should be displayed. + * @param {!print_preview.NativeLayer} nativeLayer Needed to communicate with + * Chromium's preview generation system. + * @constructor + * @extends {print_preview.Component} + */ + function PreviewArea(destinationStore, printTicketStore, nativeLayer) { + print_preview.Component.call(this); + + /** + * Used to get the currently selected destination. + * @type {!print_preview.DestinationStore} + * @private + */ + this.destinationStore_ = destinationStore; + + /** + * Used to get information about how the preview should be displayed. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = printTicketStore; + + /** + * Used to contruct the preview generator. + * @type {!print_preview.NativeLayer} + * @private + */ + this.nativeLayer_ = nativeLayer; + + /** + * Used to read generated page previews. + * @type {print_preview.PreviewGenerator} + * @private + */ + this.previewGenerator_ = null; + + /** + * The embedded pdf plugin object. It's value is null if not yet loaded. + * @type {HTMLEmbedElement} + * @private + */ + this.plugin_ = null; + + /** + * Custom margins component superimposed on the preview plugin. + * @type {!print_preview.MarginControlContainer} + * @private + */ + this.marginControlContainer_ = + new print_preview.MarginControlContainer(this.printTicketStore_); + this.addChild(this.marginControlContainer_); + + /** + * Current zoom level as a percentage. + * @type {?number} + * @private + */ + this.zoomLevel_ = null; + + /** + * Current page offset which can be used to calculate scroll amount. + * @type {print_preview.Coordinate2d} + * @private + */ + this.pageOffset_ = null; + + /** + * Whether the plugin has finished reloading. + * @type {boolean} + * @private + */ + this.isPluginReloaded_ = false; + + /** + * Whether the document preview is ready. + * @type {boolean} + * @private + */ + this.isDocumentReady_ = false; + + /** + * Timeout object used to display a loading message if the preview is taking + * a long time to generate. + * @type {Object} + * @private + */ + this.loadingTimeout_ = null; + + /** + * Overlay element. + * @type {HTMLElement} + * @private + */ + this.overlayEl_ = null; + + /** + * The "Open system dialog" button. + * @type {HTMLButtonElement} + * @private + */ + this.openSystemDialogButton_ = null; + }; + + /** + * Event types dispatched by the preview area. + * @enum {string} + */ + PreviewArea.EventType = { + // Dispatched when the "Open system dialog" button is clicked. + OPEN_SYSTEM_DIALOG_CLICK: + 'print_preview.PreviewArea.OPEN_SYSTEM_DIALOG_CLICK', + + // Dispatched when the document preview is complete. + PREVIEW_GENERATION_DONE: + 'print_preview.PreviewArea.PREVIEW_GENERATION_DONE', + + // Dispatched when the document preview failed to be generated. + PREVIEW_GENERATION_FAIL: + 'print_preview.PreviewArea.PREVIEW_GENERATION_FAIL', + + // Dispatched when a new document preview is being generated. + PREVIEW_GENERATION_IN_PROGRESS: + 'print_preview.PreviewArea.PREVIEW_GENERATION_IN_PROGRESS' + }; + + /** + * CSS classes used by the preview area. + * @enum {string} + * @private + */ + PreviewArea.Classes_ = { + COMPATIBILITY_OBJECT: 'preview-area-compatibility-object', + CUSTOM_MESSAGE_TEXT: 'preview-area-custom-message-text', + MESSAGE: 'preview-area-message', + INVISIBLE: 'invisible', + OPEN_SYSTEM_DIALOG_BUTTON: 'preview-area-open-system-dialog-button', + OPEN_SYSTEM_DIALOG_BUTTON_THROBBER: + 'preview-area-open-system-dialog-button-throbber', + OVERLAY: 'preview-area-overlay-layer', + PDF_PLUGIN: 'preview-area-pdf-plugin' + }; + + /** + * Enumeration of IDs shown in the preview area. + * @enum {string} + * @private + */ + PreviewArea.MessageId_ = { + CUSTOM: 'custom', + LOADING: 'loading', + PREVIEW_FAILED: 'preview-failed' + }; + + /** + * Maps message IDs to the CSS class that contains them. + * @type {object.<PreviewArea.MessageId_, string>} + * @private + */ + PreviewArea.MessageIdClassMap_ = {}; + PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.CUSTOM] = + 'preview-area-custom-message'; + PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.LOADING] = + 'preview-area-loading-message'; + PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.PREVIEW_FAILED] = + 'preview-area-preview-failed-message'; + + /** + * Amount of time in milliseconds to wait after issueing a new preview before + * the loading message is shown. + * @type {number} + * @const + * @private + */ + PreviewArea.LOADING_TIMEOUT_ = 200; + + PreviewArea.prototype = { + __proto__: print_preview.Component.prototype, + + /** + * Should only be called after calling this.render(). + * @return {boolean} Whether the preview area has a compatible plugin to + * display the print preview in. + */ + get hasCompatiblePlugin() { + return this.previewGenerator_ != null; + }, + + /** + * Processes a keyboard event that could possibly be used to change state of + * the preview plugin. + * @param {MouseEvent} e Mouse event to process. + */ + handleDirectionalKeyEvent: function(e) { + // Make sure the PDF plugin is there. + // We only care about: PageUp, PageDown, Left, Up, Right, Down. + // If the user is holding a modifier key, ignore. + if (!this.plugin_ || + !arrayContains([33, 34, 37, 38, 39, 40], e.keyCode) || + e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) { + return; + } + + // Don't handle the key event for these elements. + var tagName = document.activeElement.tagName; + if (arrayContains(['INPUT', 'SELECT', 'EMBED'], tagName)) { + return; + } + + // For the most part, if any div of header was the last clicked element, + // then the active element is the body. Starting with the last clicked + // element, and work up the DOM tree to see if any element has a + // scrollbar. If there exists a scrollbar, do not handle the key event + // here. + var element = e.target; + while (element) { + if (element.scrollHeight > element.clientHeight || + element.scrollWidth > element.clientWidth) { + return; + } + element = element.parentElement; + } + + // No scroll bar anywhere, or the active element is something else, like a + // button. Note: buttons have a bigger scrollHeight than clientHeight. + this.plugin_.sendKeyEvent(e.keyCode); + e.preventDefault(); + }, + + /** + * Shows a custom message on the preview area's overlay. + * @param {string} message Custom message to show. + */ + showCustomMessage: function(message) { + this.showMessage_(PreviewArea.MessageId_.CUSTOM, message); + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + this.tracker.add( + this.openSystemDialogButton_, + 'click', + this.onOpenSystemDialogButtonClick_.bind(this)); + + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.INITIALIZE, + this.onTicketChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.TICKET_CHANGE, + this.onTicketChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, + this.onTicketChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE, + this.onTicketChange_.bind(this)); + + if (this.checkPluginCompatibility_()) { + this.previewGenerator_ = new print_preview.PreviewGenerator( + this.destinationStore_, this.printTicketStore_, this.nativeLayer_); + this.tracker.add( + this.previewGenerator_, + print_preview.PreviewGenerator.EventType.PREVIEW_START, + this.onPreviewStart_.bind(this)); + this.tracker.add( + this.previewGenerator_, + print_preview.PreviewGenerator.EventType.PAGE_READY, + this.onPagePreviewReady_.bind(this)); + this.tracker.add( + this.previewGenerator_, + print_preview.PreviewGenerator.EventType.FAIL, + this.onPreviewGenerationFail_.bind(this)); + this.tracker.add( + this.previewGenerator_, + print_preview.PreviewGenerator.EventType.DOCUMENT_READY, + this.onDocumentReady_.bind(this)); + } else { + this.showCustomMessage(localStrings.getString('noPlugin')); + } + }, + + /** @override */ + exitDocument: function() { + print_preview.Component.prototype.exitDocument.call(this); + if (this.previewGenerator_) { + this.previewGenerator_.removeEventListeners(); + } + this.overlayEl_ = null; + this.openSystemDialogButton_ = null; + }, + + /** @override */ + decorateInternal: function() { + this.marginControlContainer_.decorate(this.getElement()); + this.overlayEl_ = this.getElement().getElementsByClassName( + PreviewArea.Classes_.OVERLAY)[0]; + this.openSystemDialogButton_ = this.getElement().getElementsByClassName( + PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON)[0]; + }, + + /** + * Checks to see if a suitable plugin for rendering the preview exists. If + * one does not exist, then an error message will be displayed. + * @return {boolean} Whether Chromium has a suitable plugin for rendering + * the preview. + * @private + */ + checkPluginCompatibility_: function() { + var compatObj = this.getElement().getElementsByClassName( + PreviewArea.Classes_.COMPATIBILITY_OBJECT)[0]; + var isCompatible = + compatObj.onload && + compatObj.goToPage && + compatObj.removePrintButton && + compatObj.loadPreviewPage && + compatObj.printPreviewPageCount && + compatObj.resetPrintPreviewUrl && + compatObj.onPluginSizeChanged && + compatObj.onScroll && + compatObj.pageXOffset && + compatObj.pageYOffset && + compatObj.setZoomLevel && + compatObj.setPageNumbers && + compatObj.setPageXOffset && + compatObj.setPageYOffset && + compatObj.getHorizontalScrollbarThickness && + compatObj.getVerticalScrollbarThickness && + compatObj.getPageLocationNormalized && + compatObj.getHeight && + compatObj.getWidth; + compatObj.parentElement.removeChild(compatObj); + return isCompatible; + }, + + /** + * Shows a given message on the overlay. + * @param {print_preview.PreviewArea.MessageId_} messageId ID of the message + * to show. + * @param {string=} opt_message Optional message to show that can be used + * by some message IDs. + * @private + */ + showMessage_: function(messageId, opt_message) { + // Hide all messages. + var messageEls = this.getElement().getElementsByClassName( + PreviewArea.Classes_.MESSAGE); + for (var i = 0, messageEl; messageEl = messageEls[i]; i++) { + setIsVisible(messageEl, false); + } + // Disable jumping animation to conserve cycles. + var jumpingDotsEl = this.getElement().querySelector( + '.preview-area-loading-message-jumping-dots'); + jumpingDotsEl.classList.remove('jumping-dots'); + + // Show specific message. + if (messageId == PreviewArea.MessageId_.CUSTOM) { + var customMessageTextEl = this.getElement().getElementsByClassName( + PreviewArea.Classes_.CUSTOM_MESSAGE_TEXT)[0]; + customMessageTextEl.textContent = opt_message; + } else if (messageId == PreviewArea.MessageId_.LOADING) { + jumpingDotsEl.classList.add('jumping-dots'); + } + var messageEl = this.getElement().getElementsByClassName( + PreviewArea.MessageIdClassMap_[messageId])[0]; + setIsVisible(messageEl, true); + + // Show overlay. + this.overlayEl_.classList.remove(PreviewArea.Classes_.INVISIBLE); + }, + + /** + * Hides the message overlay. + * @private + */ + hideOverlay_: function() { + this.overlayEl_.classList.add(PreviewArea.Classes_.INVISIBLE); + // Disable jumping animation to conserve cycles. + var jumpingDotsEl = this.getElement().querySelector( + '.preview-area-loading-message-jumping-dots'); + jumpingDotsEl.classList.remove('jumping-dots'); + }, + + /** + * Creates a preview plugin and adds it to the DOM. + * @param {string} srcUrl Initial URL of the plugin. + * @private + */ + createPlugin_: function(srcUrl) { + if (this.plugin_) { + console.warn('Pdf preview plugin already created'); + return; + } + this.plugin_ = document.createElement('embed'); + // NOTE: The plugin's 'id' field must be set to 'pdf-viewer' since + // chrome/renderer/print_web_view_helper.cc actually references it. + this.plugin_.setAttribute('id', 'pdf-viewer'); + this.plugin_.setAttribute('class', 'preview-area-plugin'); + this.plugin_.setAttribute( + 'type', 'application/x-google-chrome-print-preview-pdf'); + this.plugin_.setAttribute('src', srcUrl); + this.plugin_.setAttribute('aria-live', 'polite'); + this.plugin_.setAttribute('aria-atomic', 'true'); + this.getElement().appendChild(this.plugin_); + + global['onPreviewPluginLoad'] = this.onPluginLoad_.bind(this); + this.plugin_.onload('onPreviewPluginLoad()'); + + global['onPreviewPluginVisualStateChange'] = + this.onPreviewVisualStateChange_.bind(this); + this.plugin_.onScroll('onPreviewPluginVisualStateChange()'); + this.plugin_.onPluginSizeChanged('onPreviewPluginVisualStateChange()'); + + this.plugin_.removePrintButton(); + this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled()); + }, + + /** + * Dispatches a PREVIEW_GENERATION_DONE event if all conditions are met. + * @private + */ + dispatchPreviewGenerationDoneIfReady_: function() { + if (this.isDocumentReady_ && this.isPluginReloaded_) { + cr.dispatchSimpleEvent( + this, PreviewArea.EventType.PREVIEW_GENERATION_DONE); + this.marginControlContainer_.showMarginControlsIfNeeded(); + } + }, + + /** + * Called when the open-system-dialog button is clicked. Disables the + * button, shows the throbber, and dispatches the OPEN_SYSTEM_DIALOG_CLICK + * event. + * @private + */ + onOpenSystemDialogButtonClick_: function() { + this.openSystemDialogButton_.disabled = true; + var openSystemDialogThrobber = this.getElement().getElementsByClassName( + PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON_THROBBER)[0]; + setIsVisible(openSystemDialogThrobber, true); + cr.dispatchSimpleEvent( + this, PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK); + }, + + /** + * Called when the print ticket changes. Updates the preview. + * @private + */ + onTicketChange_: function() { + if (this.previewGenerator_ && this.previewGenerator_.requestPreview()) { + if (this.loadingTimeout_ == null) { + this.loadingTimeout_ = setTimeout( + this.showMessage_.bind(this, PreviewArea.MessageId_.LOADING), + PreviewArea.LOADING_TIMEOUT_); + } + } else { + this.marginControlContainer_.showMarginControlsIfNeeded(); + } + }, + + /** + * Called when the preview generator begins loading the preview. + * @param {cr.Event} Contains the URL to initialize the plugin to. + * @private + */ + onPreviewStart_: function(event) { + this.isDocumentReady_ = false; + this.isPluginReloaded_ = false; + if (!this.plugin_) { + this.createPlugin_(event.previewUrl); + } + this.plugin_.goToPage('0'); + this.plugin_.resetPrintPreviewUrl(event.previewUrl); + this.plugin_.reload(); + this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled()); + cr.dispatchSimpleEvent( + this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS); + }, + + /** + * Called when a page preview has been generated. Updates the plugin with + * the new page. + * @param {cr.Event} event Contains information about the page preview. + * @private + */ + onPagePreviewReady_: function(event) { + this.plugin_.loadPreviewPage(event.previewUrl, event.previewIndex); + }, + + /** + * Called when the preview generation is complete and the document is ready + * to print. + * @private + */ + onDocumentReady_: function(event) { + this.isDocumentReady_ = true; + this.dispatchPreviewGenerationDoneIfReady_(); + }, + + /** + * Called when the generation of a preview fails. Shows an error message. + * @private + */ + onPreviewGenerationFail_: function() { + this.showMessage_(PreviewArea.MessageId_.PREVIEW_FAILED); + cr.dispatchSimpleEvent( + this, PreviewArea.EventType.PREVIEW_GENERATION_FAIL); + }, + + /** + * Called when the plugin loads. This is a consequence of calling + * plugin.reload(). Certain plugin state can only be set after the plugin + * has loaded. + * @private + */ + onPluginLoad_: function() { + if (this.loadingTimeout_) { + clearTimeout(this.loadingTimeout_); + this.loadingTimeout_ = null; + } + // Setting the plugin's page count can only be called after the plugin is + // loaded and the document must be modifiable. + if (this.printTicketStore_.isDocumentModifiable) { + this.plugin_.printPreviewPageCount( + this.printTicketStore_.getPageNumberSet().size); + } + this.plugin_.setPageNumbers(JSON.stringify( + this.printTicketStore_.getPageNumberSet().asArray())); + if (this.zoomLevel_ != null && this.pageOffset_ != null) { + this.plugin_.setZoomLevel(this.zoomLevel_); + this.plugin_.setPageXOffset(this.pageOffset_.x); + this.plugin_.setPageYOffset(this.pageOffset_.y); + } else { + this.plugin_.fitToHeight(); + } + this.hideOverlay_(); + this.isPluginReloaded_ = true; + this.dispatchPreviewGenerationDoneIfReady_(); + }, + + /** + * Called when the preview plugin's visual state has changed. This is a + * consequence of scrolling or zooming the plugin. Updates the custom + * margins component if shown. + * @private + */ + onPreviewVisualStateChange_: function() { + if (this.isPluginReloaded_) { + this.zoomLevel_ = this.plugin_.getZoomLevel(); + this.pageOffset_ = new print_preview.Coordinate2d( + this.plugin_.pageXOffset(), this.plugin_.pageYOffset()); + } + var normalized = this.plugin_.getPageLocationNormalized().split(';'); + var pluginWidth = this.plugin_.getWidth(); + var pluginHeight = this.plugin_.getHeight(); + var translationTransform = new print_preview.Coordinate2d( + parseFloat(normalized[0]) * pluginWidth, + parseFloat(normalized[1]) * pluginHeight); + this.marginControlContainer_.updateTranslationTransform( + translationTransform); + var pageWidthInPixels = parseFloat(normalized[2]) * pluginWidth; + this.marginControlContainer_.updateScaleTransform( + pageWidthInPixels / this.printTicketStore_.pageSize.width); + this.marginControlContainer_.updateClippingMask( + new print_preview.Size( + pluginWidth - this.plugin_.getVerticalScrollbarThickness(), + pluginHeight - this.plugin_.getHorizontalScrollbarThickness())); + } + }; + + // Export + return { + PreviewArea: PreviewArea + }; +}); diff --git a/chrome/browser/resources/print_preview/print_header.css b/chrome/browser/resources/print_preview/print_header.css new file mode 100644 index 0000000..dca74d3a --- /dev/null +++ b/chrome/browser/resources/print_preview/print_header.css @@ -0,0 +1,15 @@ +/* Copyright (c) 2012 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. + */ + +.print-header { + padding-bottom: 10px; + padding-top: 10px; +} + +.print-header-summary { + color: rgb(83, 99, 125); + display: block; + min-height: 30px; +} diff --git a/chrome/browser/resources/print_preview/print_header.html b/chrome/browser/resources/print_preview/print_header.html new file mode 100644 index 0000000..f0c3aeb --- /dev/null +++ b/chrome/browser/resources/print_preview/print_header.html @@ -0,0 +1,10 @@ +<div id="print-header" class="print-header"> + <span class="print-header-summary"></span> + <div class="button-strip"> + <button class="print-header-print-button default" + i18n-content="printButton" + disabled></button> + <button class="print-header-cancel-button" + i18n-content="cancelButton"></button> + </div> +</div> diff --git a/chrome/browser/resources/print_preview/print_header.js b/chrome/browser/resources/print_preview/print_header.js index 6a74d04..2d93429 100644 --- a/chrome/browser/resources/print_preview/print_header.js +++ b/chrome/browser/resources/print_preview/print_header.js @@ -8,176 +8,232 @@ cr.define('print_preview', function() { /** * Creates a PrintHeader object. This object encapsulates all the elements * and logic related to the top part of the left pane in print_preview.html. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to read + * information about the document. + * @param {!print_preview.DestinationStore} destinationStore Used to get the + * selected destination. * @constructor + * @extends {print_preview.Component} */ - function PrintHeader() { - this.printButton_ = $('print-button'); - this.cancelButton_ = $('cancel-button'); - this.summary_ = $('print-summary'); - this.printButton_.focus(); - this.addEventListeners_(); - } - - cr.addSingletonGetter(PrintHeader); - - PrintHeader.prototype = { - get printButton() { - return this.printButton_; - }, - - get cancelButton() { - return this.cancelButton_; - }, - - get summary() { - return this.summary_; - }, + function PrintHeader(printTicketStore, destinationStore) { + print_preview.Component.call(this); /** - * Adding event listeners where necessary. Listeners take care of changing - * their behavior depending on the current state, no need to remove them. + * Used to read information about the document. + * @type {!print_preview.PrintTicketStore} * @private */ - addEventListeners_: function() { - this.cancelButton_.onclick = function() { - this.disableCancelButton(); - closePrintPreviewTab(); - }.bind(this); - this.printButton_.onclick = this.onPrintRequested.bind(this); - document.addEventListener(customEvents.UPDATE_SUMMARY, - this.updateSummary_.bind(this)); - document.addEventListener(customEvents.UPDATE_PRINT_BUTTON, - this.updatePrintButton_.bind(this)); - document.addEventListener(customEvents.PDF_GENERATION_ERROR, - this.onPDFGenerationError_.bind(this)); - document.addEventListener(customEvents.PRINTER_CAPABILITIES_UPDATED, - this.onPrinterCapabilitiesUpdated_.bind(this)); - }, + this.printTicketStore_ = printTicketStore; /** - * Enables the cancel button and attaches its keydown event listener. + * Used to get the selected destination. + * @type {!print_preview.DestinationStore} + * @private */ - enableCancelButton: function() { - window.onkeydown = onKeyDown; - this.cancelButton_.disabled = false; - }, + this.destinationStore_ = destinationStore; /** - * Executes when a |customEvents.PDF_GENERATION_ERROR| event occurs. + * Whether the component is enabled. + * @type {boolean} * @private */ - onPDFGenerationError_: function() { - this.printButton_.disabled = true; + this.isEnabled_ = true; + }; + + /** + * Event types dispatched by the print header. + * @enum {string} + */ + PrintHeader.EventType = { + PRINT_BUTTON_CLICK: 'print_preview.PrintHeader.PRINT_BUTTON_CLICK', + CANCEL_BUTTON_CLICK: 'print_preview.PrintHeader.CANCEL_BUTTON_CLICK' + }, + + /** + * CSS classes used by the print header. + * @enum {string} + * @private + */ + PrintHeader.Classes_ = { + CANCEL_BUTTON: 'print-header-cancel-button', + PRINT_BUTTON: 'print-header-print-button', + SUMMARY: 'print-header-summary' + }; + + PrintHeader.prototype = { + __proto__: print_preview.Component.prototype, + + set isEnabled(isEnabled) { + this.isEnabled_ = isEnabled; + this.printButton_.disabled = !isEnabled; + this.cancelButton_.disabled = !isEnabled; }, - /** - * Executes when a |customEvents.PRINTER_CAPABILITIES_UPDATED| event occurs. - * @private - */ - onPrinterCapabilitiesUpdated_: function() { - getSelectedPrinterName() == PRINT_TO_PDF ? - this.printButton.textContent = localStrings.getString('saveButton') : - this.printButton.textContent = localStrings.getString('printButton'); + setErrorMessage: function(message) { + var summaryEl = this.getElement().getElementsByClassName( + PrintHeader.Classes_.SUMMARY)[0]; + summaryEl.innerHTML = ''; + summaryEl.textContent = message; }, - /** - * Disables the cancel button and removes its keydown event listener. - */ - disableCancelButton: function() { - window.onkeydown = null; - this.cancelButton_.disabled = true; + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + this.printButton_.focus(); + + // User events + this.tracker.add( + this.cancelButton_, 'click', this.onCancelButtonClick_.bind(this)); + this.tracker.add( + this.printButton_, 'click', this.onPrintButtonClick_.bind(this)); + + // Data events. + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.INITIALIZE, + this.onTicketChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE, + this.onTicketChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.TICKET_CHANGE, + this.onTicketChange_.bind(this)); + this.tracker.add( + this.destinationStore_, + print_preview.DestinationStore.EventType.DESTINATION_SELECT, + this.onDestinationSelect_.bind(this)); }, /** - * Listener executing whenever the print button is clicked or user presses - * the enter button while focus is in the pages field. + * @return {Element} Print button element. + * @private */ - onPrintRequested: function() { - var printToPDF = getSelectedPrinterName() == PRINT_TO_PDF; - if (!printToPDF) { - this.printButton_.classList.add('loading'); - this.cancelButton_.classList.add('loading'); - this.summary_.innerHTML = localStrings.getString('printing'); - } - this.disableCancelButton(); - requestToPrintDocument(); + get printButton_() { + return this.getElement().getElementsByClassName( + PrintHeader.Classes_.PRINT_BUTTON)[0]; }, /** - * Updates the state of |this.printButton_| depending on the user selection. - * The button is enabled only when the following conditions are true. - * 1) The selected page ranges are valid. - * 2) The number of copies is valid (if applicable). + * @return {Element} Cancel button element. * @private */ - updatePrintButton_: function() { - if (showingSystemDialog) - return; - this.printButton_.disabled = !areSettingsValid(); + get cancelButton_() { + return this.getElement().getElementsByClassName( + PrintHeader.Classes_.CANCEL_BUTTON)[0]; }, /** - * Updates |this.summary_| based on the currently selected user options. + * Updates the summary element based on the currently selected user options. * @private */ updateSummary_: function() { - var printToPDF = getSelectedPrinterName() == PRINT_TO_PDF; - var copies = printToPDF ? 1 : copiesSettings.numberOfCopies; - - if ((!printToPDF && !copiesSettings.isValid()) || - !pageSettings.isPageSelectionValid()) { - this.summary_.innerHTML = ''; + var summaryEl = this.getElement().getElementsByClassName( + PrintHeader.Classes_.SUMMARY)[0]; + if (!this.printTicketStore_.isTicketValid()) { + summaryEl.innerHTML = ''; return; } - if (!marginSettings.areMarginSettingsValid()) { - this.summary_.innerHTML = ''; - return; - } - - var pageSet = pageSettings.selectedPagesSet; - var numOfSheets = pageSet.length; - if (numOfSheets == 0) - return; - var summaryLabel = localStrings.getString('printPreviewSheetsLabelSingular'); - var numOfPagesText = ''; var pagesLabel = localStrings.getString('printPreviewPageLabelPlural'); - if (printToPDF) + var saveToPdf = this.destinationStore_.selectedDestination && + this.destinationStore_.selectedDestination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF; + if (saveToPdf) { summaryLabel = localStrings.getString('printPreviewPageLabelSingular'); + } - if (!printToPDF && - copiesSettings.duplexMode == print_preview.CopiesSettings.LONG_EDGE) { - numOfSheets = Math.ceil(numOfSheets / 2); + var numPages = this.printTicketStore_.getPageNumberSet().size; + var numSheets = numPages; + if (!saveToPdf && this.printTicketStore_.isDuplexEnabled()) { + numSheets = Math.ceil(numPages / 2); } - numOfSheets *= copies; - if (numOfSheets > 1) { - summaryLabel = printToPDF ? pagesLabel : + var copies = this.printTicketStore_.getCopies(); + numSheets *= copies; + numPages *= copies; + + if (numSheets > 1) { + summaryLabel = saveToPdf ? pagesLabel : localStrings.getString('printPreviewSheetsLabelPlural'); } - var html = ''; - if (pageSet.length * copies != numOfSheets) { - numOfPagesText = pageSet.length * copies; + var html; + if (numPages != numSheets) { html = localStrings.getStringF('printPreviewSummaryFormatLong', - '<b>' + numOfSheets + '</b>', + '<b>' + numSheets + '</b>', '<b>' + summaryLabel + '</b>', - numOfPagesText, pagesLabel); + numPages, + pagesLabel); } else { html = localStrings.getStringF('printPreviewSummaryFormatShort', - '<b>' + numOfSheets + '</b>', + '<b>' + numSheets + '</b>', '<b>' + summaryLabel + '</b>'); } // Removing extra spaces from within the string. html = html.replace(/\s{2,}/g, ' '); - this.summary_.innerHTML = html; + summaryEl.innerHTML = html; + }, + + /** + * Called when the print button is clicked. Dispatches a PRINT_DOCUMENT + * common event. + * @private + */ + onPrintButtonClick_: function() { + if (this.destinationStore_.selectedDestination.id != + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) { + this.printButton_.classList.add('loading'); + this.cancelButton_.classList.add('loading'); + var summaryEl = this.getElement().getElementsByClassName( + PrintHeader.Classes_.SUMMARY)[0]; + summaryEl.innerHTML = localStrings.getString('printing'); + } + cr.dispatchSimpleEvent(this, PrintHeader.EventType.PRINT_BUTTON_CLICK); + }, + + /** + * Called when the cancel button is clicked. Dispatches a + * CLOSE_PRINT_PREVIEW event. + * @private + */ + onCancelButtonClick_: function() { + cr.dispatchSimpleEvent(this, PrintHeader.EventType.CANCEL_BUTTON_CLICK); + }, + + /** + * Called when a new destination is selected. Updates the text on the print + * button. + * @private + */ + onDestinationSelect_: function() { + if (this.destinationStore_.selectedDestination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) { + this.printButton_.textContent = localStrings.getString('saveButton'); + } else { + this.printButton_.textContent = localStrings.getString('printButton'); + } + }, + + /** + * Called when the print ticket has changed. Disables the print button if + * any of the settings are invalid. + * @private + */ + onTicketChange_: function() { + this.printButton_.disabled = + !this.printTicketStore_.isTicketValid() || + !this.isEnabled_; + this.updateSummary_(); } }; + // Export return { PrintHeader: PrintHeader }; diff --git a/chrome/browser/resources/print_preview/print_preview.css b/chrome/browser/resources/print_preview/print_preview.css index b484c2a..1c91d66 100644 --- a/chrome/browser/resources/print_preview/print_preview.css +++ b/chrome/browser/resources/print_preview/print_preview.css @@ -7,9 +7,9 @@ html { } body { + display: -webkit-box; height: 100%; margin: 0; - overflow: hidden; } /* Header */ @@ -17,20 +17,16 @@ body { header { -webkit-padding-end: 14px; -webkit-padding-start: 16px; + background-color: #F1F1F1; } #print-preview #navbar-container { -webkit-border-end: 1px solid rgb(198, 201, 206); -webkit-box-orient: vertical; -webkit-user-select: none; - background-color: #f1f1f1; - bottom: 0; + background-color: white; display: -webkit-box; - /* We set both left and right for the sake of RTL. */ - left: 0; - position: fixed; - right: 0; - top: 0; + position: relative; width: 310px; z-index: 2; } @@ -45,25 +41,14 @@ header { text-shadow: white 0 1px 2px; } -#print-header { - padding-bottom: 10px; - padding-top: 10px; -} - -#print-summary { - color: rgb(83, 99, 125); - display: block; - min-height: 30px; -} - /* Settings */ #settings { - -webkit-box-flex: 1; -webkit-box-shadow: inset 0 2px 2px rgba(0, 0, 0, .3); background: white; overflow-y: auto; padding-top: 2px; + width: 100%; } .two-column { @@ -87,10 +72,10 @@ header { } .two-column h1 { + -webkit-padding-end: 16px; -webkit-padding-start: 16px; display: table-cell; font-size: 1.1em; - width: 86px; } .two-column.visible h1, @@ -117,19 +102,20 @@ h1 { font-weight: 300; } -.preview-link-button { - -webkit-padding-start: 16px; +#print-preview .navbar-link { + -webkit-margin-start: 16px; + margin-top: 10px; outline: 0; - padding-top: 10px; + padding: 0; text-align: start; text-decoration: none; } -.preview-link-button:hover:not(:disabled) { +#print-preview .navbar-link:hover:not(:disabled) { text-decoration: underline; } -.preview-link-button:disabled { +#print-preview .navbar-link:disabled { color: rgba(0, 0, 0, .5); cursor: default; text-shadow: none; @@ -241,150 +227,12 @@ label { /* Individual settings sections */ -#print-pages-div { - -webkit-box-align: center; - -webkit-box-orient: horizontal; - display: -webkit-box; -} - -#individual-pages { - -webkit-box-flex: 1; - -webkit-margin-start: 5px; - display: block; -} - -#collate-option { - -webkit-padding-start: 16px; - display: inline-block; -} - -#copies { - position: relative; - width: 2.75em; -} - -#copies.invalid { - background: rgb(255, 240, 240); - color: rgb(140, 20, 20); -} - -#increment, -#decrement { - -webkit-padding-end: 0; - -webkit-padding-start: 0; - font-weight: 600; - margin: 0; - min-width: 0; - position: relative; - width: 2em; -} - -#increment:focus, -#decrement:focus, -#copies:focus { - z-index: 1; -} - -#increment { - -webkit-margin-start: -5px; - border-radius: 0; -} - -#decrement { - -webkit-margin-start: -5px; - border-bottom-left-radius: 0; - border-bottom-right-radius: 3px; - border-top-left-radius: 0; - border-top-right-radius: 3px; -} - -html[dir='rtl'] #decrement { - border-bottom-left-radius: 3px; - border-bottom-right-radius: 0; - border-top-left-radius: 3px; - border-top-right-radius: 0; -} - -#system-dialog-link { - -webkit-margin-start: 16px; - margin-top: 10px; - padding: 0; -} - -/* PDF view */ - -#mainview { - -webkit-margin-start: 310px; - -webkit-padding-start: 0; - -webkit-user-select: none; - background-color: #ccc; - bottom: 0; - left: 0; - overflow: hidden; - position: absolute; - right: 0; - top: 0; - z-index: 1; -} - -#pdf-viewer { - /* pluginFadeInTransitionDuration = 200ms */ - -webkit-transition: opacity 200ms linear; - /* pluginFadeInTransitionDelay = overlayFadeOutTransitionDuration = 100ms */ - -webkit-transition-delay: 100ms; - height: 100%; - opacity: 1; - width: 100%; -} - -#pdf-viewer.invisible { - /* pluginFadeOutTransitionDuration = 100ms */ - -webkit-transition: opacity 100ms linear; - /* pluginFadeOutTransitionDelay = 250ms */ - -webkit-transition-delay: 250ms; - opacity: 0; -} - -#no-plugin { - padding: 20px; -} - /* TODO(estade): this should be in a shared location but I'm afraid of the * damage it could do. */ [hidden] { display: none !important; } -#overlay-layer { - -webkit-transition: opacity 200ms linear; - /* overlayFadeInTransitionDelay = pluginFadeOutTransitionDelay + - * pluginFadeOutTransitionDuration = 350ms */ - -webkit-transition-delay: 350ms; - -webkit-user-select: none; - background: #ccc; - height: 100%; - margin: 0; - opacity: 1; - position: absolute; - width: 100%; -} - -#overlay-layer.invisible { - /* overlayFadeOutTransitionDuration = 100ms */ - -webkit-transition: opacity 100ms linear; - opacity: 0; - pointer-events: none; -} - -#messages { - color: #404040; - font-size: 1.1em; - position: relative; - text-align: center; - text-shadow: 0 1px 0 rgba(255, 255, 255, .5); - top: 50%; -} - @-webkit-keyframes dancing-dots-jump { 0% { top: 0; } 55% { top: 0; } @@ -395,28 +243,20 @@ html[dir='rtl'] #decrement { 100% { top: 0; } } -#loading { - -webkit-margin-end: -3px; -} - -.message-with-dots span span { +span.jumping-dots > span { -webkit-animation: dancing-dots-jump 1800ms infinite; padding: 1px; position: relative; } -.message-with-dots span span:nth-child(2) { +span.jumping-dots > span:nth-child(2) { -webkit-animation-delay: 100ms; } -.message-with-dots span span:nth-child(3) { +span.jumping-dots > span:nth-child(3) { -webkit-animation-delay: 300ms; } -#error-action-area { - margin-top: 10px; -} - /* TODO(estade): unfork this code. */ .button-strip { <if expr="not pp_ifdef('toolkit_views')"> @@ -431,3 +271,7 @@ html[dir='rtl'] #decrement { -webkit-margin-start: 4px; display: block; } + +#link-container { + -webkit-box-flex: 1; +} diff --git a/chrome/browser/resources/print_preview/print_preview.html b/chrome/browser/resources/print_preview/print_preview.html index 1090875..58bfce5 100644 --- a/chrome/browser/resources/print_preview/print_preview.html +++ b/chrome/browser/resources/print_preview/print_preview.html @@ -1,92 +1,77 @@ <!DOCTYPE html> <html i18n-values="dir:textdirection;" id="print-preview"> + <head> -<meta charset="utf-8"> -<title i18n-content="title"></title> -<link rel="icon" href="../../../app/theme/print_preview_favicon.png"> + <meta charset="utf-8"/> + <title i18n-content="title"></title> + <link rel="icon" href="../../../app/theme/print_preview_favicon.png"> -<link rel="stylesheet" href="margins.css"> -<link rel="stylesheet" href="print_preview.css"> -<link rel="stylesheet" href="../shared/css/chrome_shared2.css"> -<link rel="stylesheet" href="../shared/css/throbber.css"> + <link rel="stylesheet" href="print_preview.css"/> + <link rel="stylesheet" href="../shared/css/chrome_shared2.css"/> + <link rel="stylesheet" href="../shared/css/throbber.css"/> + <link rel="stylesheet" href="../shared/css/widgets.css"/> + <link rel="stylesheet" href="print_header.css"/> + <link rel="stylesheet" href="settings/copies_settings.css"/> + <link rel="stylesheet" href="settings/page_settings.css"/> + <link rel="stylesheet" href="previewarea/preview_area.css"/> + <link rel="stylesheet" href="previewarea/margin_control_container.css"/> + <link rel="stylesheet" href="previewarea/margin_control.css"/> + <link rel="stylesheet" href="../shared/css/overlay.css"/> -<script src="chrome://resources/js/cr.js"></script> -<script src="chrome://resources/js/event_tracker.js"></script> -<script src="chrome://resources/js/local_strings.js"></script> -<script src="chrome://resources/js/util.js"></script> -<script src="chrome://print/print_preview.js"></script> -<script src="chrome://print/strings.js"></script> + <script src="chrome://resources/js/cr.js"></script> + <script src="chrome://resources/js/cr/event_target.js"></script> + <script src="chrome://resources/js/event_tracker.js"></script> + <script src="chrome://resources/js/local_strings.js"></script> + <script src="chrome://resources/js/util.js"></script> + <script src="chrome://print/print_preview.js"></script> + <script src="chrome://print/strings.js"></script> + <script src="chrome://resources/js/i18n_template.js"></script> </head> + <body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize"> <div id="navbar-container"> <header> <h1 id="navbar-content-title" i18n-content="title"></h1> - <div id="print-header"> - <span id="print-summary"></span> - <div class="button-strip"> - <button id="print-button" i18n-content="printButton" class="default"> - </button> - <button id="cancel-button" i18n-content="cancelButton"></button> - </div> - </div> + <include src="print_header.html"/> </header> <div id="settings"> - <div id="destination-option" class="two-column visible"> - <h1 i18n-content="destinationLabel"></h1> - <div class="right-column"> - <select id="printer-list"></select> - </div> - </div> - <include src="page_settings.html"></include> - <include src="copies_settings.html"></include> - <include src="layout_settings.html"></include> - <include src="color_settings.html"></include> - <include src="margin_settings.html"></include> - <include src="more_options.html"></include> + <include src="settings/destination_settings.html"/> + <include src="settings/page_settings.html"/> + <include src="settings/copies_settings.html"/> + <include src="settings/layout_settings.html"/> + <include src="settings/color_settings.html"/> + <include src="settings/margin_settings.html"/> + <include src="settings/other_options_settings.html"/> + </div> + <div id="link-container"> <div> - <if expr="pp_ifdef('chromeos')"> - <button id="system-dialog-link" - class="link-button preview-link-button" - i18n-content="cloudPrintDialogOption"></button> - </if> - <if expr="not pp_ifdef('chromeos')"> - <button id="system-dialog-link" - class="link-button preview-link-button" - i18n-content="systemDialogOption"></button> - </if> - <div id="system-dialog-throbber" class="throbber" hidden></div> + <button id="cloud-print-dialog-link" + class="link-button navbar-link" + style="display: none;" + i18n-content="cloudPrintDialogOption"></button> + <button id="system-dialog-link" + class="link-button navbar-link" + style="display: none;" + i18n-content="systemDialogOption"></button> + <div id="dialog-throbber" + style="display: none;" + class="throbber"></div> </div> - <if expr="is_macosx"> - <div> - <button id="open-pdf-in-preview-link" - class="link-button preview-link-button" - i18n-content="openPdfInPreviewOption"></button> - <div id="open-preview-app-throbber" class="throbber" hidden></div> - </div> - </if> - </div> - </div> - <div id="mainview"> - <div id="overlay-layer" class="invisible"> - <div id="messages"> - <div id="dancing-dots-text" hidden> - <span id="loading"></span> - <span><span>.</span><span>.</span><span>.</span></span> - </div> - <div id="custom-message" hidden></div> - <div id="custom-message-with-dots" - class="message-with-dots" hidden></div> - <div id="error-action-area"> - <button id="error-button" hidden></button> - <div id="native-print-dialog-throbber" class="throbber" hidden></div> - </div> + <div> + <button id="open-pdf-in-preview-link" + class="link-button navbar-link" + style="display: none;" + i18n-content="openPdfInPreviewOption"></button> + <div id="open-preview-app-throbber" + style="display: none;" + class="throbber"></div> </div> </div> </div> - <object id="dummy-viewer" - type="application/x-google-chrome-print-preview-pdf" - data="chrome://print/dummy.pdf"></object> - <script src="chrome://resources/js/i18n_template.js"></script> - <script src="chrome://resources/js/i18n_process.js"></script> + <include src="previewarea/preview_area.html"/> + + <!-- HTML Templates --> + <include src="previewarea/margin_control.html"/> </body> + </html> diff --git a/chrome/browser/resources/print_preview/print_preview.js b/chrome/browser/resources/print_preview/print_preview.js index f47cb29..ed78cfc 100644 --- a/chrome/browser/resources/print_preview/print_preview.js +++ b/chrome/browser/resources/print_preview/print_preview.js @@ -2,1267 +2,916 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// require: cr/ui/print_preview_cloud.js +// TODO(rltoscano): Might be a problem where might be in fetching destinations +// state, then to file selection state, then cancel, which results in the +// fetching destinations state being lost. -var localStrings = new LocalStrings(); - -// If useCloudPrint is true we attempt to connect to cloud print -// and populate the list of printers with cloud print printers. -var useCloudPrint = false; - -// Store the last selected printer index. -var lastSelectedPrinterIndex = 0; - -// Used to disable some printing options when the preview is not modifiable. -var previewModifiable = false; - -// Used to identify whether the printing frame has specific page size style. -var hasPageSizeStyle = false; - -// Destination list special value constants. - -/** @const */ var MANAGE_CLOUD_PRINTERS = 'manageCloudPrinters'; -/** @const */ var MANAGE_LOCAL_PRINTERS = 'manageLocalPrinters'; -/** @const */ var SIGN_IN = 'signIn'; -/** @const */ var PRINT_TO_PDF = 'Save as PDF'; -/** @const */ var PRINT_WITH_CLOUD_PRINT = 'printWithCloudPrint'; - -// State of the print preview settings. -var printSettings = new PrintSettings(); - -// Print ready data index. -/** @const */ var PRINT_READY_DATA_INDEX = -1; - -// The name of the default or last used printer. -var defaultOrLastUsedPrinterName = ''; - -// True when a pending print preview request exists. -var hasPendingPreviewRequest = false; - -// True when the first page is loaded in the plugin. -var isFirstPageLoaded = false; - -// The ID of the last preview request. -var lastPreviewRequestID = -1; - -// The ID of the initial preview request. -var initialPreviewRequestID = -1; - -// True when a pending print file request exists. -var hasPendingPrintDocumentRequest = false; +// TODO(rltoscano): Move data/* into print_preview.data namespace -// True when the complete metafile for the previewed doc is ready. -var isPrintReadyMetafileReady = false; +// TODO(rltoscano): Handle case where cloud print is initial destination, but +// cloud print is not enabled. -// True when preview tab is hidden. -var isTabHidden = false; - -// True in kiosk mode where print preview can print automatically without -// user intervention. See http://crbug.com/31395. -var printAutomaticallyInKioskMode = false; - -// @type {print_preview.PrintHeader} Holds the print and cancel buttons. -var printHeader; - -// @type {print_preview.PageSettings} Holds all the pages related settings. -var pageSettings; +var localStrings = new LocalStrings(); -// @type {print_preview.CopiesSettings} Holds all the copies related settings. -var copiesSettings; - -// @type {print_preview.LayoutSettings} Holds all the layout related settings. -var layoutSettings; - -// @type {print_preview.MarginSettings} Holds all the margin related settings. -var marginSettings; - -// @type {print_preview.HeaderFooterSettings} Holds all the header footer -// related settings. -var headerFooterSettings; - -// @type {print_preview.FitToPageSettings} Holds all the fit to page related -// settings. -var fitToPageSettings; - -// @type {print_preview.MoreOptions} Holds the more options implementation. -var moreOptions; - -// @type {print_preview.ColorSettings} Holds all the color related settings. -var colorSettings; - -// @type {print_preview.PreviewArea} Holds information related to the preview -// area (on the right). -var previewArea; - -// True if the user has click 'Advanced...' in order to open the system print -// dialog. -var showingSystemDialog = false; - -// True if the user has clicked 'Open PDF in Preview' option. -var previewAppRequested = false; - -// The range of options in the printer dropdown controlled by cloud print. -var firstCloudPrintOptionPos = 0; -var lastCloudPrintOptionPos = firstCloudPrintOptionPos; - -// Store the current previewUid. -var currentPreviewUid = ''; - -// True if we need to generate draft preview data. -var generateDraftData = true; - -// The last element clicked with the mouse. -var lastClickedElement = null; - -// A dictionary of cloud printers that have been added to the printer -// dropdown. -var addedCloudPrinters = {}; - -// The maximum number of cloud printers to allow in the dropdown. -/** @const */ var maxCloudPrinters = 10; - -/** @const */ var MIN_REQUEST_ID = 0; -/** @const */ var MAX_REQUEST_ID = 32000; - -// Names of all the custom events used. -var customEvents = { - // Fired when the header footer option visibility changed. - HEADER_FOOTER_VISIBILITY_CHANGED: 'headerFooterVisibilityChanged', - // Fired when the mouse moves while a margin line is being dragged. - MARGIN_LINE_DRAG: 'marginLineDrag', - // Fired when a mousedown event occurs on a margin line. - MARGIN_LINE_MOUSE_DOWN: 'marginLineMouseDown', - // Fired when a margin textbox gains focus. - MARGIN_TEXTBOX_FOCUSED: 'marginTextboxFocused', - // Fired when a new preview might be needed because of margin changes. - MARGINS_MAY_HAVE_CHANGED: 'marginsMayHaveChanged', - // Fired when a pdf generation related error occurs. - PDF_GENERATION_ERROR: 'PDFGenerationError', - // Fired once the first page of the pdf document is loaded in the plugin. - PDF_LOADED: 'PDFLoaded', - // Fired when the selected printer capabilities change. - PRINTER_CAPABILITIES_UPDATED: 'printerCapabilitiesUpdated', - // Fired when the destination printer is changed. - PRINTER_SELECTION_CHANGED: 'printerSelectionChanged', - // Fired when the print button needs to be updated. - UPDATE_PRINT_BUTTON: 'updatePrintButton', - // Fired when the print summary needs to be updated. - UPDATE_SUMMARY: 'updateSummary' -}; - -/** - * Window onload handler, sets up the page and starts print preview by getting - * the printer list. - */ -function onLoad() { - initialPreviewRequestID = randomInteger(MIN_REQUEST_ID, MAX_REQUEST_ID); - lastPreviewRequestID = initialPreviewRequestID; - - previewArea = print_preview.PreviewArea.getInstance(); - printHeader = print_preview.PrintHeader.getInstance(); - document.addEventListener(customEvents.PDF_GENERATION_ERROR, - cancelPendingPrintRequest); - document.addEventListener('click', setLastClickedElement); - - if (!checkCompatiblePluginExists()) { - disableInputElementsInSidebar(); - $('cancel-button').focus(); - previewArea.displayErrorMessageWithButtonAndNotify( - localStrings.getString('noPlugin'), - localStrings.getString('launchNativeDialog'), - launchNativePrintDialog); - $('mainview').parentElement.removeChild($('dummy-viewer')); - return; - } - - $('system-dialog-link').addEventListener('click', onSystemDialogLinkClicked); - if (cr.isMac) { - $('open-pdf-in-preview-link').addEventListener( - 'click', onOpenPdfInPreviewLinkClicked); - } - $('mainview').parentElement.removeChild($('dummy-viewer')); - - $('printer-list').disabled = true; - - pageSettings = print_preview.PageSettings.getInstance(); - copiesSettings = print_preview.CopiesSettings.getInstance(); - layoutSettings = print_preview.LayoutSettings.getInstance(); - marginSettings = print_preview.MarginSettings.getInstance(); - headerFooterSettings = print_preview.HeaderFooterSettings.getInstance(); - fitToPageSettings = print_preview.FitToPageSettings.getInstance(); - moreOptions = print_preview.MoreOptions.getInstance(); - colorSettings = print_preview.ColorSettings.getInstance(); - $('printer-list').onchange = updateControlsWithSelectedPrinterCapabilities; - - previewArea.showLoadingAnimation(); - chrome.send('getInitialSettings'); -} - -/** - * @param {object} initialSettings An object containing all the initial - * settings. - */ -function setInitialSettings(initialSettings) { - setInitiatorTabTitle(initialSettings['initiatorTabTitle']); - previewModifiable = initialSettings['previewModifiable']; - if (previewModifiable) { - print_preview.MarginSettings.setNumberFormatAndMeasurementSystem( - initialSettings['numberFormat'], - initialSettings['measurementSystem']); - marginSettings.setLastUsedMargins(initialSettings); - } - printAutomaticallyInKioskMode = - initialSettings['printAutomaticallyInKioskMode']; - headerFooterSettings.setChecked(initialSettings['headerFooterEnabled']); - copiesSettings.previousDuplexMode = initialSettings['duplex']; - setDefaultPrinter(initialSettings['printerName'], - initialSettings['cloudPrintData']); -} - -/** - * Disables the input elements in the sidebar. - */ -function disableInputElementsInSidebar() { - var els = $('navbar-container').querySelectorAll('input, button, select'); - for (var i = 0; i < els.length; i++) { - if (els[i] == printHeader.cancelButton) - continue; - els[i].disabled = true; - } -} - -/** - * Enables the input elements in the sidebar. - */ -function enableInputElementsInSidebar() { - var els = $('navbar-container').querySelectorAll('input, button, select'); - for (var i = 0; i < els.length; i++) - els[i].disabled = false; -} - -/** - * Keep track of the last element to receive a click. - * @param {Event} e The click event. - */ -function setLastClickedElement(e) { - lastClickedElement = e.target; -} - -/** - * Disables the controls in the sidebar, shows the throbber and instructs the - * backend to open the native print dialog. - */ -function onSystemDialogLinkClicked() { - if (showingSystemDialog) - return; - showingSystemDialog = true; - disableInputElementsInSidebar(); - printHeader.disableCancelButton(); - $('system-dialog-throbber').hidden = false; - chrome.send('showSystemDialog'); -} - -/** - * Disables the controls in the sidebar, shows the throbber and instructs the - * backend to open the pdf in native preview app. This is only for Mac. - */ -function onOpenPdfInPreviewLinkClicked() { - if (previewAppRequested) - return; - previewAppRequested = true; - disableInputElementsInSidebar(); - $('open-preview-app-throbber').hidden = false; - printHeader.disableCancelButton(); - requestToPrintDocument(); -} - -/** - * Similar to onSystemDialogLinkClicked(), but specific to the - * 'Launch native print dialog' UI. - */ -function launchNativePrintDialog() { - if (showingSystemDialog) - return; - showingSystemDialog = true; - previewArea.errorButton.disabled = true; - printHeader.disableCancelButton(); - $('native-print-dialog-throbber').hidden = false; - chrome.send('showSystemDialog'); -} - -/** - * Notifies listeners of |customEvents.PRINTER_SELECTION_CHANGED| event about - * the current selected printer. - */ -function dispatchPrinterSelectionChangedEvent() { - var customEvent = cr.Event(customEvents.PRINTER_SELECTION_CHANGED); - customEvent.selectedPrinter = getSelectedPrinterName(); - document.dispatchEvent(customEvent); -} - -/** - * Gets the selected printer capabilities and updates the controls accordingly. - */ -function updateControlsWithSelectedPrinterCapabilities() { - var printerList = $('printer-list'); - var selectedIndex = printerList.selectedIndex; - if (selectedIndex < 0) - return; - if (cr.isMac) - $('open-pdf-in-preview-link').disabled = false; - - var skip_refresh = false; - var selectedPrinterChanged = true; - var selectedValue = printerList.options[selectedIndex].value; - if (cloudprint.isCloudPrint(printerList.options[selectedIndex])) { - cloudprint.updatePrinterCaps(printerList.options[selectedIndex], - doUpdateCloudPrinterCapabilities); - skip_refresh = true; - } else if (selectedValue == SIGN_IN || - selectedValue == MANAGE_CLOUD_PRINTERS || - selectedValue == MANAGE_LOCAL_PRINTERS) { - printerList.selectedIndex = lastSelectedPrinterIndex; - chrome.send(selectedValue); - skip_refresh = true; - selectedPrinterChanged = false; - } else if (selectedValue == PRINT_TO_PDF || - selectedValue == PRINT_WITH_CLOUD_PRINT) { - updateWithPrinterCapabilities({ - 'disableColorOption': true, - 'setColorAsDefault': true, - 'setDuplexAsDefault': false, - 'printerColorModelForColor': print_preview.ColorSettings.COLOR, - 'printerDefaultDuplexValue': copiesSettings.UNKNOWN_DUPLEX_MODE, - 'disableCopiesOption': true}); - if (cr.isChromeOS && selectedValue == PRINT_WITH_CLOUD_PRINT) - requestToPrintDocument(); - } else { - // This message will call back to 'updateWithPrinterCapabilities' - // function. - chrome.send('getPrinterCapabilities', [selectedValue]); - } - if (selectedPrinterChanged) - dispatchPrinterSelectionChangedEvent(); - - if (!skip_refresh) { - lastSelectedPrinterIndex = selectedIndex; - - // Regenerate the preview data based on selected printer settings. - // Do not reset the margins if no preview request has been made. - var resetMargins = lastPreviewRequestID != initialPreviewRequestID; - setDefaultValuesAndRegeneratePreview(resetMargins); - } -} - -/** - * Helper function to do the actual work of updating cloud printer - * capabilities. - * @param {Object} printer The printer object to set capabilities for. - */ -function doUpdateCloudPrinterCapabilities(printer) { - var settings = {'disableColorOption': !cloudprint.supportsColor(printer), - 'setColorAsDefault': cloudprint.colorIsDefault(printer), - 'disableCopiesOption': true, - 'disableLandscapeOption': true}; - updateWithPrinterCapabilities(settings); - var printerList = $('printer-list'); - var selectedIndex = printerList.selectedIndex; - lastSelectedPrinterIndex = selectedIndex; - - // Regenerate the preview data based on selected printer settings. - // Do not reset the margins if no preview request has been made. - var resetMargins = lastPreviewRequestID != initialPreviewRequestID; - setDefaultValuesAndRegeneratePreview(resetMargins); -} - -/** - * Notifies listeners of |customEvents.PRINTER_CAPABILITIES_UPDATED| about the - * capabilities of the currently selected printer. It is called from C++ too. - * @param {Object} settingInfo printer setting information. - */ -function updateWithPrinterCapabilities(settingInfo) { - var customEvent = new cr.Event(customEvents.PRINTER_CAPABILITIES_UPDATED); - customEvent.printerCapabilities = settingInfo; - document.dispatchEvent(customEvent); -} - -/** - * Reloads the printer list. - */ -function reloadPrintersList() { - $('printer-list').length = 0; - firstCloudPrintOptionPos = 0; - lastCloudPrintOptionPos = 0; - chrome.send('getPrinters'); -} - -/** - * Turn on the integration of Cloud Print. - * @param {string} cloudPrintURL The URL to use for cloud print servers. - */ -function setUseCloudPrint(cloudPrintURL) { - useCloudPrint = true; - cloudprint.setBaseURL(cloudPrintURL); -} - -/** - * Take the PDF data handed to us and submit it to the cloud, closing the print - * preview tab once the upload is successful. - * @param {string} data Data to send as the print job. - */ -function printToCloud(data) { - cloudprint.printToCloud(data, finishedCloudPrinting); -} - -/** - * Cloud print upload of the PDF file is finished, time to close the dialog. - */ -function finishedCloudPrinting() { - closePrintPreviewTab(); -} - -/** - * Updates the fit to page option state based on the print scaling option of - * source pdf. PDF's have an option to enable/disable print scaling. When we - * find out that the print scaling option is disabled for the source pdf, we - * uncheck the fit to page checkbox. This function is called from C++ code. - */ -function printScalingDisabledForSourcePDF() { - fitToPageSettings.onPrintScalingDisabled(); -} - -/** - * Checks whether the specified settings are valid. - * - * @return {boolean} true if settings are valid, false if not. - */ -function areSettingsValid() { - var selectedPrinter = getSelectedPrinterName(); - return pageSettings.isPageSelectionValid() && - marginSettings.areMarginSettingsValid() && - (copiesSettings.isValid() || selectedPrinter == PRINT_TO_PDF || - selectedPrinter == PRINT_WITH_CLOUD_PRINT); -} - -/** - * Creates an object based on the values in the printer settings. - * - * @return {Object} Object containing print job settings. - */ -function getSettings() { - var deviceName = getSelectedPrinterName(); - var printToPDF = deviceName == PRINT_TO_PDF; - var printWithCloudPrint = deviceName == PRINT_WITH_CLOUD_PRINT; - - var settings = - {'deviceName': deviceName, - 'pageRange': pageSettings.selectedPageRanges, - 'duplex': copiesSettings.duplexMode, - 'copies': copiesSettings.numberOfCopies, - 'collate': copiesSettings.isCollated(), - 'landscape': layoutSettings.isLandscape(), - 'color': colorSettings.colorMode, - 'printToPDF': printToPDF, - 'printWithCloudPrint': printWithCloudPrint, - 'isFirstRequest' : false, - 'headerFooterEnabled': headerFooterSettings.hasHeaderFooter(), - 'marginsType': marginSettings.selectedMarginsValue, - 'requestID': -1, - 'generateDraftData': generateDraftData, - 'fitToPageEnabled': fitToPageSettings.hasFitToPage(), - 'previewModifiable': previewModifiable}; - - if (marginSettings.isCustomMarginsSelected()) - settings['marginsCustom'] = marginSettings.customMargins; - - var printerList = $('printer-list'); - var selectedPrinter = printerList.selectedIndex; - if (cloudprint.isCloudPrint(printerList.options[selectedPrinter])) { - settings['cloudPrintID'] = - printerList.options[selectedPrinter].value; - } - return settings; -} - -/** - * Creates an object based on the values in the printer settings. - * Note: |lastPreviewRequestID| is being modified every time this function is - * called. Only call this function when a preview request is actually sent, - * otherwise (for example when debugging) call getSettings(). - * - * @return {Object} Object containing print job settings. - */ -function getSettingsWithRequestID() { - var settings = getSettings(); - settings.requestID = generatePreviewRequestID(); - settings.isFirstRequest = isFirstPreviewRequest(); - return settings; -} - -/** - * @return {number} The next unused preview request id. - */ -function generatePreviewRequestID() { - return ++lastPreviewRequestID; -} - -/** - * @return {boolean} True iff a preview has been requested. - */ -function hasRequestedPreview() { - return lastPreviewRequestID != initialPreviewRequestID; -} - -/** - * @return {boolean} True if |lastPreviewRequestID| corresponds to the initial - * preview request. - */ -function isFirstPreviewRequest() { - return lastPreviewRequestID == initialPreviewRequestID + 1; -} - -/** - * Checks if |previewResponseId| matches |lastPreviewRequestId|. Used to ignore - * obsolete preview data responses. - * @param {number} previewResponseId The id to check. - * @return {boolean} True if previewResponseId reffers to the expected response. - */ -function isExpectedPreviewResponse(previewResponseId) { - return lastPreviewRequestID == previewResponseId; -} - -/** - * Returns the name of the selected printer or the empty string if no - * printer is selected. - * @return {string} The name of the currently selected printer. - */ -function getSelectedPrinterName() { - var printerList = $('printer-list'); - var selectedPrinter = printerList.selectedIndex; - if (selectedPrinter < 0) - return ''; - return printerList.options[selectedPrinter].value; -} - -/** - * Asks the browser to print the preview PDF based on current print - * settings. If the preview is still loading, printPendingFile() will get - * called once the preview loads. - */ -function requestToPrintDocument() { - hasPendingPrintDocumentRequest = !isPrintReadyMetafileReady; - var selectedPrinterName = getSelectedPrinterName(); - var printToPDF = selectedPrinterName == PRINT_TO_PDF; - var printWithCloudPrint = selectedPrinterName == PRINT_WITH_CLOUD_PRINT; - if (hasPendingPrintDocumentRequest) { - if (previewAppRequested) { - previewArea.showCustomMessage( +<include src="component.js"/> + +cr.define('print_preview', function() { + 'use strict'; + + /** + * Container class for Chromium's print preview. + * @constructor + * @extends {print_preview.Component} + */ + function PrintPreview() { + print_preview.Component.call(this); + + /** + * Used to communicate with Chromium's print system. + * @type {!print_preview.NativeLayer} + * @private + */ + this.nativeLayer_ = new print_preview.NativeLayer(); + + /** + * Data store which holds print destinations. + * @type {!print_preview.DestinationStore} + * @private + */ + this.destinationStore_ = new print_preview.DestinationStore(); + + /** + * Storage of the print ticket used to create the print job. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = new print_preview.PrintTicketStore( + this.destinationStore_); + + /** + * Holds the print and cancel buttons and renders some document statistics. + * @type {!print_preview.PrintHeader} + * @private + */ + this.printHeader_ = new print_preview.PrintHeader( + this.printTicketStore_, this.destinationStore_); + this.addChild(this.printHeader_); + + /** + * Component that renders the print destination. + * @type {!print_preview.DestinationSettings} + * @private + */ + this.destinationSettings_ = new print_preview.DestinationSettings( + this.destinationStore_); + this.addChild(this.destinationSettings_); + + /** + * Component that renders UI for entering in page range. + * @type {!print_preview.PageSettings} + * @private + */ + this.pageSettings_ = new print_preview.PageSettings(this.printTicketStore_); + this.addChild(this.pageSettings_); + + /** + * Component that renders the copies settings. + * @type {!print_preview.CopiesSettings} + * @private + */ + this.copiesSettings_ = new print_preview.CopiesSettings( + this.printTicketStore_); + this.addChild(this.copiesSettings_); + + /** + * Component that renders the layout settings. + * @type {!print_preview.LayoutSettings} + * @private + */ + this.layoutSettings_ = new print_preview.LayoutSettings( + this.printTicketStore_); + this.addChild(this.layoutSettings_); + + /** + * Component that renders the color options. + * @type {!print_preview.ColorSettings} + * @private + */ + this.colorSettings_ = new print_preview.ColorSettings( + this.printTicketStore_); + this.addChild(this.colorSettings_); + + /** + * Component that renders a select box for choosing margin settings. + * @type {!print_preview.MarginSettings} + * @private + */ + this.marginSettings_ = new print_preview.MarginSettings( + this.printTicketStore_); + this.addChild(this.marginSettings_); + + /** + * Component that renders miscellaneous print options. + * @type {!print_preview.OtherOptionsSettings} + * @private + */ + this.otherOptionsSettings_ = new print_preview.OtherOptionsSettings( + this.printTicketStore_); + this.addChild(this.otherOptionsSettings_); + + /** + * Area of the UI that holds the print preview. + * @type {!print_preview.PreviewArea} + * @private + */ + this.previewArea_ = new print_preview.PreviewArea( + this.destinationStore_, this.printTicketStore_, this.nativeLayer_); + this.addChild(this.previewArea_); + + /** + * Interface to the Google Cloud Print API. Null if Google Cloud Print + * integration is disabled. + * @type {cloudprint.CloudPrintInterface} + * @private + */ + this.cloudPrintInterface_ = null; + + /** + * Whether in kiosk mode where print preview can print automatically without + * user intervention. See http://crbug.com/31395. Print will start when + * both the print ticket has been initialized, and an initial printer has + * been selected. + * @type {boolean} + * @private + */ + this.isInKioskAutoPrintMode_ = false; + + /** + * State of the print preview UI. + * @type {print_preview.PrintPreview.UiState_} + * @private + */ + this.uiState_ = PrintPreview.UiState_.INITIALIZING; + + /** + * Current state of fetching destinations. + * @type {print_preview.PrintPreview.FetchState_} + * @private + */ + this.fetchState_ = PrintPreview.FetchState_.READY; + + /** + * Whether document preview generation is in progress. + * @type {boolean} + * @private + */ + this.isPreviewGenerationInProgress_ = true; + + this.tracker.add(window, 'DOMContentLoaded', this.onWindowLoad_.bind(this)); + }; + + /** + * States of the print preview. + * @enum {string} + * @private + */ + PrintPreview.UiState_ = { + INITIALIZING: 'initializing', + READY: 'ready', + OPENING_PDF_PREVIEW: 'opening-pdf-preview', + OPENING_NATIVE_PRINT_DIALOG: 'opening-native-print-dialog', + PRINTING: 'printing', + FILE_SELECTION: 'file-selection', + CLOSING: 'closing', + ERROR: 'error' + }; + + /** + * Bitfield of the states of fetching destinations. + * @enum {number} + * @private + */ + PrintPreview.FetchState_ = { + READY: 1, + LOCAL_DESTINATIONS: 2, + RECENT_CLOUD_DESTINATIONS: 4, + ALL_CLOUD_DESTINATIONS: 8 + }; + + PrintPreview.prototype = { + __proto__: print_preview.Component.prototype, + + /** @override */ + decorateInternal: function() { + this.printHeader_.decorate($('print-header')); + this.destinationSettings_.decorate($('destination-settings')); + this.pageSettings_.decorate($('page-settings')); + this.copiesSettings_.decorate($('copies-settings')); + this.layoutSettings_.decorate($('layout-settings')); + this.colorSettings_.decorate($('color-settings')); + this.marginSettings_.decorate($('margin-settings')); + this.otherOptionsSettings_.decorate($('other-options-settings')); + this.previewArea_.decorate($('preview-area')); + + setIsVisible($('cloud-print-dialog-link'), cr.isChromeOS); + setIsVisible($('system-dialog-link'), !cr.isChromeOS); + setIsVisible($('open-pdf-in-preview-link'), cr.isMac); + }, + + /** @override */ + enterDocument: function() { + // Native layer events. + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.INITIAL_SETTINGS_SET, + this.onInitialSettingsSet_.bind(this)); + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.CLOUD_PRINT_ENABLE, + this.onCloudPrintEnable_.bind(this)); + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.LOCAL_DESTINATIONS_SET, + this.onLocalDestinationsSet_.bind(this)); + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.CAPABILITIES_SET, + this.onLocalDestinationCapabilitiesSet_.bind(this)); + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD, + this.onDestinationsReload_.bind(this)); + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.PRINT_TO_CLOUD, + this.onPrintToCloud_.bind(this)); + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.FILE_SELECTION_CANCEL, + this.onFileSelectionCancel_.bind(this)); + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.FILE_SELECTION_COMPLETE, + this.onFileSelectionComplete_.bind(this)); + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.SETTINGS_INVALID, + this.onSettingsInvalid_.bind(this)); + this.tracker.add( + this.nativeLayer_, + print_preview.NativeLayer.EventType.DISABLE_SCALING, + this.onDisableScaling_.bind(this)); + + this.tracker.add( + $('system-dialog-link'), + 'click', + this.openSystemPrintDialog_.bind(this)); + this.tracker.add( + $('cloud-print-dialog-link'), + 'click', + this.openSystemPrintDialog_.bind(this)); + this.tracker.add( + $('open-pdf-in-preview-link'), + 'click', + this.onOpenPdfInPreviewLinkClick_.bind(this)); + + this.tracker.add( + this.previewArea_, + print_preview.PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS, + this.onPreviewGenerationInProgress_.bind(this)); + this.tracker.add( + this.previewArea_, + print_preview.PreviewArea.EventType.PREVIEW_GENERATION_DONE, + this.onPreviewGenerationDone_.bind(this)); + this.tracker.add( + this.previewArea_, + print_preview.PreviewArea.EventType.PREVIEW_GENERATION_FAIL, + this.onPreviewGenerationFail_.bind(this)); + this.tracker.add( + this.previewArea_, + print_preview.PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK, + this.openSystemPrintDialog_.bind(this)); + + this.tracker.add( + this.destinationStore_, + print_preview.DestinationStore.EventType.DESTINATION_SELECT, + this.onDestinationSelect_.bind(this)); + + this.tracker.add( + this.printHeader_, + print_preview.PrintHeader.EventType.PRINT_BUTTON_CLICK, + this.onPrintButtonClick_.bind(this)); + this.tracker.add( + this.printHeader_, + print_preview.PrintHeader.EventType.CANCEL_BUTTON_CLICK, + this.onCancelButtonClick_.bind(this)); + + this.tracker.add( + this.destinationSettings_, + print_preview.DestinationSettings.EventType.MANAGE_PRINTERS_SELECT, + this.onManagePrinters_.bind(this)); + + this.tracker.add(window, 'keydown', this.onKeyDown_.bind(this)); + }, + + /** + * Sets whether the controls in the print preview are enabled. + * @param {boolean} isEnabled Whether the controls in the print preview are + * enabled. + * @private + */ + setIsEnabled_: function(isEnabled) { + $('system-dialog-link').disabled = !isEnabled; + $('cloud-print-dialog-link').disabled = !isEnabled; + $('open-pdf-in-preview-link').disabled = !isEnabled; + this.printHeader_.isEnabled = isEnabled; + this.destinationSettings_.isEnabled = isEnabled; + this.pageSettings_.isEnabled = isEnabled; + this.copiesSettings_.isEnabled = isEnabled; + this.layoutSettings_.isEnabled = isEnabled; + this.colorSettings_.isEnabled = isEnabled; + this.marginSettings_.isEnabled = isEnabled; + this.otherOptionsSettings_.isEnabled = isEnabled; + }, + + /** + * Creates a local PDF print destination. + * @return {!print_preview.Destination} Created print destination. + * @private + */ + createLocalPdfPrintDestination_: function() { + var dest = new print_preview.Destination( + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF, + localStrings.getString('printToPDF'), + false /*isRecent*/, + true /*isLocal*/); + dest.capabilities = new print_preview.ChromiumCapabilities( + false /*hasCopiesCapability*/, + '1' /*defaultCopiesStr*/, + false /*hasCollateCapability*/, + false /*defaultIsCollateEnabled*/, + false /*hasDuplexCapability*/, + false /*defaultIsDuplexEnabled*/, + true /*hasOrientationCapability*/, + false /*defaultIsLandscapeEnabled*/, + true /*hasColorCapability*/, + true /*defaultIsColorEnabled*/); + return dest; + }, + + /** + * Creates a new "Print with Cloud Print" print destination. NOTE: this + * destination will appear as "Search for additional printers..." on + * Chrome OS. + * @return {!print_preview.Destination} Created print destination. + * @private + */ + createPrintWithCloudPrintDestination_: function() { + var dest = new print_preview.Destination( + print_preview.Destination.GooglePromotedId.PRINT_WITH_CLOUD_PRINT, + localStrings.getString('printWithCloudPrint'), + false /*isRecent*/, + false /*isLocal*/); + dest.capabilities = new print_preview.ChromiumCapabilities( + false /*hasCopiesCapability*/, + '1' /*defaultCopiesStr*/, + false /*hasCollateCapability*/, + false /*defaultIsCollateEnabled*/, + false /*hasDuplexCapability*/, + false /*defaultIsDuplexEnabled*/, + true /*hasOrientationCapability*/, + false /*defaultIsLandscapeEnabled*/, + true /*hasColorCapability*/, + true /*defaultIsColorEnabled*/); + return dest; + }, + + /** + * Prints the document or launches a pdf preview on the local system. + * @param {boolean} isPdfPreview Whether to launch the pdf preview. + * @private + */ + printDocumentOrOpenPdfPreview_: function(isPdfPreview) { + assert(this.uiState_ == PrintPreview.UiState_.READY, + 'Print document request received when not in ready state: ' + + this.uiState_); + if (isPdfPreview) { + this.uiState_ = PrintPreview.UiState_.OPENING_PDF_PREVIEW; + } else if (this.destinationStore_.selectedDestination.id == + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) { + this.uiState_ = PrintPreview.UiState_.FILE_SELECTION; + } else { + this.uiState_ = PrintPreview.UiState_.PRINTING; + } + this.setIsEnabled_(false); + if (this.printIfReady_() && + ((this.destinationStore_.selectedDestination.isLocal && + this.destinationStore_.selectedDestination.id != + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) || + this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW)) { + // Hide the dialog for now. The actual print command will be issued when + // the preview generation is done. + this.nativeLayer_.startHideDialog(); + } + }, + + /** + * Attempts to print if needed and if ready. + * @return {boolean} Whether a print request was issued. + * @private + */ + printIfReady_: function() { + if ((this.uiState_ == PrintPreview.UiState_.PRINTING || + this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW || + this.uiState_ == PrintPreview.UiState_.FILE_SELECTION || + this.isInKioskAutoPrintMode_) && + !this.isPreviewGenerationInProgress_ && + this.destinationStore_.selectedDestination && + this.destinationStore_.selectedDestination.capabilities) { + assert(this.printTicketStore_.isTicketValid(), + 'Trying to print with invalid ticket'); + this.nativeLayer_.startSaveDestinationAndTicket( + this.destinationStore_.selectedDestination, + this.printTicketStore_); + this.nativeLayer_.startPrint( + this.destinationStore_.selectedDestination, + this.printTicketStore_, + this.cloudPrintInterface_, + this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW); + return true; + } else { + return false; + } + }, + + /** + * Closes the print preview. + * @private + */ + close_: function() { + this.exitDocument(); + this.uiState_ = PrintPreview.UiState_.CLOSING; + this.nativeLayer_.startCloseDialog(); + }, + + /** + * Opens the native system print dialog after disabling all controls. + * @private + */ + openSystemPrintDialog_: function() { + assert(this.uiState_ == PrintPreview.UiState_.READY, + 'Opening system dialog when not in ready state: ' + this.uiState_); + setIsVisible($('dialog-throbber'), true); + this.setIsEnabled_(false); + this.uiState_ = PrintPreview.UiState_.OPENING_NATIVE_PRINT_DIALOG; + this.nativeLayer_.startShowSystemDialog(); + }, + + /** + * Window onload handler, sets up the page and starts print preview by + * getting the printer list. + * @private + */ + onWindowLoad_: function() { + this.decorate($('print-preview')); + i18nTemplate.process(document, templateData); + if (!this.previewArea_.hasCompatiblePlugin) { + this.setIsEnabled_(false); + } + this.nativeLayer_.startGetInitialSettings(); + this.nativeLayer_.startGetLocalDestinations(); + }, + + /** + * Called when the native layer has initial settings to set. Sets the + * initial settings of the print preview and begins fetching print + * destinations. + * @param {cr.Event} event Contains the initial print preview settings + * persisted through the session. + * @private + */ + onInitialSettingsSet_: function(event) { + assert(this.uiState_ == PrintPreview.UiState_.INITIALIZING, + 'Updating initial settings when not in initializing state: ' + + this.uiState_); + this.uiState_ = PrintPreview.UiState_.READY; + + this.isInKioskAutoPrintMode_ = + event.initialSettings.isInKioskAutoPrintMode; + this.destinationStore_.setInitialDestinationId( + event.initialSettings.initialDestinationId); + this.printTicketStore_.initialize( + event.initialSettings.isDocumentModifiable, + event.initialSettings.isDuplexEnabled, + event.initialSettings.isHeaderFooterEnabled, + event.initialSettings.marginsType, + event.initialSettings.customMargins, + event.initialSettings.thousandsDelimeter, + event.initialSettings.decimalDelimeter, + event.initialSettings.unitType); + }, + + /** + * Calls when the native layer enables Google Cloud Print integration. + * Fetches the user's cloud printers. + * @param {cr.Event} event Contains the base URL of the Google Cloud Print + * service. + * @private + */ + onCloudPrintEnable_: function(event) { + this.cloudPrintInterface_ = new cloudprint.CloudPrintInterface( + event.baseCloudPrintUrl); + this.tracker.add( + this.cloudPrintInterface_, + cloudprint.CloudPrintInterface.EventType.SEARCH_DONE, + this.onCloudPrintSearchDone_.bind(this)); + this.tracker.add( + this.cloudPrintInterface_, + cloudprint.CloudPrintInterface.EventType.PRINTER_DONE, + this.onCloudPrintPrinterDone_.bind(this)); + this.tracker.add( + this.cloudPrintInterface_, + cloudprint.CloudPrintInterface.EventType.SUBMIT_DONE, + this.onCloudPrintSubmitDone_.bind(this)); + this.tracker.add( + this.cloudPrintInterface_, + cloudprint.CloudPrintInterface.EventType.ERROR, + this.onCloudPrintError_.bind(this)); + + var printWithCloudPrintDest = + this.createPrintWithCloudPrintDestination_(); + this.destinationStore_.insertDestination(printWithCloudPrintDest); + + if (cr.isChromeOS) { + this.cloudPrintInterface_.search(true /*isRecent*/); + this.fetchState_ |= PrintPreview.FetchState_.RECENT_CLOUD_DESTINATIONS; + } + }, + + /** + * Called when the native layer gets local destinations. Adds local + * destination objects received from the operating system to the destination + * store. Also adds a save-as-pdf printer. + * @param {cr.Event} Contains the local destinations to set. + * @private + */ + onLocalDestinationsSet_: function(event) { + var localDestinations = []; + for (var destInfo, i = 0; destInfo = event.destinationInfos[i]; i++) { + localDestinations.push( + print_preview.LocalDestinationParser.parse(destInfo)); + } + localDestinations.push(this.createLocalPdfPrintDestination_()); + this.destinationStore_.insertDestinations(localDestinations); + this.fetchState_ &= ~PrintPreview.FetchState_.LOCAL_DESTINATIONS; + }, + + /** + * Called when the native layer retrieves the capabilities for the selected + * local destination. + * @param {cr.Event} event Contains the capabilities of the local print + * destination. + * @private + */ + onLocalDestinationCapabilitiesSet_: function(event) { + // TODO(rltoscano): There may be a race condition here. This method is + // assumed to return capabilities for the currently selected printer. But + // between the time the local printer was selected and the capabilities + // were retrieved, the selected printer can change. One way to address + // this is to include the destination ID in the settingsInfo parameter. + var selectedDestination = this.destinationStore_.selectedDestination; + if (selectedDestination.isLocal) { + var capabilities = print_preview.LocalCapabilitiesParser.parse( + event.settingsInfo); + selectedDestination.capabilities = capabilities; + this.printTicketStore_.updateDestinationCapabilities(capabilities); + this.printIfReady_(); + } + }, + + /** + * Called from native layer after the user was requested to sign in, and did + * so successfully. + * @private + */ + onDestinationsReload_: function() { + this.destinationStore_.clear(); + this.nativeLayer_.startGetLocalDestinations(); + if (this.cloudPrintInterface_) { + // Fetch recent printers. + this.cloudPrintInterface_.search(true /*isRecent*/); + // Fetch the full printer list. + this.cloudPrintInterface_.search(false /*isRecent*/); + } + this.fetchState_ = + PrintPreview.FetchState_.LOCAL_DESTINATIONS | + PrintPreview.FetchState_.ALL_CLOUD_DESTINATIONS | + PrintPreview.FetchState_.RECENT_CLOUD_DESTINATIONS; + }, + + /** + * Called from the native layer when ready to print to Google Cloud Print. + * @param {cr.Event} event Contains the body to send in the HTTP request. + * @private + */ + onPrintToCloud_: function(event) { + assert(this.uiState_ == PrintPreview.UiState_.PRINTING, + 'Document ready to be sent to the cloud when not in printing ' + + 'state: ' + this.uiState_); + assert(this.cloudPrintInterface_ != null, + 'Google Cloud Print is not enabled'); + this.cloudPrintInterface_.submit(event.data); + }, + + /** + * Called from the native layer when the user cancels the save-to-pdf file + * selection dialog. + * @private + */ + onFileSelectionCancel_: function() { + assert(this.uiState_ == PrintPreview.UiState_.FILE_SELECTION, + 'File selection cancelled when not in file-selection state: ' + + this.uiState_); + this.setIsEnabled_(true); + this.uiState_ = PrintPreview.UiState_.READY; + }, + + /** + * Called from the native layer when save-to-pdf file selection is complete. + * @private + */ + onFileSelectionComplete_: function() { + assert(this.uiState_ == PrintPreview.UiState_.FILE_SELECTION, + 'File selection completed when not in file-selection state: ' + + this.uiState_); + this.previewArea_.showCustomMessage( + localStrings.getString('printingToPDFInProgress')); + this.uiState_ = PrintPreview.UiState_.PRINTING; + }, + + /** + * Called when the Google Cloud Print search API call completes. Adds + * destinations to the printer store and selects one if it matches the + * initial destination. + * @param {cr.Event} event Contains the new cloud destinations. + * @private + */ + onCloudPrintSearchDone_: function(event) { + this.destinationStore_.insertDestinations(event.printers); + if (event.isRecent) { + this.fetchState_ &= ~PrintPreview.FetchState_.RECENT_CLOUD_DESTINATIONS; + } else { + this.fetchState_ &= ~PrintPreview.FetchState_.ALL_CLOUD_DESTINATIONS; + } + }, + + /** + * Called when the Google Cloud Print printer API call completes. Updates + * the UI with the newly received capabilities. + * @param {cr.Event} event Contains the destination returned in the printer + * API call. + */ + onCloudPrintPrinterDone_: function(event) { + var dest = this.destinationStore_.updateDestination(event.printer); + if (this.destinationStore_.selectedDestination == dest) { + this.printTicketStore_.updateDestinationCapabilities(dest.capabilities); + this.printIfReady_(); + } + }, + + /** + * Called after successfully submitting a job to Google Cloud Print. + * @private + */ + onCloudPrintSubmitDone_: function() { + assert(this.uiState_ == PrintPreview.UiState_.PRINTING, + 'Submited job to Google Cloud Print but not in printing state ' + + this.uiState_); + this.close_(); + }, + + /** + * Called when there was an error communicating with Google Cloud print. + * Displays an error message in the print header. + * @param {cr.Event} event Contains the error message. + * @private + */ + onCloudPrintError_: function(event) { + if (cr.isChromeOS && event.message == '403') { + this.nativeLayer_.startCloudPrintSignIn(); + } else { + this.printHeader_.setErrorMessage(event.message); + } + this.fetchState_ &= + ~PrintPreview.FetchState_.RECENT_CLOUD_DESTINATIONS & + ~PrintPreview.FetchState_.ALL_CLOUD_DESTINATIONS; + }, + + /** + * Called when a new destination has been selected. Fetches the + * destination's capability list. + * @private + */ + onDestinationSelect_: function() { + var destination = this.destinationStore_.selectedDestination; + + // Fetch destination capabilities if necessary. + if (!destination.capabilities) { + if (destination.isLocal) { + this.nativeLayer_.startGetLocalDestinationCapabilities( + destination.id); + } else { + assert(this.cloudPrintInterface_ != null, + 'Selected destination is a cloud destination, but Google ' + + 'Cloud Print is not enabled'); + this.cloudPrintInterface_.printer(destination.id); + } + } else { + this.printTicketStore_.updateDestinationCapabilities( + destination.capabilities); + } + + this.printIfReady_(); + }, + + /** + * Called when the preview area's preview generation is in progress. + * @private + */ + onPreviewGenerationInProgress_: function() { + this.isPreviewGenerationInProgress_ = true; + }, + + /** + * Called when the preview area's preview generation is complete. + * @private + */ + onPreviewGenerationDone_: function() { + this.isPreviewGenerationInProgress_ = false; + this.printIfReady_(); + }, + + /** + * Called when the preview area's preview failed to load. + * @private + */ + onPreviewGenerationFail_: function() { + this.isPreviewGenerationInProgress_ = false; + if (this.uiState_ == PrintPreview.UiState_.PRINTING) { + this.nativeLayer_.startCancelPendingPrint(); + } + }, + + /** + * Called when the 'Open pdf in preview' link is clicked. Launches the pdf + * preview app. + * @private + */ + onOpenPdfInPreviewLinkClick_: function() { + assert(this.uiState_ == PrintPreview.UiState_.READY, + 'Trying to open pdf in preview when not in ready state: ' + + this.uiState_); + setIsVisible($('open-preview-app-throbber'), true); + this.previewArea_.showCustomMessage( localStrings.getString('openingPDFInPreview')); - } else if (printToPDF) { - sendPrintDocumentRequest(); - } else if (printWithCloudPrint) { - previewArea.showCustomMessage( - localStrings.getString('printWithCloudPrintWait')); - disableInputElementsInSidebar(); - } else { - isTabHidden = true; - chrome.send('hidePreview'); - } - return; - } - - if (printToPDF || previewAppRequested) { - sendPrintDocumentRequest(); - } else { - window.setTimeout(function() { sendPrintDocumentRequest(); }, 1000); - } -} - -/** - * Sends a message to cancel the pending print request. - */ -function cancelPendingPrintRequest() { - if (isTabHidden) - chrome.send('cancelPendingPrintRequest'); -} - -/** - * Sends a message to initiate print workflow. - */ -function sendPrintDocumentRequest() { - var printerList = $('printer-list'); - var printer = printerList[printerList.selectedIndex]; - chrome.send('saveLastPrinter', [printer.value, cloudprint.getData(printer)]); - - var settings = getSettings(); - if (cr.isMac && previewAppRequested) - settings.OpenPDFInPreview = true; - - chrome.send('print', [JSON.stringify(settings), - cloudprint.getPrintTicketJSON(printer)]); -} - -/** - * Loads the selected preview pages. - */ -function loadSelectedPages() { - pageSettings.updatePageSelection(); - var pageSet = pageSettings.previouslySelectedPages; - var pageCount = pageSet.length; - if (pageCount == 0 || currentPreviewUid == '') - return; - - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); - for (var i = 0; i < pageCount; i++) - onDidPreviewPage(pageSet[i] - 1, currentPreviewUid, lastPreviewRequestID); -} - -/** - * Updates the variables states for preview. - */ -function updateStateForPreview() { - if (!isTabHidden) - previewArea.showLoadingAnimation(); - - if (!hasPendingPreviewRequest && previewModifiable && - hasOnlyPageSettingsChanged()) { - loadSelectedPages(); - generateDraftData = false; - } else { - hasPendingPreviewRequest = true; - generateDraftData = true; - pageSettings.updatePageSelection(); - } - - printSettings.save(); - layoutSettings.updateState(); - previewArea.resetState(); - isPrintReadyMetafileReady = false; - isFirstPageLoaded = false; - - var totalPageCount = pageSettings.totalPageCount; - if (!previewModifiable && totalPageCount > 0) - generateDraftData = false; -} - -/** - * Asks the browser to generate a preview PDF based on current print settings. - */ -function requestPrintPreview() { - updateStateForPreview(); - var totalPageCount = pageSettings.totalPageCount; - var pageCount = totalPageCount || -1; - chrome.send('getPreview', [JSON.stringify(getSettingsWithRequestID()), - pageCount, - previewModifiable]); -} - -/** - * Called from PrintPreviewUI::OnFileSelectionCancelled to notify the print - * preview tab regarding the file selection cancel event. - */ -function fileSelectionCancelled() { - printHeader.enableCancelButton(); -} - -/** - * Called from PrintPreviewUI::OnFileSelectionCompleted to notify the print - * preview tab regarding the file selection completed event. - */ -function fileSelectionCompleted() { - // If the file selection is completed and the tab is not already closed it - // means that a pending print to pdf request exists. - disableInputElementsInSidebar(); - previewArea.showCustomMessage( - localStrings.getString('printingToPDFInProgress')); -} - -/** - * Set the default printer. If there is one, generate a print preview. - * @param {string} printerName Name of the default printer. Empty if none. - * @param {string} cloudPrintData Cloud print related data to restore if - * the default printer is a cloud printer. - */ -function setDefaultPrinter(printerName, cloudPrintData) { - // Add a placeholder value so the printer list looks valid. - addDestinationListOption('', '', true, true, true); - if (printerName) { - defaultOrLastUsedPrinterName = printerName; - if (cloudPrintData) { - cloudprint.setDefaultPrinter(printerName, - cloudPrintData, - addDestinationListOptionAtPosition, - doUpdateCloudPrinterCapabilities); - } else { - $('printer-list')[0].value = defaultOrLastUsedPrinterName; - updateControlsWithSelectedPrinterCapabilities(); - } - } - chrome.send('getPrinters'); -} - -/** - * Fill the printer list drop down. - * Called from PrintPreviewHandler::SetupPrinterList(). - * @param {Array} printers Array of printer info objects. - */ -function setPrinters(printers) { - var printerList = $('printer-list'); - // Remove empty entry added by setDefaultPrinter. - if (printerList[0] && printerList[0].textContent == '') - printerList.remove(0); - for (var i = 0; i < printers.length; ++i) { - var isDefault = (printers[i].deviceName == defaultOrLastUsedPrinterName); - addDestinationListOption(printers[i].printerName, printers[i].deviceName, - isDefault, false, false); - } - - if (printers.length != 0) - addDestinationListOption('', '', false, true, true); - - // Adding option for saving PDF to disk. - addDestinationListOption(localStrings.getString('printToPDF'), - PRINT_TO_PDF, - defaultOrLastUsedPrinterName == PRINT_TO_PDF, - false, - false); - addDestinationListOption('', '', false, true, true); - if (useCloudPrint) { - addDestinationListOption(localStrings.getString('printWithCloudPrint'), - PRINT_WITH_CLOUD_PRINT, - false, - false, - false); - addDestinationListOption('', '', false, true, true); - } - // Add options to manage printers. - if (!cr.isChromeOS) { - addDestinationListOption(localStrings.getString('managePrinters'), - MANAGE_LOCAL_PRINTERS, false, false, false); - } else if (useCloudPrint) { - // Fetch recent printers. - cloudprint.fetchPrinters(addDestinationListOptionAtPosition, false); - // Fetch the full printer list. - cloudprint.fetchPrinters(addDestinationListOptionAtPosition, true); - addDestinationListOption(localStrings.getString('managePrinters'), - MANAGE_CLOUD_PRINTERS, false, false, false); - } - - printerList.disabled = false; - - if (!hasRequestedPreview()) - updateControlsWithSelectedPrinterCapabilities(); -} - -/** - * Creates an option that can be added to the printer destination list. - * @param {string} optionText specifies the option text content. - * @param {string} optionValue specifies the option value. - * @param {boolean} isDefault is true if the option needs to be selected. - * @param {boolean} isDisabled is true if the option needs to be disabled. - * @param {boolean} isSeparator is true if the option is a visual separator and - * needs to be disabled. - * @return {Object} The created option. - */ -function createDestinationListOption(optionText, optionValue, isDefault, - isDisabled, isSeparator) { - var option = document.createElement('option'); - option.textContent = optionText; - option.value = optionValue; - option.selected = isDefault; - option.disabled = isSeparator || isDisabled; - // Adding attribute for improved accessibility. - if (isSeparator) - option.setAttribute('role', 'separator'); - return option; -} - -/** - * Adds an option to the printer destination list. - * @param {string} optionText specifies the option text content. - * @param {string} optionValue specifies the option value. - * @param {boolean} isDefault is true if the option needs to be selected. - * @param {boolean} isDisabled is true if the option needs to be disabled. - * @param {boolean} isSeparator is true if the option serves just as a - * separator. - * @return {Object} The created option. - */ -function addDestinationListOption(optionText, optionValue, isDefault, - isDisabled, isSeparator) { - var option = createDestinationListOption(optionText, - optionValue, - isDefault, - isDisabled, - isSeparator); - $('printer-list').add(option); - return option; -} - -/** - * Adds an option to the printer destination list at a specified position. - * @param {number} position The index in the printer-list wher the option - should be created. - * @param {string} optionText specifies the option text content. - * @param {string} optionValue specifies the option value. - * @param {boolean} isDefault is true if the option needs to be selected. - * @param {boolean} isDisabled is true if the option needs to be disabled. - * @param {boolean} isSeparator is true if the option is a visual separator and - * needs to be disabled. - * @return {Object} The created option. - */ -function addDestinationListOptionAtPosition(position, - optionText, - optionValue, - isDefault, - isDisabled, - isSeparator) { - var option = createDestinationListOption(optionText, - optionValue, - isDefault, - isDisabled, - isSeparator); - var printerList = $('printer-list'); - var before = printerList[position]; - printerList.add(option, before); - return option; -} -/** - * Sets the color mode for the PDF plugin. - * Called from PrintPreviewHandler::ProcessColorSetting(). - * @param {boolean} color is true if the PDF plugin should display in color. - */ -function setColor(color) { - if (!previewArea.pdfPlugin) - return; - - previewArea.pdfPlugin.grayscale(!color); - var printerList = $('printer-list'); - cloudprint.setColor(printerList[printerList.selectedIndex], color); -} - -/** - * Display an error message when print preview fails. - * Called from PrintPreviewMessageHandler::OnPrintPreviewFailed(). - */ -function printPreviewFailed() { - previewArea.displayErrorMessageAndNotify( - localStrings.getString('previewFailed')); -} - -/** - * Display an error message when encountered invalid printer settings. - * Called from PrintPreviewMessageHandler::OnInvalidPrinterSettings(). - */ -function invalidPrinterSettings() { - if (cr.isMac) { - if (previewAppRequested) { - $('open-preview-app-throbber').hidden = true; - previewArea.clearCustomMessageWithDots(); - previewAppRequested = false; - hasPendingPrintDocumentRequest = false; - enableInputElementsInSidebar(); - } - $('open-pdf-in-preview-link').disabled = true; - } - previewArea.displayErrorMessageAndNotify( - localStrings.getString('invalidPrinterSettings')); -} - -/** - * Called when the PDF plugin loads its document. - */ -function onPDFLoad() { - if (previewModifiable) { - setPluginPreviewPageCount(); - } else { - // If the source is pdf, print ready metafile is available only after - // loading the pdf in the plugin. - isPrintReadyMetafileReady = true; - } - // Instruct the plugin which page numbers to display in the page number - // indicator. - previewArea.pdfPlugin.setPageNumbers( - JSON.stringify(pageSettings.selectedPagesSet)); - cr.dispatchSimpleEvent(document, customEvents.PDF_LOADED); - isFirstPageLoaded = true; - checkAndHideOverlayLayerIfValid(); - sendPrintDocumentRequestIfNeeded(); - if (printAutomaticallyInKioskMode) - printHeader.printButton.click(); -} - -function setPluginPreviewPageCount() { - previewArea.pdfPlugin.printPreviewPageCount( - pageSettings.previouslySelectedPages.length); -} - -/** - * Update the page count and check the page range. - * Called from PrintPreviewUI::OnDidGetPreviewPageCount(). - * @param {number} pageCount The number of pages. - * @param {number} previewResponseId The preview request id that resulted in - * this response. - */ -function onDidGetPreviewPageCount(pageCount, previewResponseId) { - if (!isExpectedPreviewResponse(previewResponseId)) - return; - pageSettings.updateState(pageCount); - if (!previewModifiable && pageSettings.requestPrintPreviewIfNeeded()) - return; - - cr.dispatchSimpleEvent(document, customEvents.UPDATE_SUMMARY); -} - -/** - * @param {{contentWidth: number, contentHeight: number, marginLeft: number, - * marginRight: number, marginTop: number, marginBottom: number, - * printableAreaX: number, printableAreaY: number, - * printableAreaWidth: number, printableAreaHeight: number}} pageLayout - * Specifies default page layout details in points. - * @param {boolean} hasCustomPageSizeStyle Indicates whether the previewed - * document has a custom page size style. - */ -function onDidGetDefaultPageLayout(pageLayout, hasCustomPageSizeStyle) { - hasPageSizeStyle = hasCustomPageSizeStyle; - marginSettings.currentDefaultPageLayout = new print_preview.PageLayout( - pageLayout.contentWidth, - pageLayout.contentHeight, - pageLayout.marginLeft, - pageLayout.marginTop, - pageLayout.marginRight, - pageLayout.marginBottom); - headerFooterSettings.checkAndHideHeaderFooterOption( - pageLayout, marginSettings.selectedMarginsValue); -} - -/** - * This function sends a request to hide the overlay layer only if there is no - * pending print document request and we are not waiting for the print ready - * metafile. - */ -function checkAndHideOverlayLayerIfValid() { - var selectedPrinter = getSelectedPrinterName(); - var printToDialog = selectedPrinter == PRINT_TO_PDF || - selectedPrinter == PRINT_WITH_CLOUD_PRINT; - if ((printToDialog || !previewModifiable) && - !isPrintReadyMetafileReady && hasPendingPrintDocumentRequest) { - return; - } - previewArea.hideOverlayLayer(); -} - -/** - * Called when no pipelining previewed pages. - * @param {string} previewUid Preview unique identifier. - * @param {number} previewResponseId The preview request id that resulted in - * this response. - */ -function reloadPreviewPages(previewUid, previewResponseId) { - if (!isExpectedPreviewResponse(previewResponseId)) - return; - - if (!previewModifiable) - previewArea.createOrReloadPDFPlugin(PRINT_READY_DATA_INDEX); - cr.dispatchSimpleEvent(document, customEvents.UPDATE_PRINT_BUTTON); - checkAndHideOverlayLayerIfValid(); - var pageSet = pageSettings.previouslySelectedPages; - for (var i = 0; i < pageSet.length; i++) { - previewArea.pdfPlugin.loadPreviewPage( - getPageSrcURL(previewUid, pageSet[i] - 1), i); - } - - hasPendingPreviewRequest = false; - isPrintReadyMetafileReady = true; - previewArea.pdfLoaded = true; - sendPrintDocumentRequestIfNeeded(); -} - -/** - * Notification that a print preview page has been rendered. - * Check if the settings have changed and request a regeneration if needed. - * Called from PrintPreviewUI::OnDidPreviewPage(). - * @param {number} pageNumber The page number, 0-based. - * @param {string} previewUid Preview unique identifier. - * @param {number} previewResponseId The preview request id that resulted in - * this response. - */ -function onDidPreviewPage(pageNumber, previewUid, previewResponseId) { - if (!isExpectedPreviewResponse(previewResponseId)) - return; - - // Refactor - if (!previewModifiable) - return; - - if (pageSettings.requestPrintPreviewIfNeeded()) - return; - - var pageIndex = pageSettings.previouslySelectedPages.indexOf(pageNumber + 1); - if (pageIndex == -1) - return; - - currentPreviewUid = previewUid; - if (pageIndex == 0) - previewArea.createOrReloadPDFPlugin(pageNumber); - - previewArea.pdfPlugin.loadPreviewPage( - getPageSrcURL(previewUid, pageNumber), pageIndex); - - if (pageIndex + 1 == pageSettings.previouslySelectedPages.length) { - hasPendingPreviewRequest = false; - if (pageIndex != 0) - sendPrintDocumentRequestIfNeeded(); - } -} - -/** - * Update the print preview when new preview data is available. - * Create the PDF plugin as needed. - * Called from PrintPreviewUI::PreviewDataIsAvailable(). - * @param {string} previewUid Preview unique identifier. - * @param {number} previewResponseId The preview request id that resulted in - * this response. - */ -function updatePrintPreview(previewUid, previewResponseId) { - if (!isExpectedPreviewResponse(previewResponseId)) - return; - - if (!previewModifiable) { - // If the preview is not modifiable the plugin has not been created yet. - currentPreviewUid = previewUid; - hasPendingPreviewRequest = false; - previewArea.createOrReloadPDFPlugin(PRINT_READY_DATA_INDEX); - } else { - isPrintReadyMetafileReady = true; - } - - cr.dispatchSimpleEvent(document, customEvents.UPDATE_PRINT_BUTTON); - if (previewModifiable) - sendPrintDocumentRequestIfNeeded(); -} - -/** - * Checks to see if the requested print data is available for printing and - * sends a print document request if needed. - */ -function sendPrintDocumentRequestIfNeeded() { - if (!hasPendingPrintDocumentRequest || !isFirstPageLoaded) - return; - - // If the selected printer is PRINT_TO_PDF or PRINT_WITH_CLOUD_PRINT or - // the preview source is not modifiable, we need the print ready data for - // printing. If the preview source is modifiable, we need to wait till all - // the requested pages are loaded in the plugin for printing. - var selectedPrinter = getSelectedPrinterName(); - var printToDialog = selectedPrinter == PRINT_TO_PDF || - selectedPrinter == PRINT_WITH_CLOUD_PRINT; - if (((printToDialog || !previewModifiable) && !isPrintReadyMetafileReady) || - (previewModifiable && hasPendingPreviewRequest)) { - return; - } - - hasPendingPrintDocumentRequest = false; - - if (!areSettingsValid()) { - cancelPendingPrintRequest(); - return; - } - sendPrintDocumentRequest(); -} - -/** - * Check if only page selection has been changed since the last preview request - * and is valid. - * @return {boolean} true if the new page selection is valid. - */ -function hasOnlyPageSettingsChanged() { - var tempPrintSettings = new PrintSettings(); - tempPrintSettings.save(); - - return !!(printSettings.deviceName == tempPrintSettings.deviceName && - printSettings.isLandscape == tempPrintSettings.isLandscape && - printSettings.hasHeaderFooter == - tempPrintSettings.hasHeaderFooter && - pageSettings.hasPageSelectionChangedAndIsValid()); -} - -/** - * Called either when there is a scroll event or when the plugin size changes. - */ -function onPreviewPositionChanged() { - marginSettings.onPreviewPositionChanged(); -} - -/** - * @return {boolean} true if a compatible pdf plugin exists. - */ -function checkCompatiblePluginExists() { - var dummyPlugin = $('dummy-viewer'); - var pluginInterface = [dummyPlugin.onload, - dummyPlugin.goToPage, - dummyPlugin.removePrintButton, - dummyPlugin.loadPreviewPage, - dummyPlugin.printPreviewPageCount, - dummyPlugin.resetPrintPreviewUrl, - dummyPlugin.onPluginSizeChanged, - dummyPlugin.onScroll, - dummyPlugin.pageXOffset, - dummyPlugin.pageYOffset, - dummyPlugin.setZoomLevel, - dummyPlugin.setPageNumbers, - dummyPlugin.setPageXOffset, - dummyPlugin.setPageYOffset, - dummyPlugin.getHorizontalScrollbarThickness, - dummyPlugin.getVerticalScrollbarThickness, - dummyPlugin.getPageLocationNormalized, - dummyPlugin.getHeight, - dummyPlugin.getWidth]; - - for (var i = 0; i < pluginInterface.length; i++) { - if (!pluginInterface[i]) - return false; - } - return true; -} - -/** - * Sets the default values and sends a request to regenerate preview data. - * Resets the margin options only if |resetMargins| is true. - * @param {boolean} resetMargins True if margin settings should be resetted. - */ -function setDefaultValuesAndRegeneratePreview(resetMargins) { - if (resetMargins) - marginSettings.resetMarginsIfNeeded(); - pageSettings.resetState(); - requestPrintPreview(); -} - -/** - * Class that represents the state of the print settings. - * @constructor - */ -function PrintSettings() { - this.deviceName = ''; - this.isLandscape = ''; - this.hasHeaderFooter = ''; -} - -/** - * Takes a snapshot of the print settings. - */ -PrintSettings.prototype.save = function() { - this.deviceName = getSelectedPrinterName(); - this.isLandscape = layoutSettings.isLandscape(); - this.hasHeaderFooter = headerFooterSettings.hasHeaderFooter(); -}; - -/** - * Updates the title of the print preview tab according to |initiatorTabTitle|. - * @param {string} initiatorTabTitle The title of the initiator tab. - */ -function setInitiatorTabTitle(initiatorTabTitle) { - if (initiatorTabTitle == '') - return; - document.title = localStrings.getStringF( - 'printPreviewTitleFormat', initiatorTabTitle); -} - -/** - * Closes this print preview tab. - */ -function closePrintPreviewTab() { - window.removeEventListener('keydown', onKeyDown); - chrome.send('closePrintPreviewTab'); - chrome.send('DialogClose'); -} - -/** - * Pass certain directional keyboard events to the PDF viewer. - * @param {Event} e The keydown event. - */ -function tryToHandleDirectionKeyDown(e) { - // Make sure the PDF plugin is there. - if (!previewArea.pdfPlugin) - return; - - // We only care about: PageUp, PageDown, Left, Up, Right, Down. - if (!(e.keyCode == 33 || e.keyCode == 34 || - (e.keyCode >= 37 && e.keyCode <= 40))) { - return; - } - - // If the user is holding a modifier key, ignore. - if (e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) - return; - - // Don't handle the key event for these elements. - var tagName = document.activeElement.tagName; - if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'EMBED') - return; - - // For the most part, if any div of header was the last clicked element, - // then the active element is the body. Starting with the last clicked - // element, and work up the DOM tree to see if any element has a scrollbar. - // If there exists a scrollbar, do not handle the key event here. - var element = document.activeElement; - if (element == document.body) { - if (lastClickedElement) - element = lastClickedElement; - while (element) { - if (element.scrollHeight > element.clientHeight) + this.printDocumentOrOpenPdfPreview_(true /*isPdfPreview*/); + }, + + /** + * Called when the print header's print button is clicked. Prints the + * document. + * @private + */ + onPrintButtonClick_: function() { + assert(this.uiState_ == PrintPreview.UiState_.READY, + 'Trying to print when not in ready state: ' + this.uiState_); + this.printDocumentOrOpenPdfPreview_(false /*isPdfPreview*/); + }, + + /** + * Called when the print header's cancel button is clicked. Closes the + * print dialog. + * @private + */ + onCancelButtonClick_: function() { + this.close_(); + }, + + /** + * Consume escape key presses and ctrl + shift + p. Delegate everything else + * to the preview area. + * @param {KeyboardEvent} e The keyboard event. + * @private + */ + onKeyDown_: function(e) { + // Escape key closes the dialog. + if (e.keyCode == 27 && !e.shiftKey && !e.ctrlKey && !e.altKey && + !e.metaKey) { + this.close_(); + e.preventDefault(); return; - element = element.parentElement; + } + + // Ctrl + Shift + p / Mac equivalent. + if (e.keyCode == 80) { + if ((cr.isMac && e.metaKey && e.altKey && !e.shiftKey && !e.ctrlKey) || + (!cr.isMac && e.shiftKey && e.ctrlKey && !e.altKey && !e.metaKey)) { + this.openSystemPrintDialog_(); + e.preventDefault(); + return; + } + } + + // Pass certain directional keyboard events to the PDF viewer. + this.previewArea_.handleDirectionalKeyEvent(e); + }, + + /** + * Called when native layer receives invalid settings for a print request. + * @private + */ + onSettingsInvalid_: function() { + this.uiState_ = PrintPreview.UiState_.ERROR; + this.previewArea_.showCustomMessage( + localStrings.getString('invalidPrinterSettings')); + }, + + /** + * Called when the native layer dispatches a DISABLE_SCALING event. Updates + * the print ticket. + * @private + */ + onDisableScaling_: function() { + this.printTicketStore_.updateFitToPage(false); + }, + + /** + * Called when the user selects the "Manage printers..." option in the + * destination select. + * @private + */ + onManagePrinters_: function() { + if (cr.isChromeOS) { + this.nativeLayer_.startManageCloudPrinters(); + } else { + this.nativeLayer_.startManageLocalPrinters(); + } } - } - - // No scroll bar anywhere, or the active element is something else, like a - // button. Note: buttons have a bigger scrollHeight than clientHeight. - previewArea.pdfPlugin.sendKeyEvent(e.keyCode); - e.preventDefault(); -} - -/** - * Handle keyboard events. - * @param {KeyboardEvent} e The keyboard event. - */ -function onKeyDown(e) { - // Escape key closes the dialog. - if (e.keyCode == 27 && !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) { - printHeader.disableCancelButton(); - closePrintPreviewTab(); - e.preventDefault(); - } - // Ctrl + Shift + p / Mac equivalent. - if (e.keyCode == 80) { - if ((cr.isMac && e.metaKey && e.altKey && !e.shiftKey && !e.ctrlKey) || - (!cr.isMac && e.shiftKey && e.ctrlKey && !e.altKey && !e.metaKey)) { - window.removeEventListener('keydown', onKeyDown); - onSystemDialogLinkClicked(); - e.preventDefault(); - } - } - - tryToHandleDirectionKeyDown(e); -} - -window.addEventListener('DOMContentLoaded', onLoad); -window.addEventListener('keydown', onKeyDown); - -/// Pull in all other scripts in a single shot. + }; + + // Export + return { + PrintPreview: PrintPreview + }; +}); + +// Pull in all other scripts in a single shot. +<include src="data/page_number_set.js"/> +<include src="data/destination.js"/> +<include src="data/local_parsers.js"/> +<include src="data/cloud_parsers.js"/> +<include src="data/chromium_capabilities.js"/> +<include src="data/cloud_capabilities.js"/> +<include src="data/destination_store.js"/> +<include src="data/margins.js"/> +<include src="data/document_info.js"/> +<include src="data/printable_area.js"/> +<include src="data/measurement_system.js"/> +<include src="data/print_ticket_store.js"/> +<include src="data/coordinate2d.js"/> +<include src="data/size.js"/> +<include src="data/capabilities_holder.js"/> + +<include src="data/ticket_items/ticket_item.js"/> + +<include src="data/ticket_items/custom_margins.js"/> +<include src="data/ticket_items/collate.js"/> +<include src="data/ticket_items/color.js"/> +<include src="data/ticket_items/copies.js"/> +<include src="data/ticket_items/duplex.js"/> +<include src="data/ticket_items/header_footer.js"/> +<include src="data/ticket_items/landscape.js"/> +<include src="data/ticket_items/margins_type.js"/> +<include src="data/ticket_items/page_range.js"/> +<include src="data/ticket_items/fit_to_page.js"/> + +<include src="native_layer.js"/> <include src="print_preview_animations.js"/> -<include src="print_preview_cloud.js"/> +<include src="cloud_print_interface.js"/> <include src="print_preview_utils.js"/> <include src="print_header.js"/> -<include src="page_settings.js"/> -<include src="copies_settings.js"/> -<include src="header_footer_settings.js"/> -<include src="fit_to_page_settings.js"/> -<include src="layout_settings.js"/> -<include src="color_settings.js"/> -<include src="margin_settings.js"/> -<include src="margin_textbox.js"/> -<include src="margin_utils.js"/> -<include src="margins_ui.js"/> -<include src="margins_ui_pair.js"/> -<include src="more_options.js"/> -<include src="preview_area.js"/> + +<include src="settings/page_settings.js"/> +<include src="settings/copies_settings.js"/> +<include src="settings/layout_settings.js"/> +<include src="settings/color_settings.js"/> +<include src="settings/margin_settings.js"/> +<include src="settings/destination_settings.js"/> +<include src="settings/other_options_settings.js"/> + +<include src="previewarea/margin_control.js"/> +<include src="previewarea/margin_control_container.js"/> +<include src="previewarea/preview_area.js"/> +<include src="preview_generator.js"/> + +var printPreview = new print_preview.PrintPreview(); diff --git a/chrome/browser/resources/print_preview/print_preview_cloud.js b/chrome/browser/resources/print_preview/print_preview_cloud.js deleted file mode 100644 index 7c8b7c0..0000000 --- a/chrome/browser/resources/print_preview/print_preview_cloud.js +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright (c) 2012 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('cloudprint', function() { - - // The URL to use to access the cloud print servers. - // Set by a call to setBaseURL. - var cloudPrintBaseURL = ''; - - // Headers to set for most cloud print API calls. - var xCloudPrintURLHeader = {'Content-Type': - 'application/x-www-form-urlencoded', - 'X-CloudPrint-Proxy': 'ChromePrintPreview'}; - - // Headers to set when sending multipart data to cloud print APIs. - // Currently only used when submitting a job. - var xCloudPrintFormHeader = {'Content-Type': - 'multipart/form-data; boundary=----CloudPrintFormBoundaryjc9wuprokl8i', - 'X-CloudPrint-Proxy': 'ChromePrintPreview'}; - - // The last received XSRF token. This should be sent with each request - // to prevent XSRF. - var lastXSRFToken = ''; - - /** - * Sets the base URL to be used for communicating with cloud print - * servers. - * @param {string} cloudPrintURL The URL to use. - */ - function setBaseURL(cloudPrintURL) { - cloudPrintBaseURL = cloudPrintURL; - } - - /** - * Gets the base URL to be used for communicating with cloud print - * servers. - * @return {string} The URL. - */ - function getBaseURL() { - return cloudPrintBaseURL; - } - - /** - * Extracts the XSRF token from each response to be used in the next - * request. - * @param {XMLHttpRequest} xhr The object used to make the request. - * @return {string} The extracted XSRF token. - */ - function extractXSRFtoken(xhr) { - if (xhr.status == 200) { - var result = JSON.parse(xhr.responseText); - return result['xsrf_token']; - } else { - return null; - } - } - - /** - * Makes a request to cloud print servers. - * @param {string} method The HTTP method to be used. - * @param {string} action The cloud print API to call. - * @param {Array} headers Headers to send with the request. - * @param {string} body Body to be sent with POST requests. - * @param {function} callback Function to be called to process response. - * @param {boolean} async True if we want the request made asyncronously. - */ - function sendCloudPrintRequest(method, - action, - headers, - params, - body, - callback) { - var xhr = new XMLHttpRequest(); - if (callback != null) { - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - var updatedXSRFToken = extractXSRFtoken(xhr); - if (updatedXSRFToken != null) { - lastXSRFToken = updatedXSRFToken; - } - callback.call(this, xhr); - } - }; - } - var url = cloudPrintBaseURL + '/' + action; - if (params == null) { - params = new Array; - } - if (lastXSRFToken.length != 0) { - params.push('xsrf=' + lastXSRFToken); - } - if (params.length != 0) { - url = url + '?'; - for (param in params) { - url = url + params[param] + '&'; - } - } - xhr.open(method, url, true); - xhr.withCredentials = true; - if (headers) { - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - xhr.setRequestHeader(header, headers[header]); - } - } - } - xhr.send(body); - return xhr; - } - - /** - * Parse the response from the fetchPrinters call. - * @param {function} callback Function to be called to process response. - * @param {XMLHttpRequest} xhr The object used to make the request. - */ - function fetchPrintersResponse(callback, xhr) { - if (xhr.status == 200) { - var searchResult = JSON.parse(xhr.responseText); - if (searchResult['success']) { - var printerList = searchResult['printers']; - addCloudPrinters(printerList, callback); - return; - } - } - addCloudPrinters(null, callback); - } - - /** - * Retrieve the list of printers available via cloud print. - * @param {function} callback Function to be called to process response. - */ - function fetchPrinters(callback, all) { - var query = 'q=^recent'; - if (all) { - query = ''; - } - sendCloudPrintRequest('GET', - 'search', - xCloudPrintURLHeader, - [query], - null, - fetchPrintersResponse.bind(this, callback)); - } - - /** - * Handle the response from printing to cloud print. - * @param {function} callback Function to be called to process response. - * @param {XMLHttpRequest} xhr The object used to make the request. - */ - function printToCloudResponse(callback, xhr) { - if (xhr.status == 200) { - var printResult = JSON.parse(xhr.responseText); - if (printResult['success']) { - callback.call(); - } - } - // TODO(abodenha@chromium.org) Catch and handle failures. - } - - /** - * Send the current document to cloud print. - * @param {string} data The document to be printed. - * @param {function} callback Function to be called to process response. - */ - function printToCloud(data, callback) { - // TODO(abodenha@chromium.org) Make sure we have an XSRF token before - // sending a submit. Right now if the user clicks print before we - // complete any request we wont have an XSRF and the submit will fail. - sendCloudPrintRequest('POST', - 'submit', - xCloudPrintFormHeader, - null, - data, - printToCloudResponse.bind(this, callback)); - } - - /** - * Gets the JSON string used to control the behavior of the current - * print job. - * @param {Object} printer The printer object to get the ticket for. - * @return {string} The print ticket or null if not a cloud printer. - */ - function getPrintTicketJSON(printer) { - if (isCloudPrint(printer)) { - return JSON.stringify({'capabilities': - [printer.cloudPrintOptions.colorOption]}); - } else { - return null; - } - } - - /** - * Process the response from cloud print containing the capabilities - * for the printer. - * @param {function} callback Function to be called to process response. - * @param {Object} printer The printer object to get the capabilites for. - * @param {XMLHttpRequest} xhr The object used to make the request. - */ - function updatePrinterCapsResponse(callback, printer, xhr) { - if (xhr.status == 200) { - var printResult = JSON.parse(xhr.responseText); - if (printResult['success']) { - if (!printer.cloudPrintOptions) - printer.cloudPrintOptions = new Object; - printer.cloudPrintOptions.capsDownloaded = true; - printer.cloudPrintOptions.colorOption = null; - printer.cloudPrintOptions.colorIsDefault = false; - var detailedCapabilities = printResult.printers[0].capabilities; - for (var capability in detailedCapabilities) { - var cap = detailedCapabilities[capability]; - if (cap.name == 'ns1:Colors') { - printer.cloudPrintOptions.colorOption = new Object; - printer.cloudPrintOptions.colorOption.name = cap.name; - printer.cloudPrintOptions.colorOption.type = cap.type; - for (var option in cap.options) { - var opt = cap.options[option]; - if (opt.name == 'Color') { - printer.cloudPrintOptions.colorOnOption = opt; - } - if (opt.name == 'Grey_K') { - printer.cloudPrintOptions.colorOffOption = opt; - } - if (opt.default) { - printer.cloudPrintOptions.colorOption.options = [opt]; - printer.cloudPrintOptions.colorIsDefault = - opt.name == printer.cloudPrintOptions.colorOnOption.name; - } - } - } - } - callback.call(this, printer); - } - } - } - - /** - * Retrieve capabilities for a printer. - * @param {Object} printer The printer object to get the capabilities for. - * @param {function} callback Function to be called to process response. - */ - function updatePrinterCaps(printer, callback) { - if (isCloudPrint(printer) && !printer.cloudPrintOptions.capsDownloaded) { - sendCloudPrintRequest('GET', - 'printer?printerid=' + - printer.value + - '&output=json', - xCloudPrintURLHeader, - null, - null, - updatePrinterCapsResponse.bind(this, - callback, - printer)); - } else { - callback.call(this, printer); - } - } - - /** - * @param {Object} printer The printer object to get the data for. - * @return {boolean} true if the printer supports color. - */ - function supportsColor(printer) { - return isCloudPrint(printer) && - printer.cloudPrintOptions.colorOption != null; - } - - /** - * @param {Object} printer The printer object to get the data for. - * @return {boolean} true if the printer defaults to color. - */ - function colorIsDefault(printer) { - // For now assume that unsupported color means we just don't know - // and assume color. - return !supportsColor(printer) || - (isCloudPrint(printer) && printer.cloudPrintOptions.colorIsDefault); - } - - /** - * Turn color on or off for the specified printer. - * @param {Object} printer The printer object to turn color on/off for. - * @param {boolean} color True to turn color on. - */ - function setColor(printer, color) { - if (isCloudPrint(printer) && supportsColor(printer)) { - if (color) { - printer.cloudPrintOptions.colorOption.options = - [printer.cloudPrintOptions.colorOnOption]; - } else { - printer.cloudPrintOptions.colorOption.options = - [printer.cloudPrintOptions.colorOffOption]; - } - } - } - - /** - * Creates a cloud print printer and sets it as the default printer. - * @param {string} printer_name The name of the printer to create. - * @param {Object} cloud_print_data Data to be stored in cloudPrintOptions. - * @param {function} add_callback The callback to be called to add the new - * printer to the print preview UI. - * @param {function} update_caps_callback The callback to be called to update - * capabilities on the new printer. - */ - function setDefaultPrinter(printer_name, - cloud_print_data, - add_callback, - update_caps_callback) { - var printer = addCloudPrinters([JSON.parse(cloud_print_data)], - add_callback); - if (printer) - update_caps_callback(printer); - } - - /** - * Returns the data necessary to serialize a cloud print printer. - * @param {Object} printer The printer object to get data for. - * @return {string} A JSON string that can be used to recreate the - * cloud print portion of the printer object, or and empty string if - * there is no data to save. - */ - function getData(printer) { - if (isCloudPrint(printer)) { - return JSON.stringify(printer.cloudPrintOptions); - } else { - return ''; - } - } - - /** - * Test if a printer is a cloud print printer. - * @param {Object} printer The printer to test. - * @return {boolean} true iff the printer is a cloud print printer. - */ - function isCloudPrint(printer) { - return printer && printer.cloudPrintOptions != null; - } - - /** - * Mark a printer as a cloud print printer and record its name and id. - * @param {Object} printer The printer to mark. - * @param {string} name The user visible name of the printer. - * @param {string} id The id of the printer used by cloud print to - * identify it. - */ - function setCloudPrint(printer, name, id) { - if (!printer.cloudPrintOptions) { - printer.cloudPrintOptions = new Object; - } - printer.cloudPrintOptions.name = name; - printer.cloudPrintOptions.id = id; - } - - /** - * Test if a particular cloud printer has already been added to the - * printer dropdown. - * @param {string} id A unique value to track this printer. - * @return {boolean} True if |id| has previously been passed to - * trackCloudPrinterAdded. - */ - function cloudPrinterAlreadyAdded(id) { - return addedCloudPrinters[id]; - } - - /** - * Test if a particular printer has already been added to the printers - * dropdown. Records it if not. - * @param {string} id A unique value to track this printer. - * @return {boolean} False if adding this printer would exceed - * |maxCloudPrinters|. - */ - function trackCloudPrinterAdded(id) { - if (Object.keys(addedCloudPrinters).length < maxCloudPrinters) { - addedCloudPrinters[id] = true; - return true; - } else { - return false; - } - } - - /** - * Add cloud printers to the list drop down. - * Called from the cloudprint object on receipt of printer information from - * the cloud print server. - * @param {Array} printers Array of printer info objects. - * @return {Object} The currently selected printer. - */ - function addCloudPrinters(printers, addDestinationListOptionAtPosition) { - var isFirstPass = false; - var printerList = $('printer-list'); - - if (firstCloudPrintOptionPos == lastCloudPrintOptionPos) { - isFirstPass = true; - // Remove empty entry added by setDefaultPrinter. - if (printerList[0] && printerList[0].textContent == '') - printerList.remove(0); - } - if (printers != null) { - for (var i = 0; i < printers.length; i++) { - if (!cloudPrinterAlreadyAdded(printers[i]['id'])) { - if (!trackCloudPrinterAdded(printers[i]['id'])) { - break; - } - if (printers[i]['displayName'] && printers[i]['displayName'] != '') - var name = printers[i]['displayName']; - else - var name = printers[i]['name']; - - var option = addDestinationListOptionAtPosition( - lastCloudPrintOptionPos++, - name, - printers[i]['id'], - name == defaultOrLastUsedPrinterName, - false, - false); - cloudprint.setCloudPrint(option, - name, - printers[i]['id']); - } - } - } else { - if (!cloudPrinterAlreadyAdded(SIGN_IN)) { - addDestinationListOptionAtPosition(lastCloudPrintOptionPos++, - localStrings.getString('signIn'), - SIGN_IN, - false, - false, - false); - trackCloudPrinterAdded(SIGN_IN); - chrome.send('signIn'); - } - } - var selectedPrinter = printerList.selectedIndex; - if (selectedPrinter < 0) - return null; - return printerList.options[selectedPrinter]; - } - - return { - addCloudPrinters: addCloudPrinters, - colorIsDefault: colorIsDefault, - fetchPrinters: fetchPrinters, - getBaseURL: getBaseURL, - getData: getData, - getPrintTicketJSON: getPrintTicketJSON, - isCloudPrint: isCloudPrint, - printToCloud: printToCloud, - setBaseURL: setBaseURL, - setCloudPrint: setCloudPrint, - setColor: setColor, - setDefaultPrinter: setDefaultPrinter, - supportsColor: supportsColor, - updatePrinterCaps: updatePrinterCaps - }; -}); diff --git a/chrome/browser/resources/print_preview/print_preview_utils.js b/chrome/browser/resources/print_preview/print_preview_utils.js index c761650..35bb80a 100644 --- a/chrome/browser/resources/print_preview/print_preview_utils.js +++ b/chrome/browser/resources/print_preview/print_preview_utils.js @@ -187,81 +187,19 @@ function pageSetToPageRanges(pageSet) { } /** - * Constructs a url for getting a specific page. - * @param {string} id The id of the preview data. - * @param {number} pageNumber The number of the desired page. - * @return {string} The constructed URL. + * Shows or hides an element. + * @param {Element} element Element to show or hide. + * @param {boolean} isVisible Whether the element should be visible or not. */ -function getPageSrcURL(id, pageNumber) { - return 'chrome://print/' + id + '/' + pageNumber + '/print.pdf'; +function setIsVisible(element, isVisible) { + element.style.display = isVisible ? '' : 'none'; } /** - * Returns a random integer within the specified range, |endPointA| and - * |endPointB| are included. - * @param {number} endPointA One end of the desired range. - * @param {number} endPointB The other end of the desired range. - * @return {number} The random integer. + * @param {Array.<object>} array Array to check for item. + * @param {object} item Item to look for in array. + * @return {boolean} Whether the item is in the array. */ -function randomInteger(endPointA, endPointB) { - var from = Math.min(endPointA, endPointB); - var to = Math.max(endPointA, endPointB); - return Math.floor(Math.random() * (to - from + 1) + from); -} - -// Number of points per inch. -var POINTS_PER_INCH = 72; -// Number of points per millimeter. -var POINTS_PER_MILLIMETER = 2.83464567; - -/** - * Converts |value| from inches to points. - * @param {number} value The number in inches. - * @return {number} |value| in points. - */ -function convertInchesToPoints(value) { - return value * POINTS_PER_INCH; -} - -/** - * Converts |value| from points to inches. - * @param {number} value The number in points. - * @return {number} |value| in inches. - */ -function convertPointsToInches(value) { - return value / POINTS_PER_INCH; -} - -/** - * Converts |value| from millimeters to points. - * @param {number} value The number in millimeters. - * @return {number} |value| in points. - */ -function convertMillimetersToPoints(value) { - return value * POINTS_PER_MILLIMETER; -} - -/** - * Converts |value| from points to millimeters. - * @param {number} value The number in points. - * @return {number} |value| in millimeters. - */ -function convertPointsToMillimeters(value) { - return value / POINTS_PER_MILLIMETER; -} - -/** - * Parses |numberFormat| and extracts the symbols used for the thousands point - * and decimal point. - * @param {string} numberFormat The formatted version of the number 12345678. - * @return {!Array.<string>} The extracted symbols in the order - * [thousandsSymbol, decimalSymbol]]. For example - * parseNumberFormat("123,456.78") returns [",", "."]. - */ -function parseNumberFormat(numberFormat) { - if (!numberFormat) - numberFormat = ''; - var regex = /^(\d+)(\W{0,1})(\d+)(\W{0,1})(\d+)$/; - var matches = numberFormat.match(regex) || ['', '', ',', '', '.']; - return [matches[2], matches[4]]; +function arrayContains(array, item) { + return array.indexOf(item) != -1; } diff --git a/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs b/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs index adaee85..5f674b7 100644 --- a/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs +++ b/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -7,7 +7,9 @@ * @constructor * @extends {testing.Test} */ -function PrintPreviewUtilsUnitTest () {} +function PrintPreviewUtilsUnitTest () { + testing.Test.call(this); +} PrintPreviewUtilsUnitTest.prototype = { __proto__: testing.Test.prototype, @@ -15,7 +17,7 @@ PrintPreviewUtilsUnitTest.prototype = { /** @inheritDoc */ extraLibraries: [ 'print_preview_utils.js', - ], + ] }; TEST_F('PrintPreviewUtilsUnitTest', 'IsInteger', function() { @@ -113,21 +115,3 @@ TEST_F('PrintPreviewUtilsUnitTest', 'PageSetToPageRanges', function() { assertEquals(pageRanges[2].from, 11); assertEquals(pageRanges[2].to, 11); }); - -TEST_F('PrintPreviewUtilsUnitTest', 'ParseNumberFormat', function() { - assertTrue(areArraysEqual(['.', ','], parseNumberFormat('123.456,78'))); - assertTrue(areArraysEqual(['.', '.'], parseNumberFormat('123.456.78'))); - assertTrue(areArraysEqual([',', '.'], parseNumberFormat('123,456.78'))); - assertTrue(areArraysEqual([',', ','], parseNumberFormat('123,456,78'))); - assertTrue(areArraysEqual([' ', ','], parseNumberFormat('123 456,78'))); - assertTrue(areArraysEqual([' ', '.'], parseNumberFormat('123 456.78'))); - assertTrue(areArraysEqual([' ', ' '], parseNumberFormat('123 456 78'))); - assertTrue(areArraysEqual(['', ''], parseNumberFormat('123'))); - - assertTrue(areArraysEqual([',', '.'], parseNumberFormat('abcdef'))); - assertTrue(areArraysEqual([',', '.'], parseNumberFormat(null))); - assertTrue(areArraysEqual([',', '.'], parseNumberFormat(undefined))); - assertTrue(areArraysEqual([',', '.'], parseNumberFormat(''))); - assertTrue(areArraysEqual([',', '.'], parseNumberFormat('1'))); - assertTrue(areArraysEqual([',', '.'], parseNumberFormat('12'))); -}); diff --git a/chrome/browser/resources/print_preview/settings/color_settings.html b/chrome/browser/resources/print_preview/settings/color_settings.html new file mode 100644 index 0000000..7b742e9 --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/color_settings.html @@ -0,0 +1,17 @@ +<div id="color-settings" + class="color-settings two-column visible" + aria-hidden="false" + aria-live="polite"> + <h1 i18n-content="optionColor"></h1> + <div class="right-column"> + <div class="radio"><label> + <input class="color-settings-color-option" type="radio" name="color"/> + <span i18n-content="optionColor"></span> + </label></div> + <div class="radio"><label> + <input class="color-settings-bw-option" type="radio" name="color" + checked/> + <span i18n-content="optionBw"></span> + </label></div> + </div> +</div> diff --git a/chrome/browser/resources/print_preview/settings/color_settings.js b/chrome/browser/resources/print_preview/settings/color_settings.js new file mode 100644 index 0000000..eb35e65 --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/color_settings.js @@ -0,0 +1,133 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Creates a ColorSettings object. This object encapsulates all settings and + * logic related to color selection (color/bw). + * @param {!print_preview.PrintTicketStore} printTicketStore Used for writing + * to the print ticket. + * @constructor + * @extends {print_preview.Component} + */ + function ColorSettings(printTicketStore) { + print_preview.Component.call(this); + + /** + * Used for writing to the print ticket. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = printTicketStore; + }; + + /** + * CSS classes used by the color settings. + * @enum {string} + * @private + */ + ColorSettings.Classes_ = { + BW_OPTION: 'color-settings-bw-option', + COLOR_OPTION: 'color-settings-color-option' + }; + + ColorSettings.prototype = { + __proto__: print_preview.Component.prototype, + + set isEnabled(isEnabled) { + this.colorRadioButton_.disabled = !isEnabled; + this.bwRadioButton_.disabled = !isEnabled; + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + this.addEventListeners_(); + }, + + get colorRadioButton_() { + return this.getElement().getElementsByClassName( + ColorSettings.Classes_.COLOR_OPTION)[0]; + }, + + get bwRadioButton_() { + return this.getElement().getElementsByClassName( + ColorSettings.Classes_.BW_OPTION)[0]; + }, + + /** + * Adding listeners to all targets and UI controls. + * @private + */ + addEventListeners_: function() { + this.tracker.add( + this.colorRadioButton_, + 'click', + this.updatePrintTicket_.bind(this, true)); + this.tracker.add( + this.bwRadioButton_, + 'click', + this.updatePrintTicket_.bind(this, false)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.INITIALIZE, + this.onCapabilitiesChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, + this.onCapabilitiesChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE, + this.onCapabilitiesChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.TICKET_CHANGE, + this.onTicketChange_.bind(this)); + }, + + /** + * Updates print ticket with whether the document should be printed in + * color. + * @param {boolean} isColor Whether the document should be printed in color. + * @private + */ + updatePrintTicket_: function(isColor) { + this.printTicketStore_.updateColor(isColor); + }, + + /** + * Called when the ticket store's capabilities have changed. Shows or hides + * the color settings. + * @private + */ + onCapabilitiesChange_: function() { + if (this.printTicketStore_.hasColorCapability()) { + fadeInOption(this.getElement()); + var isColorEnabled = this.printTicketStore_.isColorEnabled(); + this.colorRadioButton_.checked = isColorEnabled; + this.bwRadioButton_.checked = !isColorEnabled; + } else { + fadeOutOption(this.getElement()); + } + this.getElement().setAttribute( + 'aria-hidden', !this.printTicketStore_.hasColorCapability()); + }, + + onTicketChange_: function() { + if (this.printTicketStore_.hasColorCapability()) { + var isColorEnabled = this.printTicketStore_.isColorEnabled(); + this.colorRadioButton_.checked = isColorEnabled; + this.bwRadioButton_.checked = !isColorEnabled; + } + } + }; + + // Export + return { + ColorSettings: ColorSettings + }; +}); diff --git a/chrome/browser/resources/print_preview/settings/copies_settings.css b/chrome/browser/resources/print_preview/settings/copies_settings.css new file mode 100644 index 0000000..129943f --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/copies_settings.css @@ -0,0 +1,56 @@ +/* Copyright (c) 2012 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. + */ + +#copies-settings .copies-settings-copies { + position: relative; + width: 2.75em; +} + +#copies-settings .copies-settings-copies.invalid { + background: rgb(255, 240, 240); + color: rgb(140, 20, 20); +} + +#copies-settings .copies-settings-increment:focus, +#copies-settings .copies-settings-decrement:focus, +.copies-settings:focus { + z-index: 1; +} + +#copies-settings .copies-settings-collate { + -webkit-padding-start: 16px; + display: inline-block; +} + +#copies-settings .copies-settings-increment, +#copies-settings .copies-settings-decrement { + -webkit-padding-end: 0; + -webkit-padding-start: 0; + font-weight: 600; + margin: 0; + min-width: 0; + position: relative; + width: 2em; +} + +#copies-settings .copies-settings-increment { + -webkit-margin-start: -5px; + border-radius: 0; +} + +#copies-settings .copies-settings-decrement { + -webkit-margin-start: -5px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 3px; + border-top-left-radius: 0; + border-top-right-radius: 3px; +} + +html[dir='rtl'] #copies-settings .copies-settings-decrement { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 0; + border-top-left-radius: 3px; + border-top-right-radius: 0; +} diff --git a/chrome/browser/resources/print_preview/settings/copies_settings.html b/chrome/browser/resources/print_preview/settings/copies_settings.html new file mode 100644 index 0000000..b8218df --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/copies_settings.html @@ -0,0 +1,31 @@ +<div id="copies-settings" class="copies-settings two-column visible"> + <h1 i18n-content="copiesLabel"></h1> + <div class="right-column"> + <div> + <input class="copies-settings-copies" type="text" value="1" maxlength="3"> + <button class="copies-settings-increment" + i18n-values="title:incrementTitle;">+</button> + <button class="copies-settings-decrement" + i18n-values="title:decrementTitle;">–</button> + <div class="copies-settings-collate checkbox" + aria-live="polite" hidden> + <label> + <input class="copies-settings-collate-checkbox" + type="checkbox" + checked/> + <span i18n-content="optionCollate"></span> + </label> + </div> + </div> + <span class="copies-settings-hint hint" + i18n-content="copiesInstruction" + aria-live="polite"> + </span> + <div class="checkbox"> + <label class="copies-settings-duplex" aria-live="polite"> + <input class="copies-settings-duplex-checkbox" type="checkbox"/> + <span i18n-content="optionTwoSided"></span> + </label> + </div> + </div> +</div> diff --git a/chrome/browser/resources/print_preview/settings/copies_settings.js b/chrome/browser/resources/print_preview/settings/copies_settings.js new file mode 100644 index 0000000..f2baecd --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/copies_settings.js @@ -0,0 +1,327 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Component that renders the copies settings UI. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to read and + * write the copies settings. + * @constructor + * @extends {print_preview.Component} + */ + function CopiesSettings(printTicketStore) { + print_preview.Component.call(this); + + /** + * Used for writing to the print ticket and validating inputted values. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = printTicketStore; + + /** + * Timeout used to delay processing of the copies input. + * @type {Object} + * @private + */ + this.textfieldTimeout_ = null; + + /** + * Whether this component is enabled or not. + * @type {boolean} + * @private + */ + this.isEnabled_ = true; + + /** + * Textfield for entering copies values. + * @type {HTMLInputElement} + * @private + */ + this.textfield_ = null; + + /** + * Increment button used to increment the copies value. + * @type {HTMLButtonElement} + * @private + */ + this.incrementButton_ = null; + + /** + * Decrement button used to decrement the copies value. + * @type {HTMLButtonElement} + * @private + */ + this.decrementButton_ = null; + + /** + * Container div for the collate checkbox. + * @type {HTMLDivElement} + * @private + */ + this.collateDiv_ = null; + + /** + * Checkbox used to enable/disable collation. + * @type {HTMLInputElement} + * @private + */ + this.collateCheckbox_ = null; + + /** + * Container div for the duplex checkbox. + * @type {HTMLDivElement} + * @private + */ + this.duplexDiv_ = null; + + /** + * Checkbox used to enable/disable duplexing. + * @type {HTMLInputElement} + * @private + */ + this.duplexCheckbox_ = null; + + /** + * Hint element used to show a hint when the copies value is invalid. + * @type {HTMLElement} + * @private + */ + this.hintEl_ = null; + }; + + /** + * CSS classes used by the component. + * @enum {string} + * @private + */ + CopiesSettings.Classes_ = { + COPIES: 'copies-settings-copies', + INCREMENT: 'copies-settings-increment', + DECREMENT: 'copies-settings-decrement', + HINT: 'copies-settings-hint', + COLLATE: 'copies-settings-collate', + COLLATE_CHECKBOX: 'copies-settings-collate-checkbox', + DUPLEX: 'copies-settings-duplex', + DUPLEX_CHECKBOX: 'copies-settings-duplex-checkbox' + }; + + /** + * Delay in milliseconds before processing the textfield. + * @type {number} + * @private + */ + CopiesSettings.TEXTFIELD_DELAY_ = 250; + + CopiesSettings.prototype = { + __proto__: print_preview.Component.prototype, + + /** @param {boolean} isEnabled Whether the copies settings is enabled. */ + set isEnabled(isEnabled) { + this.textfield_.disabled = !isEnabled; + this.collateCheckbox_.disabled = !isEnabled; + this.duplexCheckbox_.disabled = !isEnabled; + this.isEnabled_ = isEnabled; + if (isEnabled) { + this.updateState_(); + } else { + this.textfield_.disabled = true; + this.incrementButton_.disabled = true; + this.decrementButton_.disabled = true; + this.duplexCheckbox_.disabled = true; + } + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + + this.tracker.add( + this.textfield_, 'keyup', this.onTextfieldKeyUp_.bind(this)); + this.tracker.add( + this.textfield_, 'blur', this.onTextfieldBlur_.bind(this)); + this.tracker.add( + this.incrementButton_, 'click', this.onButtonClicked_.bind(this, 1)); + this.tracker.add( + this.decrementButton_, 'click', this.onButtonClicked_.bind(this, -1)); + this.tracker.add( + this.duplexCheckbox_, + 'click', + this.onDuplexCheckboxClick_.bind(this)); + this.tracker.add( + this.collateCheckbox_, + 'click', + this.onCollateCheckboxClick_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.INITIALIZE, + this.updateState_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, + this.updateState_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.TICKET_CHANGE, + this.updateState_.bind(this)); + }, + + /** @override */ + exitDocument: function() { + print_preview.Component.prototype.exitDocument.call(this); + this.textfield_ = null; + this.incrementButton_ = null; + this.decrementButton_ = null; + this.collateDiv_ = null; + this.collateCheckbox_ = null; + this.duplexDiv_ = null; + this.duplexCheckbox_ = null; + this.hintEl_ = null; + }, + + /** @override */ + decorateInternal: function() { + this.textfield_ = this.getElement().getElementsByClassName( + CopiesSettings.Classes_.COPIES)[0]; + this.incrementButton_ = this.getElement().getElementsByClassName( + CopiesSettings.Classes_.INCREMENT)[0]; + this.decrementButton_ = this.getElement().getElementsByClassName( + CopiesSettings.Classes_.DECREMENT)[0]; + this.collateDiv_ = this.getElement().getElementsByClassName( + CopiesSettings.Classes_.COLLATE)[0]; + this.collateCheckbox_ = this.getElement().getElementsByClassName( + CopiesSettings.Classes_.COLLATE_CHECKBOX)[0]; + this.duplexDiv_ = this.getElement().getElementsByClassName( + CopiesSettings.Classes_.DUPLEX)[0]; + this.duplexCheckbox_ = this.getElement().getElementsByClassName( + CopiesSettings.Classes_.DUPLEX_CHECKBOX)[0]; + this.hintEl_ = this.getElement().getElementsByClassName( + CopiesSettings.Classes_.HINT)[0]; + }, + + /** + * Updates the state of the copies settings UI controls. + * @private + */ + updateState_: function() { + if (!this.printTicketStore_.hasCopiesCapability()) { + fadeOutOption(this.getElement()); + return; + } + + if (this.textfield_.value != this.printTicketStore_.getCopiesStr()) { + this.textfield_.value = this.printTicketStore_.getCopiesStr(); + } + + var currentValueGreaterThan1 = false; + if (this.printTicketStore_.isCopiesValid()) { + this.textfield_.classList.remove('invalid'); + fadeOutElement(this.hintEl_); + this.hintEl_.setAttribute('aria-hidden', true); + var currentValue = parseInt(this.printTicketStore_.getCopiesStr()); + var currentValueGreaterThan1 = currentValue > 1; + this.incrementButton_.disabled = + !this.isEnabled_ || + !this.printTicketStore_.isCopiesValidForValue(currentValue + 1); + this.decrementButton_.disabled = + !this.isEnabled_ || + !this.printTicketStore_.isCopiesValidForValue(currentValue - 1); + } else { + this.textfield_.classList.add('invalid'); + this.hintEl_.setAttribute('aria-hidden', false); + fadeInElement(this.hintEl_); + this.incrementButton_.disabled = true; + this.decrementButton_.disabled = true; + } + + if (!(this.collateDiv_.hidden = + !this.printTicketStore_.hasCollateCapability() || + !currentValueGreaterThan1)) { + this.collateCheckbox_.checked = + this.printTicketStore_.isCollateEnabled(); + } + + // On Windows, some printers don't specify their duplex values in the + // printer schema. If the printer duplex value is UNKNOWN_DUPLEX_MODE, + // hide the two sided option in preview tab UI. + // Ref bug: http://crbug.com/89204 + if (!(this.duplexDiv_.hidden = + !this.printTicketStore_.hasDuplexCapability())) { + this.duplexCheckbox_.checked = this.printTicketStore_.isDuplexEnabled(); + } + + fadeInOption(this.getElement()); + }, + + /** + * Called when the duplex checkbox changes state. Updates the print ticket. + * @private + */ + onDuplexCheckboxClick_: function() { + this.printTicketStore_.updateDuplex(this.duplexCheckbox_.checked); + }, + + /** + * Called whenever the increment/decrement buttons are clicked. + * @param {number} delta Must be 1 for an increment button click and -1 for + * a decrement button click. + * @private + */ + onButtonClicked_: function(delta) { + // Assumes text field has a valid number. + var newValue = parseInt(this.textfield_.value) + delta; + this.printTicketStore_.updateCopies(newValue); + }, + + /** + * Called after a timeout after user input into the textfield. + * @private + */ + onTextfieldTimeout_: function() { + if (this.textfield_ != '') { + this.printTicketStore_.updateCopies(this.textfield_.value); + } + }, + + /** + * Called when a keyup event occurs on the textfield. Starts an input + * timeout. + * @param {Event} event Contains the pressed key. + * @private + */ + onTextfieldKeyUp_: function(event) { + if (this.textfieldTimeout_) { + clearTimeout(this.textfieldTimeout_); + } + this.textfieldTimeout_ = setTimeout( + this.onTextfieldTimeout_.bind(this), CopiesSettings.TEXTFIELD_DELAY_); + }, + + /** + * Called when the focus leaves the textfield. If the textfield is empty, + * its value is set to 1. + * @private + */ + onTextfieldBlur_: function() { + if (this.textfield_.value == '') { + this.printTicketStore_.updateCopies('1'); + } + }, + + /** + * Called when the collate checkbox is clicked. Updates the print ticket. + * @private + */ + onCollateCheckboxClick_: function() { + this.printTicketStore_.updateCollate(this.collateCheckbox_.checked); + } + }; + + // Export + return { + CopiesSettings: CopiesSettings + }; +}); diff --git a/chrome/browser/resources/print_preview/settings/destination_settings.html b/chrome/browser/resources/print_preview/settings/destination_settings.html new file mode 100644 index 0000000..55a9e47 --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/destination_settings.html @@ -0,0 +1,6 @@ +<div id="destination-settings" class="two-column visible"> + <h1 i18n-content="destinationLabel"></h1> + <div class="right-column"> + <select class="destination-settings-select"></select> + </div> +</div> diff --git a/chrome/browser/resources/print_preview/settings/destination_settings.js b/chrome/browser/resources/print_preview/settings/destination_settings.js new file mode 100644 index 0000000..4713d0a --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/destination_settings.js @@ -0,0 +1,199 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + // TODO(rltoscano): This class needs a throbber while loading the destination + // or another solution is persist the settings of the printer so that next + // load is fast. + + /** + * Component used to render the print destination. + * @param {!print_preview.DestinationStore} destinationStore Used to determine + * the selected destination. + * @constructor + * @extends {print_preview.Component} + */ + function DestinationSettings(destinationStore) { + print_preview.Component.call(this); + + /** + * Used to determine the selected destination. + * @type {!print_preview.DestinationStore} + * @private + */ + this.destinationStore_ = destinationStore; + }; + + /** + * Event types dispatched by the component. + * @enum {string} + */ + DestinationSettings.EventType = { + MANAGE_PRINTERS_SELECT: + 'print_preview.DestinationSettings.MANAGE_PRINTERS_SELECT' + }; + + /** + * CSS classes used by the component. + * @enum {string} + * @private + */ + DestinationSettings.Classes_ = { + SELECT: 'destination-settings-select' + }; + + /** + * Option value of the "Manage Printers..." select option. + * @type {string} + * @const + * @private + */ + DestinationSettings.MANAGE_ID_ = '__manage'; + + DestinationSettings.prototype = { + __proto__: print_preview.Component.prototype, + + set isEnabled(isEnabled) { + this.select_.disabled = !isEnabled; + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + this.tracker.add( + this.select_, 'change', this.onSelectChange_.bind(this)); + this.tracker.add( + this.destinationStore_, + print_preview.DestinationStore.EventType.DESTINATION_SELECT, + this.onDestinationSelect_.bind(this)); + this.tracker.add( + this.destinationStore_, + print_preview.DestinationStore.EventType.DESTINATIONS_INSERTED, + this.onDestinationsInserted_.bind(this)); + }, + + get select_() { + return this.getElement().getElementsByClassName( + DestinationSettings.Classes_.SELECT)[0]; + }, + + renderDestinations_: function() { + var select = this.select_; + select.innerHTML = ''; + var destinations = this.destinationStore_.destinations; + var selectedDestination = this.destinationStore_.selectedDestination; + var saveToPdfDest = null; + var printWithCloudPrintDest = null; + for (var dest, i = 0; dest = destinations[i]; i++) { + if (dest.id == print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) { + saveToPdfDest = dest; + continue; + } + if (dest.isPrintWithCloudPrint) { + printWithCloudPrintDest = dest; + continue; + } + var optionEl = document.createElement('option'); + optionEl.value = dest.id; + optionEl.selected = + selectedDestination && selectedDestination.id == dest.id; + optionEl.textContent = dest.displayName; + select.appendChild(optionEl); + } + + // Add special destinations. + if (saveToPdfDest) { + select.appendChild(this.createSeparatorOption_()); + var printToPdfOptionEl = document.createElement('option'); + printToPdfOptionEl.value = saveToPdfDest.id; + printToPdfOptionEl.selected = + selectedDestination && selectedDestination.id == saveToPdfDest.id; + printToPdfOptionEl.textContent = saveToPdfDest.displayName; + select.appendChild(printToPdfOptionEl); + } + if (printWithCloudPrintDest) { + select.appendChild(this.createSeparatorOption_()); + var printWithCloudPrintOptionEl = document.createElement('option'); + printWithCloudPrintOptionEl.value = printWithCloudPrintDest.id; + printWithCloudPrintOptionEl.selected = + selectedDestination && + selectedDestination.id == printWithCloudPrintDest.id; + printWithCloudPrintOptionEl.textContent = + printWithCloudPrintDest.displayName; + select.appendChild(printWithCloudPrintOptionEl); + } + select.appendChild(this.createSeparatorOption_()); + var manageOptionEl = document.createElement('option'); + manageOptionEl.value = DestinationSettings.MANAGE_ID_; + manageOptionEl.textContent = localStrings.getString('managePrinters'); + select.appendChild(manageOptionEl); + }, + + createSeparatorOption_: function() { + var sep = document.createElement('option'); + sep.disabled = true; + sep.role = 'separator'; + return sep; + }, + + /** + * Called when a destination is selected. Selects the corresponding option. + * @private + */ + onDestinationSelect_: function() { + var select = this.select_; + if (select.options.length > 0) { + select.options[select.selectedIndex].selected = false; + } + var selectedDestination = this.destinationStore_.selectedDestination; + for (var option, i = 0; option = select.options[i]; i++) { + if (selectedDestination.id == option.value) { + option.selected = true; + break; + } + } + }, + + /** + * Called when destinations are inserted into the destination store. Updates + * the select element. + * @private + */ + onDestinationsInserted_: function() { + this.renderDestinations_(); + }, + + /** + * Called when the select element changes options. Selects the corresponding + * print destination. + * @private + */ + onSelectChange_: function() { + var select = this.select_; + var selectedDestId = select.options[select.selectedIndex].value; + + if (selectedDestId == DestinationSettings.MANAGE_ID_) { + cr.dispatchSimpleEvent( + this, DestinationSettings.EventType.MANAGE_PRINTERS_SELECT); + // Select first in the list. + this.destinationStore_.selectDestination( + this.destinationStore_.destinations[0]); + } else { + var destinations = this.destinationStore_.destinations; + for (var dest, i = 0; dest = destinations[i]; i++) { + if (dest.id == selectedDestId) { + this.destinationStore_.selectDestination(dest); + break; + } + } + } + } + }; + + return { + DestinationSettings: DestinationSettings + }; +}); diff --git a/chrome/browser/resources/print_preview/settings/layout_settings.html b/chrome/browser/resources/print_preview/settings/layout_settings.html new file mode 100644 index 0000000..3425e22b --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/layout_settings.html @@ -0,0 +1,18 @@ +<div id="layout-settings" class="two-column visible layout-settings"> + <h1 i18n-content="layoutLabel"></h1> + <div class="right-column"> + <div class="radio"><label> + <input class="layout-settings-portrait-radio" + type="radio" + name="layout" + checked/> + <span i18n-content="optionPortrait"></span> + </label></div> + <div class="radio"><label> + <input class="layout-settings-landscape-radio" + type="radio" + name="layout"/> + <span i18n-content="optionLandscape"></span> + </label></div> + </div> +</div> diff --git a/chrome/browser/resources/print_preview/settings/layout_settings.js b/chrome/browser/resources/print_preview/settings/layout_settings.js new file mode 100644 index 0000000..cba29bf --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/layout_settings.js @@ -0,0 +1,124 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Creates a LayoutSettings object. This object encapsulates all settings and + * logic related to layout mode (portrait/landscape). + * @param {!print_preview.PrintTicketStore} printTicketStore Used to get the + * layout written to the print ticket. + * @constructor + * @extends {print_preview.Component} + */ + function LayoutSettings(printTicketStore) { + print_preview.Component.call(this); + + /** + * Used to get the layout written to the print ticket. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = printTicketStore; + }; + + /** + * CSS classes used by the layout settings. + * @enum {string} + * @private + */ + LayoutSettings.Classes_ = { + LANDSCAPE_RADIO: 'layout-settings-landscape-radio', + PORTRAIT_RADIO: 'layout-settings-portrait-radio' + }; + + LayoutSettings.prototype = { + __proto__: print_preview.Component.prototype, + + /** @param {boolean} isEnabled Whether this component is enabled. */ + set isEnabled(isEnabled) { + this.landscapeRadioButton_.disabled = !isEnabled; + this.portraitRadioButton_.disabled = !isEnabled; + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + this.tracker.add( + this.portraitRadioButton_, + 'click', + this.onLayoutButtonClick_.bind(this)); + this.tracker.add( + this.landscapeRadioButton_, + 'click', + this.onLayoutButtonClick_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.TICKET_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.INITIALIZE, + this.onPrintTicketStoreChange_.bind(this)); + }, + + /** + * @return {HTMLInputElement} The portrait orientation radio button. + * @private + */ + get portraitRadioButton_() { + return this.getElement().getElementsByClassName( + LayoutSettings.Classes_.PORTRAIT_RADIO)[0]; + }, + + /** + * @return {HTMLInputElement} The landscape orientation radio button. + * @private + */ + get landscapeRadioButton_() { + return this.getElement().getElementsByClassName( + LayoutSettings.Classes_.LANDSCAPE_RADIO)[0]; + }, + + /** + * Called when one of the radio buttons is clicked. Updates the print ticket + * store. + * @private + */ + onLayoutButtonClick_: function() { + this.printTicketStore_.updateOrientation( + this.landscapeRadioButton_.checked); + }, + + /** + * Called when the print ticket store changes state. Updates the state of + * the radio buttons and hides the setting if necessary. + * @private + */ + onPrintTicketStoreChange_: function() { + if (this.printTicketStore_.hasOrientationCapability()) { + var isLandscapeEnabled = this.printTicketStore_.isLandscapeEnabled(); + this.portraitRadioButton_.checked = !isLandscapeEnabled; + this.landscapeRadioButton_.checked = isLandscapeEnabled; + fadeInOption(this.getElement()); + } else { + fadeOutOption(this.getElement()); + } + } + }; + + // Export + return { + LayoutSettings: LayoutSettings + }; +}); diff --git a/chrome/browser/resources/print_preview/margin_settings.html b/chrome/browser/resources/print_preview/settings/margin_settings.html index 80d98d87..b3faaa3 100644 --- a/chrome/browser/resources/print_preview/margin_settings.html +++ b/chrome/browser/resources/print_preview/settings/margin_settings.html @@ -1,7 +1,9 @@ -<div id="margins-option" class="two-column visible"> +<div id="margin-settings" class="two-column visible margin-settings"> <h1 i18n-content="marginsLabel"></h1> <div class="right-column"> - <select id="margin-list"> + <select class="margin-settings-select"> + <!-- The order of these options must match the natural order of their + values, which come from print_preview.ticket_items.MarginsType.Value. --> <option i18n-content="defaultMargins" value="0" selected></option> <option i18n-content="noMargins" value="1"></option> <option i18n-content="minimumMargins" value="2"></option> diff --git a/chrome/browser/resources/print_preview/settings/margin_settings.js b/chrome/browser/resources/print_preview/settings/margin_settings.js new file mode 100644 index 0000000..54e5699 --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/margin_settings.js @@ -0,0 +1,112 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Creates a MarginSettings object. This object encapsulates all settings and + * logic related to the margins mode. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to get + * ticket margins value. + * @constructor + * @extends {print_preview.Component} + */ + function MarginSettings(printTicketStore) { + print_preview.Component.call(this); + + /** + * Used to get ticket margins value. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = printTicketStore; + }; + + /** + * CSS classes used by the margin settings component. + * @enum {string} + * @private + */ + MarginSettings.Classes_ = { + SELECT: 'margin-settings-select' + }; + + MarginSettings.prototype = { + __proto__: print_preview.Component.prototype, + + /** @param {boolean} isEnabled Whether this component is enabled. */ + set isEnabled(isEnabled) { + this.select_.disabled = !isEnabled; + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + this.tracker.add( + this.select_, 'change', this.onSelectChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.TICKET_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + }, + + /** + * @return {HTMLSelectElement} Select element containing the margin options. + * @private + */ + get select_() { + return this.getElement().getElementsByClassName( + MarginSettings.Classes_.SELECT)[0]; + }, + + /** + * Called when the select element is changed. Updates the print ticket + * margin type. + * @private + */ + onSelectChange_: function() { + var select = this.select_; + var marginsType = + /** @type {print_preview.ticket_items.MarginsType.Value} */ ( + select.selectedIndex); + this.printTicketStore_.updateMarginsType(marginsType); + }, + + /** + * Called when the print ticket store changes. Selects the corresponding + * select option. + * @private + */ + onPrintTicketStoreChange_: function() { + if (this.printTicketStore_.hasMarginsCapability()) { + var select = this.select_; + var marginsType = this.printTicketStore_.getMarginsType(); + var selectedMarginsType = + /** @type {print_preview.ticket_items.MarginsType.Value} */ ( + select.selectedIndex); + if (marginsType != selectedMarginsType) { + select.options[selectedMarginsType].selected = false; + select.options[marginsType].selected = true; + } + fadeInOption(this.getElement()); + } else { + fadeOutOption(this.getElement()); + } + } + }; + + // Export + return { + MarginSettings: MarginSettings + }; +}); diff --git a/chrome/browser/resources/print_preview/settings/other_options_settings.html b/chrome/browser/resources/print_preview/settings/other_options_settings.html new file mode 100644 index 0000000..623dfa0 --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/other_options_settings.html @@ -0,0 +1,20 @@ +<div id="other-options-settings" + class="other-options-settings two-column visible"> + <h1 i18n-content="optionsLabel"></h1> + <div class="right-column checkbox"> + <div class="other-options-settings-header-footer"> + <label> + <input class="other-options-settings-header-footer-checkbox" + type="checkbox" /> + <span i18n-content="optionHeaderFooter"></span> + </label> + </div> + <div class="other-options-settings-fit-to-page"> + <label> + <input class="other-options-settings-fit-to-page-checkbox" + type="checkbox" /> + <span i18n-content="optionFitToPage"></span> + </label> + </div> + </div> +</div> diff --git a/chrome/browser/resources/print_preview/settings/other_options_settings.js b/chrome/browser/resources/print_preview/settings/other_options_settings.js new file mode 100644 index 0000000..621b62b --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/other_options_settings.js @@ -0,0 +1,172 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * UI component that renders checkboxes for various print options. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to monitor + * the state of the print ticket. + * @constructor + * @extends {print_preview.Component} + */ + function OtherOptionsSettings(printTicketStore) { + print_preview.Component.call(this); + + /** + * Used to monitor the state of the print ticket. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = printTicketStore; + + /** + * Header footer container element. + * @type {HTMLElement} + * @private + */ + this.headerFooterEl_ = null; + + /** + * Header footer checkbox. + * @type {HTMLInputElement} + * @private + */ + this.headerFooterCheckbox_ = null; + + /** + * Fit-to-page container element. + * @type {HTMLElement} + * @private + */ + this.fitToPageEl_ = null; + + /** + * Fit-to-page checkbox. + * @type {HTMLInputElement} + * @private + */ + this.fitToPageCheckbox_ = null; + }; + + /** + * CSS classes used by the other options component. + * @enum {string} + * @private + */ + OtherOptionsSettings.Classes_ = { + FIT_TO_PAGE: 'other-options-settings-fit-to-page', + FIT_TO_PAGE_CHECKBOX: 'other-options-settings-fit-to-page-checkbox', + HEADER_FOOTER: 'other-options-settings-header-footer', + HEADER_FOOTER_CHECKBOX: 'other-options-settings-header-footer-checkbox' + }; + + OtherOptionsSettings.prototype = { + __proto__: print_preview.Component.prototype, + + set isEnabled(isEnabled) { + this.headerFooterCheckbox_.disabled = !isEnabled; + this.fitToPageCheckbox_.disabled = !isEnabled; + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + this.tracker.add( + this.headerFooterCheckbox_, + 'click', + this.onHeaderFooterCheckboxClick_.bind(this)); + this.tracker.add( + this.fitToPageCheckbox_, + 'click', + this.onFitToPageCheckboxClick_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.INITIALIZE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.TICKET_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + }, + + /** @override */ + exitDocument: function() { + print_preview.Component.prototype.exitDocument.call(this); + this.headerFooterEl_ = null; + this.headerFooterCheckbox_ = null; + this.fitToPageEl_ = null; + this.fitToPageCheckbox_ = null; + }, + + /** @override */ + decorateInternal: function() { + this.headerFooterEl_ = this.getElement().getElementsByClassName( + OtherOptionsSettings.Classes_.HEADER_FOOTER)[0]; + this.headerFooterCheckbox_ = this.getElement().getElementsByClassName( + OtherOptionsSettings.Classes_.HEADER_FOOTER_CHECKBOX)[0]; + this.fitToPageEl_ = this.getElement().getElementsByClassName( + OtherOptionsSettings.Classes_.FIT_TO_PAGE)[0]; + this.fitToPageCheckbox_ = this.getElement().getElementsByClassName( + OtherOptionsSettings.Classes_.FIT_TO_PAGE_CHECKBOX)[0]; + }, + + /** + * Called when the header-footer checkbox is clicked. Updates the print + * ticket. + * @private + */ + onHeaderFooterCheckboxClick_: function() { + this.printTicketStore_.updateHeaderFooter( + this.headerFooterCheckbox_.checked); + }, + + /** + * Called when the fit-to-page checkbox is clicked. Updates the print + * ticket. + * @private + */ + onFitToPageCheckboxClick_: function() { + this.printTicketStore_.updateFitToPage( + this.fitToPageCheckbox_.checked); + }, + + /** + * Called when the print ticket store has changed. Hides or shows the + * setting. + * @private + */ + onPrintTicketStoreChange_: function() { + setIsVisible(this.headerFooterEl_, + this.printTicketStore_.hasHeaderFooterCapability()); + this.headerFooterCheckbox_.checked = + this.printTicketStore_.isHeaderFooterEnabled(); + setIsVisible(this.fitToPageEl_, + this.printTicketStore_.hasFitToPageCapability()); + this.fitToPageCheckbox_.checked = + this.printTicketStore_.isFitToPageEnabled(); + + if (this.printTicketStore_.hasHeaderFooterCapability() || + this.printTicketStore_.hasFitToPageCapability()) { + fadeInOption(this.getElement()); + } else { + fadeOutOption(this.getElement()); + } + } + }; + + // Export + return { + OtherOptionsSettings: OtherOptionsSettings + }; +}); diff --git a/chrome/browser/resources/print_preview/settings/page_settings.css b/chrome/browser/resources/print_preview/settings/page_settings.css new file mode 100644 index 0000000..cabdf7f --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/page_settings.css @@ -0,0 +1,13 @@ +/* Copyright (c) 2012 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. + */ + +#page-settings .page-settings-custom-input.invalid { + background: rgb(255, 240, 240); + color: rgb(140, 20, 20); +} + +#page-settings .page-settings-print-pages-div { + white-space: nowrap; +} diff --git a/chrome/browser/resources/print_preview/settings/page_settings.html b/chrome/browser/resources/print_preview/settings/page_settings.html new file mode 100644 index 0000000..76a8aef --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/page_settings.html @@ -0,0 +1,23 @@ +<div id="page-settings" class="two-column visible page-settings"> + <h1 i18n-content="pagesLabel"></h1> + <div class="right-column"> + <div class="radio"><label> + <input class="page-settings-all-radio" name="pages" checked type="radio"> + <span i18n-content="optionAllPages"></span> + </label></div> + <div> + <div class="page-settings-print-pages-div"> + <input class="page-settings-custom-radio" + name="pages" + type="radio" + i18n-values="aria-label:printPagesLabel;"/> + <input class="page-settings-custom-input" + type="text" + i18n-values="placeholder:examplePageRangeText"/> + </div> + <span class="page-settings-custom-hint hint suggestion" + aria-hidden="true" + aria-live="polite"></span> + </div> + </div> +</div> diff --git a/chrome/browser/resources/print_preview/settings/page_settings.js b/chrome/browser/resources/print_preview/settings/page_settings.js new file mode 100644 index 0000000..1489c6b --- /dev/null +++ b/chrome/browser/resources/print_preview/settings/page_settings.js @@ -0,0 +1,247 @@ +// Copyright (c) 2012 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() { + 'use strict'; + + /** + * Creates a PageSettings object. This object encapsulates all settings and + * logic related to page selection. + * @param {!print_preview.PrintTicketStore} printTicketStore Used to read and + * write page range settings. + * @constructor + * @extends {print_preview.Component} + */ + function PageSettings(printTicketStore) { + print_preview.Component.call(this); + + /** + * Used to read and write page range settings. + * @type {!print_preview.PrintTicketStore} + * @private + */ + this.printTicketStore_ = printTicketStore; + + /** + * Timeout used to delay processing of the custom page range input. + * @type {Object} + * @private + */ + this.customInputTimeout_ = null; + + /** + * Custom page range input. + * @type {HTMLInputElement} + * @private + */ + this.customInput_ = null; + + /** + * Custom page range radio button. + * @type {HTMLInputElement} + * @private + */ + this.customRadio_ = null; + + /** + * All page rage radio button. + * @type {HTMLInputElement} + * @private + */ + this.allRadio_ = null; + + /** + * Container of a hint to show when the custom page range is invalid. + * @type {HTMLElement} + * @private + */ + this.customHintEl_ = null; + }; + + /** + * CSS classes used by the page settings. + * @enum {string} + * @private + */ + PageSettings.Classes_ = { + ALL_RADIO: 'page-settings-all-radio', + CUSTOM_HINT: 'page-settings-custom-hint', + CUSTOM_INPUT: 'page-settings-custom-input', + CUSTOM_RADIO: 'page-settings-custom-radio' + }; + + /** + * Delay in milliseconds before processing custom page range input. + * @type {number} + * @private + */ + PageSettings.CUSTOM_INPUT_DELAY_ = 500; + + PageSettings.prototype = { + __proto__: print_preview.Component.prototype, + + set isEnabled(isEnabled) { + this.customInput_.disabled = !isEnabled; + this.allRadio_.disabled = !isEnabled; + this.customRadio_.disabled = !isEnabled; + }, + + /** @override */ + enterDocument: function() { + print_preview.Component.prototype.enterDocument.call(this); + this.tracker.add( + this.allRadio_, 'click', this.onAllRadioClick_.bind(this)); + this.tracker.add( + this.customRadio_, 'click', this.onCustomRadioClick_.bind(this)); + this.tracker.add( + this.customInput_, 'blur', this.onCustomInputBlur_.bind(this)); + this.tracker.add( + this.customInput_, 'keyup', this.onCustomInputKeyUp_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.TICKET_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + this.tracker.add( + this.printTicketStore_, + print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE, + this.onPrintTicketStoreChange_.bind(this)); + }, + + /** @override */ + exitDocument: function() { + print_preview.Component.prototype.exitDocument.call(this); + this.customInput_ = null; + this.customRadio_ = null; + this.allRadio_ = null; + this.customHintEl_ = null; + }, + + /** @override */ + decorateInternal: function() { + this.customInput_ = this.getElement().getElementsByClassName( + PageSettings.Classes_.CUSTOM_INPUT)[0]; + this.allRadio_ = this.getElement().getElementsByClassName( + PageSettings.Classes_.ALL_RADIO)[0]; + this.customRadio_ = this.getElement().getElementsByClassName( + PageSettings.Classes_.CUSTOM_RADIO)[0]; + this.customHintEl_ = this.getElement().getElementsByClassName( + PageSettings.Classes_.CUSTOM_HINT)[0]; + this.customHintEl_.textContent = localStrings.getStringF( + 'pageRangeInstruction', + localStrings.getString('examplePageRangeText')); + }, + + /** + * @param {boolean} Whether the custom hint is visible. + * @private + */ + setInvalidStateVisible_: function(isVisible) { + if (isVisible) { + this.customInput_.classList.add('invalid'); + this.customHintEl_.setAttribute('aria-hidden', 'false'); + fadeInElement(this.customHintEl_); + } else { + this.customInput_.classList.remove('invalid'); + fadeOutElement(this.customHintEl_); + this.customHintEl_.setAttribute('aria-hidden', 'true'); + } + }, + + /** + * Called when the all radio button is clicked. Updates the print ticket. + * @private + */ + onAllRadioClick_: function() { + this.printTicketStore_.updatePageRange(''); + }, + + /** + * Called when the custom radio button is clicked. Updates the print ticket. + * @private + */ + onCustomRadioClick_: function() { + this.customInput_.focus(); + this.printTicketStore_.updatePageRange(this.customInput_.value); + }, + + /** + * Called when the custom input is blurred. Enables the all radio button if + * the custom input is empty. + * @private + */ + onCustomInputBlur_: function() { + if (this.customInput_.value == '') { + this.allRadio_.checked = true; + this.customRadio_.checked = false; + } + }, + + /** + * Called when a key is pressed on the custom input. + * @param {Event} event Contains the key that was pressed. + * @private + */ + onCustomInputKeyUp_: function(event) { + if (this.customInputTimeout_) { + clearTimeout(this.customInputTimeout_); + } + if (event.keyIdentifier == 'Enter') { + this.printTicketStore_.updatePageRange(this.customInput_.value); + } else { + this.allRadio_.checked = false; + this.customRadio_.checked = true; + this.customInputTimeout_ = setTimeout( + this.onCustomInputTimeout_.bind(this), + PageSettings.CUSTOM_INPUT_DELAY_); + } + }, + + /** + * Called after a delay following a key press in the custom input. + * @private + */ + onCustomInputTimeout_: function() { + this.customInputTimeout_ = null; + if (this.customRadio_.checked) { + this.printTicketStore_.updatePageRange(this.customInput_.value); + } + }, + + /** + * Called when the print ticket changes. Updates the state of the component. + * @private + */ + onPrintTicketStoreChange_: function() { + if (this.printTicketStore_.hasPageRangeCapability()) { + var pageRangeStr = this.printTicketStore_.getPageRangeStr(); + if (pageRangeStr || this.customRadio_.checked) { + if (!document.hasFocus() || + document.activeElement != this.customInput_) { + this.customInput_.value = pageRangeStr; + } + this.customRadio_.checked = true; + this.allRadio_.checked = false; + this.setInvalidStateVisible_( + !this.printTicketStore_.isPageRangeValid()); + } else { + this.allRadio_.checked = true; + this.customRadio_.checked = false; + this.setInvalidStateVisible_(false); + } + fadeInOption(this.getElement()); + } else { + fadeOutOption(this.getElement()); + } + } + }; + + // Export + return { + PageSettings: PageSettings + }; +}); |