// 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; }