Home | History | Annotate | Download | only in resources
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 var localStrings = new LocalStrings();
      6 var hasPDFPlugin = true;
      7 
      8 // The total page count of the previewed document regardless of which pages the
      9 // user has selected.
     10 var totalPageCount = -1;
     11 
     12 // The previously selected pages by the user. It is used in
     13 // onPageSelectionMayHaveChanged() to make sure that a new preview is not
     14 // requested more often than necessary.
     15 var previouslySelectedPages = [];
     16 
     17 // Timer id of the page range textfield. It is used to reset the timer whenever
     18 // needed.
     19 var timerId;
     20 
     21 /**
     22  * Window onload handler, sets up the page and starts print preview by getting
     23  * the printer list.
     24  */
     25 function onLoad() {
     26   initializeAnimation();
     27 
     28   $('printer-list').disabled = true;
     29   $('print-button').disabled = true;
     30   $('print-button').addEventListener('click', printFile);
     31   $('cancel-button').addEventListener('click', function(e) {
     32     window.close();
     33   });
     34 
     35   $('all-pages').addEventListener('click', onPageSelectionMayHaveChanged);
     36   $('copies').addEventListener('input', validateNumberOfCopies);
     37   $('copies').addEventListener('blur', handleCopiesFieldBlur);
     38   $('print-pages').addEventListener('click', handleIndividualPagesCheckbox);
     39   $('individual-pages').addEventListener('blur', handlePageRangesFieldBlur);
     40   $('individual-pages').addEventListener('focus', addTimerToPageRangeField);
     41   $('individual-pages').addEventListener('input', resetPageRangeFieldTimer);
     42   $('landscape').addEventListener('click', onLayoutModeToggle);
     43   $('portrait').addEventListener('click', onLayoutModeToggle);
     44   $('color').addEventListener('click', function() { setColor(true); });
     45   $('bw').addEventListener('click', function() { setColor(false); });
     46   $('printer-list').addEventListener(
     47       'change', updateControlsWithSelectedPrinterCapabilities);
     48 
     49   chrome.send('getPrinters');
     50 }
     51 
     52 /**
     53  * Gets the selected printer capabilities and updates the controls accordingly.
     54  */
     55 function updateControlsWithSelectedPrinterCapabilities() {
     56   var printerList = $('printer-list');
     57   var selectedPrinter = printerList.selectedIndex;
     58   if (selectedPrinter < 0)
     59     return;
     60 
     61   var printerName = printerList.options[selectedPrinter].textContent;
     62   if (printerName == localStrings.getString('printToPDF')) {
     63     updateWithPrinterCapabilities({'disableColorOption': true,
     64                                    'setColorAsDefault': true});
     65   } else {
     66     // This message will call back to 'updateWithPrinterCapabilities'
     67     // function.
     68     chrome.send('getPrinterCapabilities', [printerName]);
     69   }
     70 }
     71 
     72 /**
     73  * Updates the controls with printer capabilities information.
     74  * @param {Object} settingInfo printer setting information.
     75  */
     76 function updateWithPrinterCapabilities(settingInfo) {
     77   var disableColorOption = settingInfo.disableColorOption;
     78   var setColorAsDefault = settingInfo.setColorAsDefault;
     79   var colorOption = $('color');
     80   var bwOption = $('bw');
     81 
     82   if (disableColorOption != colorOption.disabled) {
     83     setControlAndLabelDisabled(colorOption, disableColorOption);
     84     setControlAndLabelDisabled(bwOption, disableColorOption);
     85   }
     86 
     87   if (colorOption.checked != setColorAsDefault) {
     88     colorOption.checked = setColorAsDefault;
     89     bwOption.checked = !setColorAsDefault;
     90     setColor(colorOption.checked);
     91   }
     92 }
     93 
     94 /**
     95  * Disables the input control element and its associated label.
     96  * @param {HTMLElement} controlElm An input control element.
     97  * @param {boolean} disable set to true to disable element and label.
     98  */
     99 function setControlAndLabelDisabled(controlElm, disable) {
    100   controlElm.disabled = disable;
    101   var label = $(controlElm.getAttribute('label'));
    102   if (disable)
    103     label.classList.add('disabled-label-text');
    104   else
    105     label.classList.remove('disabled-label-text');
    106 }
    107 
    108 /**
    109  * Parses the copies field text for validation and updates the state of print
    110  * button and collate checkbox. If the specified value is invalid, displays an
    111  * invalid warning icon on the text box and sets the error message as the title
    112  * message of text box.
    113  */
    114 function validateNumberOfCopies() {
    115   var copiesField = $('copies');
    116   var message = '';
    117   if (!isNumberOfCopiesValid())
    118     message = localStrings.getString('invalidNumberOfCopiesTitleToolTip');
    119   copiesField.setCustomValidity(message);
    120   copiesField.title = message;
    121   updatePrintButtonState();
    122 }
    123 
    124 /**
    125  * Handles copies field blur event.
    126  */
    127 function handleCopiesFieldBlur() {
    128   checkAndSetCopiesField();
    129   printSettingChanged();
    130 }
    131 
    132 /**
    133  * Handles page ranges field blur event.
    134  */
    135 function handlePageRangesFieldBlur() {
    136   checkAndSetPageRangesField();
    137   onPageSelectionMayHaveChanged();
    138 }
    139 
    140 /**
    141  * Validates the copies text field value.
    142  * NOTE: An empty copies field text is considered valid because the blur event
    143  * listener of this field will set it back to a default value.
    144  * @return {boolean} true if the number of copies is valid else returns false.
    145  */
    146 function isNumberOfCopiesValid() {
    147   var copiesFieldText = $('copies').value.replace(/\s/g, '');
    148   if (copiesFieldText == '')
    149     return true;
    150 
    151   var numericExp = /^[0-9]+$/;
    152   return (numericExp.test(copiesFieldText) && Number(copiesFieldText) > 0);
    153 }
    154 
    155 /**
    156  * Checks the value of the copies field. If it is a valid number it does
    157  * nothing. If it can only parse the first part of the string it replaces the
    158  * string with the first part. Example: '123abcd' becomes '123'.
    159  * If the string can't be parsed at all it replaces with 1.
    160  */
    161 function checkAndSetCopiesField() {
    162   var copiesField = $('copies');
    163   var copies = parseInt(copiesField.value, 10);
    164   if (isNaN(copies))
    165     copies = 1;
    166   copiesField.value = copies;
    167   updateSummary();
    168 }
    169 
    170 /**
    171  * Checks the value of the page ranges text field. It parses the page ranges and
    172  * normalizes them. For example: '1,2,3,5,9-10' becomes '1-3, 5, 9-10'.
    173  * If it can't parse the whole string it will replace with the part it parsed.
    174  * For example: '1-6,9-10,sd343jf' becomes '1-6, 9-10'. If the specified page
    175  * range includes all pages it replaces it with the empty string (so that the
    176  * example text is automatically shown.
    177  *
    178  */
    179 function checkAndSetPageRangesField() {
    180   var pageRanges = getSelectedPageRanges();
    181   var parsedPageRanges = '';
    182   var individualPagesField = $('individual-pages');
    183 
    184   for (var i = 0; i < pageRanges.length; ++i) {
    185     if (pageRanges[i].from == pageRanges[i].to)
    186       parsedPageRanges += pageRanges[i].from;
    187     else
    188       parsedPageRanges += pageRanges[i].from + '-' + pageRanges[i].to;
    189     if (i < pageRanges.length - 1)
    190       parsedPageRanges += ', ';
    191   }
    192   individualPagesField.value = parsedPageRanges;
    193   updateSummary();
    194 }
    195 
    196 /**
    197  * Checks whether the preview layout setting is set to 'landscape' or not.
    198  *
    199  * @return {boolean} true if layout is 'landscape'.
    200  */
    201 function isLandscape() {
    202   return $('landscape').checked;
    203 }
    204 
    205 /**
    206  * Checks whether the preview color setting is set to 'color' or not.
    207  *
    208  * @return {boolean} true if color is 'color'.
    209  */
    210 function isColor() {
    211   return $('color').checked;
    212 }
    213 
    214 /**
    215  * Checks whether the preview collate setting value is set or not.
    216  *
    217  * @return {boolean} true if collate setting is enabled and checked.
    218  */
    219 function isCollated() {
    220   var collateField = $('collate');
    221   return !collateField.disabled && collateField.checked;
    222 }
    223 
    224 /**
    225  * Returns the number of copies currently indicated in the copies textfield. If
    226  * the contents of the textfield can not be converted to a number or if <1 it
    227  * returns 1.
    228  *
    229  * @return {number} number of copies.
    230  */
    231 function getCopies() {
    232   var copies = parseInt($('copies').value, 10);
    233   if (!copies || copies <= 1)
    234     copies = 1;
    235   return copies;
    236 }
    237 
    238 /**
    239  * Checks whether the preview two-sided checkbox is checked.
    240  *
    241  * @return {boolean} true if two-sided is checked.
    242  */
    243 function isTwoSided() {
    244   return $('two-sided').checked;
    245 }
    246 
    247 /**
    248  * Creates a JSON string based on the values in the printer settings.
    249  *
    250  * @return {string} JSON string with print job settings.
    251  */
    252 function getSettingsJSON() {
    253   var printerList = $('printer-list')
    254   var selectedPrinter = printerList.selectedIndex;
    255   var printerName = '';
    256   if (selectedPrinter >= 0)
    257     printerName = printerList.options[selectedPrinter].textContent;
    258   var printAll = $('all-pages').checked;
    259   var printToPDF = (printerName == localStrings.getString('printToPDF'));
    260 
    261   return JSON.stringify({'printerName': printerName,
    262                          'pageRange': getSelectedPageRanges(),
    263                          'printAll': printAll,
    264                          'twoSided': isTwoSided(),
    265                          'copies': getCopies(),
    266                          'collate': isCollated(),
    267                          'landscape': isLandscape(),
    268                          'color': isColor(),
    269                          'printToPDF': printToPDF});
    270 }
    271 
    272 /**
    273  * Asks the browser to print the preview PDF based on current print settings.
    274  */
    275 function printFile() {
    276   chrome.send('print', [getSettingsJSON()]);
    277 }
    278 
    279 /**
    280  * Asks the browser to generate a preview PDF based on current print settings.
    281  */
    282 function getPreview() {
    283   chrome.send('getPreview', [getSettingsJSON()]);
    284 }
    285 
    286 /**
    287  * Fill the printer list drop down.
    288  * Called from PrintPreviewHandler::SendPrinterList().
    289  * @param {Array} printers Array of printer names.
    290  * @param {number} defaultPrinterIndex The index of the default printer.
    291  */
    292 function setPrinters(printers, defaultPrinterIndex) {
    293   var printerList = $('printer-list');
    294   for (var i = 0; i < printers.length; ++i) {
    295     var option = document.createElement('option');
    296     option.textContent = printers[i];
    297     printerList.add(option);
    298     if (i == defaultPrinterIndex)
    299       option.selected = true;
    300   }
    301 
    302   // Adding option for saving PDF to disk.
    303   var option = document.createElement('option');
    304   option.textContent = localStrings.getString('printToPDF');
    305   printerList.add(option);
    306   printerList.disabled = false;
    307 
    308   updateControlsWithSelectedPrinterCapabilities();
    309 
    310   // Once the printer list is populated, generate the initial preview.
    311   getPreview();
    312 }
    313 
    314 /**
    315  * Sets the color mode for the PDF plugin.
    316  * Called from PrintPreviewHandler::ProcessColorSetting().
    317  * @param {boolean} color is true if the PDF plugin should display in color.
    318  */
    319 function setColor(color) {
    320   if (!hasPDFPlugin) {
    321     return;
    322   }
    323   $('pdf-viewer').grayscale(!color);
    324 }
    325 
    326 /**
    327  * Called when the PDF plugin loads its document.
    328  */
    329 function onPDFLoad() {
    330   if (isLandscape())
    331     $('pdf-viewer').fitToWidth();
    332   else
    333     $('pdf-viewer').fitToHeight();
    334 }
    335 
    336 /**
    337  * Update the print preview when new preview data is available.
    338  * Create the PDF plugin as needed.
    339  * Called from PrintPreviewUI::PreviewDataIsAvailable().
    340  * @param {number} pageCount The expected total pages count.
    341  * @param {string} jobTitle The print job title.
    342  *
    343  */
    344 function updatePrintPreview(pageCount, jobTitle) {
    345   // Initialize the expected page count.
    346   if (totalPageCount == -1)
    347     totalPageCount = pageCount;
    348 
    349   // Initialize the selected pages (defaults to all selected).
    350   if (previouslySelectedPages.length == 0)
    351     for (var i = 0; i < totalPageCount; i++)
    352       previouslySelectedPages.push(i+1);
    353 
    354   regeneratePreview = false;
    355 
    356   // Update the current tab title.
    357   document.title = localStrings.getStringF('printPreviewTitleFormat', jobTitle);
    358 
    359   createPDFPlugin();
    360 
    361   updateSummary();
    362 }
    363 
    364 /**
    365  * Create the PDF plugin or reload the existing one.
    366  */
    367 function createPDFPlugin() {
    368   if (!hasPDFPlugin) {
    369     return;
    370   }
    371 
    372   // Enable the print button.
    373   if (!$('printer-list').disabled) {
    374     $('print-button').disabled = false;
    375   }
    376 
    377   var pdfViewer = $('pdf-viewer');
    378   if (pdfViewer) {
    379     pdfViewer.reload();
    380     pdfViewer.grayscale(!isColor());
    381     return;
    382   }
    383 
    384   var loadingElement = $('loading');
    385   loadingElement.classList.add('hidden');
    386   var mainView = loadingElement.parentNode;
    387 
    388   var pdfPlugin = document.createElement('embed');
    389   pdfPlugin.setAttribute('id', 'pdf-viewer');
    390   pdfPlugin.setAttribute('type', 'application/pdf');
    391   pdfPlugin.setAttribute('src', 'chrome://print/print.pdf');
    392   mainView.appendChild(pdfPlugin);
    393   if (!pdfPlugin.onload) {
    394     hasPDFPlugin = false;
    395     mainView.removeChild(pdfPlugin);
    396     $('no-plugin').classList.remove('hidden');
    397     return;
    398   }
    399   pdfPlugin.grayscale(true);
    400   pdfPlugin.onload('onPDFLoad()');
    401 }
    402 
    403 /**
    404  * Updates the state of print button depending on the user selection.
    405  *
    406  * If the user has selected 'All' pages option, enables the print button.
    407  * If the user has selected a page range, depending on the validity of page
    408  * range text enables/disables the print button.
    409  * Depending on the validity of 'copies' value, enables/disables the print
    410  * button.
    411  */
    412 function updatePrintButtonState() {
    413   $('print-button').disabled = (!($('all-pages').checked ||
    414                                   $('individual-pages').checkValidity()) ||
    415                                 !$('copies').checkValidity());
    416 }
    417 
    418 window.addEventListener('DOMContentLoaded', onLoad);
    419 
    420 /**
    421  * Listener function that executes whenever any of the available settings
    422  * is changed.
    423  */
    424 function printSettingChanged() {
    425   $('collate-option').hidden = getCopies() <= 1;
    426   updateSummary();
    427 }
    428 
    429 /**
    430  * Updates the print summary based on the currently selected user options.
    431  *
    432  */
    433 function updateSummary() {
    434   var copies = getCopies();
    435   var printButton = $('print-button');
    436   var printSummary = $('print-summary');
    437 
    438   if (isNaN($('copies').value)) {
    439     printSummary.innerHTML =
    440         localStrings.getString('invalidNumberOfCopiesTitleToolTip');
    441     return;
    442   }
    443 
    444   var pageList = getSelectedPages();
    445   if (pageList.length <= 0) {
    446     printSummary.innerHTML =
    447         localStrings.getString('pageRangeInvalidTitleToolTip');
    448     printButton.disabled = true;
    449     return;
    450   }
    451 
    452   var pagesLabel = localStrings.getString('printPreviewPageLabelSingular');
    453   var twoSidedLabel = '';
    454   var timesSign = '';
    455   var numOfCopies = '';
    456   var copiesLabel = '';
    457   var equalSign = '';
    458   var numOfSheets = '';
    459   var sheetsLabel = '';
    460 
    461   printButton.disabled = false;
    462 
    463   if (pageList.length > 1)
    464     pagesLabel = localStrings.getString('printPreviewPageLabelPlural');
    465 
    466   if (isTwoSided())
    467     twoSidedLabel = '('+localStrings.getString('optionTwoSided')+')';
    468 
    469   if (copies > 1) {
    470     timesSign = '';
    471     numOfCopies = copies;
    472     copiesLabel = localStrings.getString('copiesLabel').toLowerCase();
    473   }
    474 
    475   if ((copies > 1) || (isTwoSided())) {
    476     numOfSheets = pageList.length;
    477 
    478     if (isTwoSided())
    479       numOfSheets = Math.ceil(numOfSheets / 2);
    480 
    481     equalSign = '=';
    482     numOfSheets *= copies;
    483     sheetsLabel = localStrings.getString('printPreviewSheetsLabel');
    484   }
    485 
    486   var html = localStrings.getStringF('printPreviewSummaryFormat',
    487                                      pageList.length, pagesLabel,
    488                                      twoSidedLabel, timesSign, numOfCopies,
    489                                      copiesLabel, equalSign,
    490                                      '<strong>' + numOfSheets + '</strong>',
    491                                      '<strong>' + sheetsLabel + '</strong>');
    492 
    493   // Removing extra spaces from within the string.
    494   html.replace(/\s{2,}/g, ' ');
    495   printSummary.innerHTML = html;
    496 }
    497 
    498 /**
    499  * Handles a click event on the two-sided option.
    500  */
    501 function handleTwoSidedClick(event) {
    502   handleZippyClickEl($('binding'));
    503   printSettingChanged(event);
    504 }
    505 
    506 /**
    507  * Gives focus to the individual pages textfield when 'print-pages' textbox is
    508  * clicked.
    509  */
    510 function handleIndividualPagesCheckbox() {
    511   printSettingChanged();
    512   $('individual-pages').focus();
    513 }
    514 
    515 /**
    516  * When the user switches printing orientation mode the page field selection is
    517  * reset to "all pages selected". After the change the number of pages will be
    518  * different and currently selected page numbers might no longer be valid.
    519  * Even if they are still valid the content of these pages will be different.
    520  */
    521 function onLayoutModeToggle() {
    522   $('individual-pages').value = '';
    523   $('all-pages').checked = true;
    524   totalPageCount = -1;
    525   previouslySelectedPages.length = 0;
    526   getPreview();
    527 }
    528 
    529 /**
    530  * Returns a list of all pages in the specified ranges. If the page ranges can't
    531  * be parsed an empty list is returned.
    532  *
    533  * @return {Array}
    534  */
    535 function getSelectedPages() {
    536   var pageText = $('individual-pages').value;
    537 
    538   if ($('all-pages').checked || pageText == '')
    539     pageText = '1-' + totalPageCount;
    540 
    541   var pageList = [];
    542   var parts = pageText.split(/,/);
    543 
    544   for (var i = 0; i < parts.length; ++i) {
    545     var part = parts[i];
    546     var match = part.match(/([0-9]+)-([0-9]+)/);
    547 
    548     if (match && match[1] && match[2]) {
    549       var from = parseInt(match[1], 10);
    550       var to = parseInt(match[2], 10);
    551 
    552       if (from && to) {
    553         for (var j = from; j <= to; ++j)
    554           if (j <= totalPageCount)
    555             pageList.push(j);
    556       }
    557     } else if (parseInt(part, 10)) {
    558       if (parseInt(part, 10) <= totalPageCount)
    559         pageList.push(parseInt(part, 10));
    560     }
    561   }
    562   return pageList;
    563 }
    564 
    565 /**
    566  * Parses the selected page ranges, processes them and returns the results.
    567  * It squashes whenever possible. Example '1-2,3,5-7' becomes 1-3,5-7
    568  *
    569  * @return {Array} an array of page range objects. A page range object has
    570  *     fields 'from' and 'to'.
    571  */
    572 function getSelectedPageRanges() {
    573   var pageList = getSelectedPages();
    574   var pageRanges = [];
    575   for (var i = 0; i < pageList.length; ++i) {
    576     tempFrom = pageList[i];
    577     while (i + 1 < pageList.length && pageList[i + 1] == pageList[i] + 1)
    578       ++i;
    579     tempTo = pageList[i];
    580     pageRanges.push({'from': tempFrom, 'to': tempTo});
    581   }
    582   return pageRanges;
    583 }
    584 
    585 /**
    586  * Whenever the page range textfield gains focus we add a timer to detect when
    587  * the user stops typing in order to update the print preview.
    588  */
    589 function addTimerToPageRangeField() {
    590   timerId = window.setTimeout(onPageSelectionMayHaveChanged, 500);
    591 }
    592 
    593 /**
    594  * As the user types in the page range textfield, we need to reset this timer,
    595  * since the page ranges are still being edited.
    596  */
    597 function resetPageRangeFieldTimer() {
    598   clearTimeout(timerId);
    599   addTimerToPageRangeField();
    600 }
    601 
    602 /**
    603  * When the user stops typing in the page range textfield or clicks on the
    604  * 'all-pages' checkbox, a new print preview is requested, only if
    605  * 1) The input is valid (it can be parsed, even only partially).
    606  * 2) The newly selected pages differ from the previously selected.
    607  */
    608 function onPageSelectionMayHaveChanged() {
    609   var currentlySelectedPages = getSelectedPages();
    610 
    611   if (currentlySelectedPages.length == 0)
    612     return;
    613   if (areArraysEqual(previouslySelectedPages, currentlySelectedPages))
    614     return;
    615 
    616   previouslySelectedPages = currentlySelectedPages;
    617   getPreview();
    618 }
    619 
    620 /**
    621  * Returns true if the contents of the two arrays are equal.
    622  */
    623 function areArraysEqual(array1, array2) {
    624   if (array1.length != array2.length)
    625     return false;
    626   for (var i = 0; i < array1.length; i++)
    627     if(array1[i] != array2[i])
    628       return false;
    629   return true;
    630 }
    631