| // Copyright (c) 2011 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. |
| |
| var localStrings = new LocalStrings(); |
| var hasPDFPlugin = true; |
| |
| // The total page count of the previewed document regardless of which pages the |
| // user has selected. |
| var totalPageCount = -1; |
| |
| // The previously selected pages by the user. It is used in |
| // onPageSelectionMayHaveChanged() to make sure that a new preview is not |
| // requested more often than necessary. |
| var previouslySelectedPages = []; |
| |
| // Timer id of the page range textfield. It is used to reset the timer whenever |
| // needed. |
| var timerId; |
| |
| /** |
| * Window onload handler, sets up the page and starts print preview by getting |
| * the printer list. |
| */ |
| function onLoad() { |
| initializeAnimation(); |
| |
| $('printer-list').disabled = true; |
| $('print-button').disabled = true; |
| $('print-button').addEventListener('click', printFile); |
| $('cancel-button').addEventListener('click', function(e) { |
| window.close(); |
| }); |
| |
| $('all-pages').addEventListener('click', onPageSelectionMayHaveChanged); |
| $('copies').addEventListener('input', validateNumberOfCopies); |
| $('copies').addEventListener('blur', handleCopiesFieldBlur); |
| $('print-pages').addEventListener('click', handleIndividualPagesCheckbox); |
| $('individual-pages').addEventListener('blur', handlePageRangesFieldBlur); |
| $('individual-pages').addEventListener('focus', addTimerToPageRangeField); |
| $('individual-pages').addEventListener('input', resetPageRangeFieldTimer); |
| $('landscape').addEventListener('click', onLayoutModeToggle); |
| $('portrait').addEventListener('click', onLayoutModeToggle); |
| $('color').addEventListener('click', function() { setColor(true); }); |
| $('bw').addEventListener('click', function() { setColor(false); }); |
| $('printer-list').addEventListener( |
| 'change', updateControlsWithSelectedPrinterCapabilities); |
| |
| chrome.send('getPrinters'); |
| } |
| |
| /** |
| * Gets the selected printer capabilities and updates the controls accordingly. |
| */ |
| function updateControlsWithSelectedPrinterCapabilities() { |
| var printerList = $('printer-list'); |
| var selectedPrinter = printerList.selectedIndex; |
| if (selectedPrinter < 0) |
| return; |
| |
| var printerName = printerList.options[selectedPrinter].textContent; |
| if (printerName == localStrings.getString('printToPDF')) { |
| updateWithPrinterCapabilities({'disableColorOption': true, |
| 'setColorAsDefault': true}); |
| } else { |
| // This message will call back to 'updateWithPrinterCapabilities' |
| // function. |
| chrome.send('getPrinterCapabilities', [printerName]); |
| } |
| } |
| |
| /** |
| * Updates the controls with printer capabilities information. |
| * @param {Object} settingInfo printer setting information. |
| */ |
| function updateWithPrinterCapabilities(settingInfo) { |
| var disableColorOption = settingInfo.disableColorOption; |
| var setColorAsDefault = settingInfo.setColorAsDefault; |
| var colorOption = $('color'); |
| var bwOption = $('bw'); |
| |
| if (disableColorOption != colorOption.disabled) { |
| setControlAndLabelDisabled(colorOption, disableColorOption); |
| setControlAndLabelDisabled(bwOption, disableColorOption); |
| } |
| |
| if (colorOption.checked != setColorAsDefault) { |
| colorOption.checked = setColorAsDefault; |
| bwOption.checked = !setColorAsDefault; |
| setColor(colorOption.checked); |
| } |
| } |
| |
| /** |
| * Disables the input control element and its associated label. |
| * @param {HTMLElement} controlElm An input control element. |
| * @param {boolean} disable set to true to disable element and label. |
| */ |
| function setControlAndLabelDisabled(controlElm, disable) { |
| controlElm.disabled = disable; |
| var label = $(controlElm.getAttribute('label')); |
| if (disable) |
| label.classList.add('disabled-label-text'); |
| else |
| label.classList.remove('disabled-label-text'); |
| } |
| |
| /** |
| * Parses the copies field text for validation and updates the state of print |
| * button and collate checkbox. If the specified value is invalid, displays an |
| * invalid warning icon on the text box and sets the error message as the title |
| * message of text box. |
| */ |
| function validateNumberOfCopies() { |
| var copiesField = $('copies'); |
| var message = ''; |
| if (!isNumberOfCopiesValid()) |
| message = localStrings.getString('invalidNumberOfCopiesTitleToolTip'); |
| copiesField.setCustomValidity(message); |
| copiesField.title = message; |
| updatePrintButtonState(); |
| } |
| |
| /** |
| * Handles copies field blur event. |
| */ |
| function handleCopiesFieldBlur() { |
| checkAndSetCopiesField(); |
| printSettingChanged(); |
| } |
| |
| /** |
| * Handles page ranges field blur event. |
| */ |
| function handlePageRangesFieldBlur() { |
| checkAndSetPageRangesField(); |
| onPageSelectionMayHaveChanged(); |
| } |
| |
| /** |
| * Validates the copies text field value. |
| * NOTE: An empty copies field text is considered valid because the blur event |
| * listener of this field will set it back to a default value. |
| * @return {boolean} true if the number of copies is valid else returns false. |
| */ |
| function isNumberOfCopiesValid() { |
| var copiesFieldText = $('copies').value.replace(/\s/g, ''); |
| if (copiesFieldText == '') |
| return true; |
| |
| var numericExp = /^[0-9]+$/; |
| return (numericExp.test(copiesFieldText) && Number(copiesFieldText) > 0); |
| } |
| |
| /** |
| * Checks the value of the copies field. If it is a valid number it does |
| * nothing. If it can only parse the first part of the string it replaces the |
| * string with the first part. Example: '123abcd' becomes '123'. |
| * If the string can't be parsed at all it replaces with 1. |
| */ |
| function checkAndSetCopiesField() { |
| var copiesField = $('copies'); |
| var copies = parseInt(copiesField.value, 10); |
| if (isNaN(copies)) |
| copies = 1; |
| copiesField.value = copies; |
| updateSummary(); |
| } |
| |
| /** |
| * Checks the value of the page ranges text field. It parses the page ranges and |
| * normalizes them. For example: '1,2,3,5,9-10' becomes '1-3, 5, 9-10'. |
| * If it can't parse the whole string it will replace with the part it parsed. |
| * For example: '1-6,9-10,sd343jf' becomes '1-6, 9-10'. If the specified page |
| * range includes all pages it replaces it with the empty string (so that the |
| * example text is automatically shown. |
| * |
| */ |
| function checkAndSetPageRangesField() { |
| var pageRanges = getSelectedPageRanges(); |
| var parsedPageRanges = ''; |
| var individualPagesField = $('individual-pages'); |
| |
| for (var i = 0; i < pageRanges.length; ++i) { |
| if (pageRanges[i].from == pageRanges[i].to) |
| parsedPageRanges += pageRanges[i].from; |
| else |
| parsedPageRanges += pageRanges[i].from + '-' + pageRanges[i].to; |
| if (i < pageRanges.length - 1) |
| parsedPageRanges += ', '; |
| } |
| individualPagesField.value = parsedPageRanges; |
| updateSummary(); |
| } |
| |
| /** |
| * Checks whether the preview layout setting is set to 'landscape' or not. |
| * |
| * @return {boolean} true if layout is 'landscape'. |
| */ |
| function isLandscape() { |
| return $('landscape').checked; |
| } |
| |
| /** |
| * Checks whether the preview color setting is set to 'color' or not. |
| * |
| * @return {boolean} true if color is 'color'. |
| */ |
| function isColor() { |
| return $('color').checked; |
| } |
| |
| /** |
| * Checks whether the preview collate setting value is set or not. |
| * |
| * @return {boolean} true if collate setting is enabled and checked. |
| */ |
| function isCollated() { |
| var collateField = $('collate'); |
| return !collateField.disabled && collateField.checked; |
| } |
| |
| /** |
| * Returns the number of copies currently indicated in the copies textfield. If |
| * the contents of the textfield can not be converted to a number or if <1 it |
| * returns 1. |
| * |
| * @return {number} number of copies. |
| */ |
| function getCopies() { |
| var copies = parseInt($('copies').value, 10); |
| if (!copies || copies <= 1) |
| copies = 1; |
| return copies; |
| } |
| |
| /** |
| * Checks whether the preview two-sided checkbox is checked. |
| * |
| * @return {boolean} true if two-sided is checked. |
| */ |
| function isTwoSided() { |
| return $('two-sided').checked; |
| } |
| |
| /** |
| * Creates a JSON string based on the values in the printer settings. |
| * |
| * @return {string} JSON string with print job settings. |
| */ |
| function getSettingsJSON() { |
| var printerList = $('printer-list') |
| var selectedPrinter = printerList.selectedIndex; |
| var printerName = ''; |
| if (selectedPrinter >= 0) |
| printerName = printerList.options[selectedPrinter].textContent; |
| var printAll = $('all-pages').checked; |
| var printToPDF = (printerName == localStrings.getString('printToPDF')); |
| |
| return JSON.stringify({'printerName': printerName, |
| 'pageRange': getSelectedPageRanges(), |
| 'printAll': printAll, |
| 'twoSided': isTwoSided(), |
| 'copies': getCopies(), |
| 'collate': isCollated(), |
| 'landscape': isLandscape(), |
| 'color': isColor(), |
| 'printToPDF': printToPDF}); |
| } |
| |
| /** |
| * Asks the browser to print the preview PDF based on current print settings. |
| */ |
| function printFile() { |
| chrome.send('print', [getSettingsJSON()]); |
| } |
| |
| /** |
| * Asks the browser to generate a preview PDF based on current print settings. |
| */ |
| function getPreview() { |
| chrome.send('getPreview', [getSettingsJSON()]); |
| } |
| |
| /** |
| * Fill the printer list drop down. |
| * Called from PrintPreviewHandler::SendPrinterList(). |
| * @param {Array} printers Array of printer names. |
| * @param {number} defaultPrinterIndex The index of the default printer. |
| */ |
| function setPrinters(printers, defaultPrinterIndex) { |
| var printerList = $('printer-list'); |
| for (var i = 0; i < printers.length; ++i) { |
| var option = document.createElement('option'); |
| option.textContent = printers[i]; |
| printerList.add(option); |
| if (i == defaultPrinterIndex) |
| option.selected = true; |
| } |
| |
| // Adding option for saving PDF to disk. |
| var option = document.createElement('option'); |
| option.textContent = localStrings.getString('printToPDF'); |
| printerList.add(option); |
| printerList.disabled = false; |
| |
| updateControlsWithSelectedPrinterCapabilities(); |
| |
| // Once the printer list is populated, generate the initial preview. |
| getPreview(); |
| } |
| |
| /** |
| * 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 (!hasPDFPlugin) { |
| return; |
| } |
| $('pdf-viewer').grayscale(!color); |
| } |
| |
| /** |
| * Called when the PDF plugin loads its document. |
| */ |
| function onPDFLoad() { |
| if (isLandscape()) |
| $('pdf-viewer').fitToWidth(); |
| else |
| $('pdf-viewer').fitToHeight(); |
| } |
| |
| /** |
| * Update the print preview when new preview data is available. |
| * Create the PDF plugin as needed. |
| * Called from PrintPreviewUI::PreviewDataIsAvailable(). |
| * @param {number} pageCount The expected total pages count. |
| * @param {string} jobTitle The print job title. |
| * |
| */ |
| function updatePrintPreview(pageCount, jobTitle) { |
| // Initialize the expected page count. |
| if (totalPageCount == -1) |
| totalPageCount = pageCount; |
| |
| // Initialize the selected pages (defaults to all selected). |
| if (previouslySelectedPages.length == 0) |
| for (var i = 0; i < totalPageCount; i++) |
| previouslySelectedPages.push(i+1); |
| |
| regeneratePreview = false; |
| |
| // Update the current tab title. |
| document.title = localStrings.getStringF('printPreviewTitleFormat', jobTitle); |
| |
| createPDFPlugin(); |
| |
| updateSummary(); |
| } |
| |
| /** |
| * Create the PDF plugin or reload the existing one. |
| */ |
| function createPDFPlugin() { |
| if (!hasPDFPlugin) { |
| return; |
| } |
| |
| // Enable the print button. |
| if (!$('printer-list').disabled) { |
| $('print-button').disabled = false; |
| } |
| |
| var pdfViewer = $('pdf-viewer'); |
| if (pdfViewer) { |
| pdfViewer.reload(); |
| pdfViewer.grayscale(!isColor()); |
| return; |
| } |
| |
| var loadingElement = $('loading'); |
| loadingElement.classList.add('hidden'); |
| var mainView = loadingElement.parentNode; |
| |
| var pdfPlugin = document.createElement('embed'); |
| pdfPlugin.setAttribute('id', 'pdf-viewer'); |
| pdfPlugin.setAttribute('type', 'application/pdf'); |
| pdfPlugin.setAttribute('src', 'chrome://print/print.pdf'); |
| mainView.appendChild(pdfPlugin); |
| if (!pdfPlugin.onload) { |
| hasPDFPlugin = false; |
| mainView.removeChild(pdfPlugin); |
| $('no-plugin').classList.remove('hidden'); |
| return; |
| } |
| pdfPlugin.grayscale(true); |
| pdfPlugin.onload('onPDFLoad()'); |
| } |
| |
| /** |
| * Updates the state of print button depending on the user selection. |
| * |
| * If the user has selected 'All' pages option, enables the print button. |
| * If the user has selected a page range, depending on the validity of page |
| * range text enables/disables the print button. |
| * Depending on the validity of 'copies' value, enables/disables the print |
| * button. |
| */ |
| function updatePrintButtonState() { |
| $('print-button').disabled = (!($('all-pages').checked || |
| $('individual-pages').checkValidity()) || |
| !$('copies').checkValidity()); |
| } |
| |
| window.addEventListener('DOMContentLoaded', onLoad); |
| |
| /** |
| * Listener function that executes whenever any of the available settings |
| * is changed. |
| */ |
| function printSettingChanged() { |
| $('collate-option').hidden = getCopies() <= 1; |
| updateSummary(); |
| } |
| |
| /** |
| * Updates the print summary based on the currently selected user options. |
| * |
| */ |
| function updateSummary() { |
| var copies = getCopies(); |
| var printButton = $('print-button'); |
| var printSummary = $('print-summary'); |
| |
| if (isNaN($('copies').value)) { |
| printSummary.innerHTML = |
| localStrings.getString('invalidNumberOfCopiesTitleToolTip'); |
| return; |
| } |
| |
| var pageList = getSelectedPages(); |
| if (pageList.length <= 0) { |
| printSummary.innerHTML = |
| localStrings.getString('pageRangeInvalidTitleToolTip'); |
| printButton.disabled = true; |
| return; |
| } |
| |
| var pagesLabel = localStrings.getString('printPreviewPageLabelSingular'); |
| var twoSidedLabel = ''; |
| var timesSign = ''; |
| var numOfCopies = ''; |
| var copiesLabel = ''; |
| var equalSign = ''; |
| var numOfSheets = ''; |
| var sheetsLabel = ''; |
| |
| printButton.disabled = false; |
| |
| if (pageList.length > 1) |
| pagesLabel = localStrings.getString('printPreviewPageLabelPlural'); |
| |
| if (isTwoSided()) |
| twoSidedLabel = '('+localStrings.getString('optionTwoSided')+')'; |
| |
| if (copies > 1) { |
| timesSign = '×'; |
| numOfCopies = copies; |
| copiesLabel = localStrings.getString('copiesLabel').toLowerCase(); |
| } |
| |
| if ((copies > 1) || (isTwoSided())) { |
| numOfSheets = pageList.length; |
| |
| if (isTwoSided()) |
| numOfSheets = Math.ceil(numOfSheets / 2); |
| |
| equalSign = '='; |
| numOfSheets *= copies; |
| sheetsLabel = localStrings.getString('printPreviewSheetsLabel'); |
| } |
| |
| var html = localStrings.getStringF('printPreviewSummaryFormat', |
| pageList.length, pagesLabel, |
| twoSidedLabel, timesSign, numOfCopies, |
| copiesLabel, equalSign, |
| '<strong>' + numOfSheets + '</strong>', |
| '<strong>' + sheetsLabel + '</strong>'); |
| |
| // Removing extra spaces from within the string. |
| html.replace(/\s{2,}/g, ' '); |
| printSummary.innerHTML = html; |
| } |
| |
| /** |
| * Handles a click event on the two-sided option. |
| */ |
| function handleTwoSidedClick(event) { |
| handleZippyClickEl($('binding')); |
| printSettingChanged(event); |
| } |
| |
| /** |
| * Gives focus to the individual pages textfield when 'print-pages' textbox is |
| * clicked. |
| */ |
| function handleIndividualPagesCheckbox() { |
| printSettingChanged(); |
| $('individual-pages').focus(); |
| } |
| |
| /** |
| * When the user switches printing orientation mode the page field selection is |
| * reset to "all pages selected". After the change the number of pages will be |
| * different and currently selected page numbers might no longer be valid. |
| * Even if they are still valid the content of these pages will be different. |
| */ |
| function onLayoutModeToggle() { |
| $('individual-pages').value = ''; |
| $('all-pages').checked = true; |
| totalPageCount = -1; |
| previouslySelectedPages.length = 0; |
| getPreview(); |
| } |
| |
| /** |
| * Returns a list of all pages in the specified ranges. If the page ranges can't |
| * be parsed an empty list is returned. |
| * |
| * @return {Array} |
| */ |
| function getSelectedPages() { |
| var pageText = $('individual-pages').value; |
| |
| if ($('all-pages').checked || pageText == '') |
| pageText = '1-' + totalPageCount; |
| |
| var pageList = []; |
| var parts = pageText.split(/,/); |
| |
| for (var i = 0; i < parts.length; ++i) { |
| var part = parts[i]; |
| var match = part.match(/([0-9]+)-([0-9]+)/); |
| |
| if (match && match[1] && match[2]) { |
| var from = parseInt(match[1], 10); |
| var to = parseInt(match[2], 10); |
| |
| if (from && to) { |
| for (var j = from; j <= to; ++j) |
| if (j <= totalPageCount) |
| pageList.push(j); |
| } |
| } else if (parseInt(part, 10)) { |
| if (parseInt(part, 10) <= totalPageCount) |
| pageList.push(parseInt(part, 10)); |
| } |
| } |
| return pageList; |
| } |
| |
| /** |
| * Parses the selected page ranges, processes them and returns the results. |
| * It squashes whenever possible. Example '1-2,3,5-7' becomes 1-3,5-7 |
| * |
| * @return {Array} an array of page range objects. A page range object has |
| * fields 'from' and 'to'. |
| */ |
| function getSelectedPageRanges() { |
| var pageList = getSelectedPages(); |
| var pageRanges = []; |
| for (var i = 0; i < pageList.length; ++i) { |
| tempFrom = pageList[i]; |
| while (i + 1 < pageList.length && pageList[i + 1] == pageList[i] + 1) |
| ++i; |
| tempTo = pageList[i]; |
| pageRanges.push({'from': tempFrom, 'to': tempTo}); |
| } |
| return pageRanges; |
| } |
| |
| /** |
| * Whenever the page range textfield gains focus we add a timer to detect when |
| * the user stops typing in order to update the print preview. |
| */ |
| function addTimerToPageRangeField() { |
| timerId = window.setTimeout(onPageSelectionMayHaveChanged, 500); |
| } |
| |
| /** |
| * As the user types in the page range textfield, we need to reset this timer, |
| * since the page ranges are still being edited. |
| */ |
| function resetPageRangeFieldTimer() { |
| clearTimeout(timerId); |
| addTimerToPageRangeField(); |
| } |
| |
| /** |
| * When the user stops typing in the page range textfield or clicks on the |
| * 'all-pages' checkbox, a new print preview is requested, only if |
| * 1) The input is valid (it can be parsed, even only partially). |
| * 2) The newly selected pages differ from the previously selected. |
| */ |
| function onPageSelectionMayHaveChanged() { |
| var currentlySelectedPages = getSelectedPages(); |
| |
| if (currentlySelectedPages.length == 0) |
| return; |
| if (areArraysEqual(previouslySelectedPages, currentlySelectedPages)) |
| return; |
| |
| previouslySelectedPages = currentlySelectedPages; |
| getPreview(); |
| } |
| |
| /** |
| * Returns true if the contents of the two arrays are equal. |
| */ |
| function areArraysEqual(array1, array2) { |
| if (array1.length != array2.length) |
| return false; |
| for (var i = 0; i < array1.length; i++) |
| if(array1[i] != array2[i]) |
| return false; |
| return true; |
| } |