print_preview.js revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 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// TODO(rltoscano): Move data/* into print_preview.data namespace
6
7var localStrings = new LocalStrings(templateData);
8
9<include src="component.js"/>
10
11cr.define('print_preview', function() {
12  'use strict';
13
14  /**
15   * Container class for Chromium's print preview.
16   * @constructor
17   * @extends {print_preview.Component}
18   */
19  function PrintPreview() {
20    print_preview.Component.call(this);
21
22    /**
23     * Used to communicate with Chromium's print system.
24     * @type {!print_preview.NativeLayer}
25     * @private
26     */
27    this.nativeLayer_ = new print_preview.NativeLayer();
28
29    /**
30     * Event target that contains information about the logged in user.
31     * @type {!print_preview.UserInfo}
32     * @private
33     */
34    this.userInfo_ = new print_preview.UserInfo();
35
36    /**
37     * Metrics object used to report usage statistics.
38     * @type {!print_preview.Metrics}
39     * @private
40     */
41    this.metrics_ = new print_preview.Metrics();
42
43    /**
44     * Application state.
45     * @type {!print_preview.AppState}
46     * @private
47     */
48    this.appState_ = new print_preview.AppState();
49
50    /**
51     * Data model that holds information about the document to print.
52     * @type {!print_preview.DocumentInfo}
53     * @private
54     */
55    this.documentInfo_ = new print_preview.DocumentInfo();
56
57    /**
58     * Data store which holds print destinations.
59     * @type {!print_preview.DestinationStore}
60     * @private
61     */
62    this.destinationStore_ = new print_preview.DestinationStore(
63        this.nativeLayer_, this.appState_);
64
65    /**
66     * Storage of the print ticket used to create the print job.
67     * @type {!print_preview.PrintTicketStore}
68     * @private
69     */
70    this.printTicketStore_ = new print_preview.PrintTicketStore(
71        this.destinationStore_, this.appState_, this.documentInfo_);
72
73    /**
74     * Holds the print and cancel buttons and renders some document statistics.
75     * @type {!print_preview.PrintHeader}
76     * @private
77     */
78    this.printHeader_ = new print_preview.PrintHeader(
79        this.printTicketStore_, this.destinationStore_);
80    this.addChild(this.printHeader_);
81
82    /**
83     * Component used to search for print destinations.
84     * @type {!print_preview.DestinationSearch}
85     * @private
86     */
87    this.destinationSearch_ = new print_preview.DestinationSearch(
88        this.destinationStore_, this.userInfo_, this.metrics_);
89    this.addChild(this.destinationSearch_);
90
91    /**
92     * Component that renders the print destination.
93     * @type {!print_preview.DestinationSettings}
94     * @private
95     */
96    this.destinationSettings_ = new print_preview.DestinationSettings(
97        this.destinationStore_);
98    this.addChild(this.destinationSettings_);
99
100    /**
101     * Component that renders UI for entering in page range.
102     * @type {!print_preview.PageSettings}
103     * @private
104     */
105    this.pageSettings_ = new print_preview.PageSettings(this.printTicketStore_);
106    this.addChild(this.pageSettings_);
107
108    /**
109     * Component that renders the copies settings.
110     * @type {!print_preview.CopiesSettings}
111     * @private
112     */
113    this.copiesSettings_ = new print_preview.CopiesSettings(
114        this.printTicketStore_.copies, this.printTicketStore_.collate);
115    this.addChild(this.copiesSettings_);
116
117    /**
118     * Component that renders the layout settings.
119     * @type {!print_preview.LayoutSettings}
120     * @private
121     */
122    this.layoutSettings_ = new print_preview.LayoutSettings(
123        this.printTicketStore_);
124    this.addChild(this.layoutSettings_);
125
126    /**
127     * Component that renders the color options.
128     * @type {!print_preview.ColorSettings}
129     * @private
130     */
131    this.colorSettings_ =
132        new print_preview.ColorSettings(this.printTicketStore_.color);
133    this.addChild(this.colorSettings_);
134
135    /**
136     * Component that renders a select box for choosing margin settings.
137     * @type {!print_preview.MarginSettings}
138     * @private
139     */
140    this.marginSettings_ = new print_preview.MarginSettings(
141        this.printTicketStore_);
142    this.addChild(this.marginSettings_);
143
144    /**
145     * Component that renders miscellaneous print options.
146     * @type {!print_preview.OtherOptionsSettings}
147     * @private
148     */
149    this.otherOptionsSettings_ = new print_preview.OtherOptionsSettings(
150        this.printTicketStore_);
151    this.addChild(this.otherOptionsSettings_);
152
153    /**
154     * Area of the UI that holds the print preview.
155     * @type {!print_preview.PreviewArea}
156     * @private
157     */
158    this.previewArea_ = new print_preview.PreviewArea(this.destinationStore_,
159                                                      this.printTicketStore_,
160                                                      this.nativeLayer_,
161                                                      this.documentInfo_);
162    this.addChild(this.previewArea_);
163
164    /**
165     * Interface to the Google Cloud Print API. Null if Google Cloud Print
166     * integration is disabled.
167     * @type {cloudprint.CloudPrintInterface}
168     * @private
169     */
170    this.cloudPrintInterface_ = null;
171
172    /**
173     * Whether in kiosk mode where print preview can print automatically without
174     * user intervention. See http://crbug.com/31395. Print will start when
175     * both the print ticket has been initialized, and an initial printer has
176     * been selected.
177     * @type {boolean}
178     * @private
179     */
180    this.isInKioskAutoPrintMode_ = false;
181
182    /**
183     * State of the print preview UI.
184     * @type {print_preview.PrintPreview.UiState_}
185     * @private
186     */
187    this.uiState_ = PrintPreview.UiState_.INITIALIZING;
188
189    /**
190     * Whether document preview generation is in progress.
191     * @type {boolean}
192     * @private
193     */
194    this.isPreviewGenerationInProgress_ = true;
195  };
196
197  /**
198   * States of the print preview.
199   * @enum {string}
200   * @private
201   */
202  PrintPreview.UiState_ = {
203    INITIALIZING: 'initializing',
204    READY: 'ready',
205    OPENING_PDF_PREVIEW: 'opening-pdf-preview',
206    OPENING_NATIVE_PRINT_DIALOG: 'opening-native-print-dialog',
207    PRINTING: 'printing',
208    FILE_SELECTION: 'file-selection',
209    CLOSING: 'closing',
210    ERROR: 'error'
211  };
212
213  PrintPreview.prototype = {
214    __proto__: print_preview.Component.prototype,
215
216    /** Sets up the page and print preview by getting the printer list. */
217    initialize: function() {
218      this.decorate($('print-preview'));
219      i18nTemplate.process(document, templateData);
220      if (!this.previewArea_.hasCompatiblePlugin) {
221        this.setIsEnabled_(false);
222      }
223      this.nativeLayer_.startGetInitialSettings();
224      this.destinationStore_.startLoadLocalDestinations();
225    },
226
227    /** @override */
228    enterDocument: function() {
229      // Native layer events.
230      this.tracker.add(
231          this.nativeLayer_,
232          print_preview.NativeLayer.EventType.INITIAL_SETTINGS_SET,
233          this.onInitialSettingsSet_.bind(this));
234      this.tracker.add(
235          this.nativeLayer_,
236          print_preview.NativeLayer.EventType.CLOUD_PRINT_ENABLE,
237          this.onCloudPrintEnable_.bind(this));
238      this.tracker.add(
239          this.nativeLayer_,
240          print_preview.NativeLayer.EventType.PRINT_TO_CLOUD,
241          this.onPrintToCloud_.bind(this));
242      this.tracker.add(
243          this.nativeLayer_,
244          print_preview.NativeLayer.EventType.FILE_SELECTION_CANCEL,
245          this.onFileSelectionCancel_.bind(this));
246      this.tracker.add(
247          this.nativeLayer_,
248          print_preview.NativeLayer.EventType.FILE_SELECTION_COMPLETE,
249          this.onFileSelectionComplete_.bind(this));
250      this.tracker.add(
251          this.nativeLayer_,
252          print_preview.NativeLayer.EventType.SETTINGS_INVALID,
253          this.onSettingsInvalid_.bind(this));
254      this.tracker.add(
255          this.nativeLayer_,
256          print_preview.NativeLayer.EventType.DISABLE_SCALING,
257          this.onDisableScaling_.bind(this));
258
259      this.tracker.add(
260          $('system-dialog-link'),
261          'click',
262          this.openSystemPrintDialog_.bind(this));
263      this.tracker.add(
264          $('cloud-print-dialog-link'),
265          'click',
266          this.onCloudPrintDialogLinkClick_.bind(this));
267      this.tracker.add(
268          $('open-pdf-in-preview-link'),
269          'click',
270          this.onOpenPdfInPreviewLinkClick_.bind(this));
271
272      this.tracker.add(
273          this.previewArea_,
274          print_preview.PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS,
275          this.onPreviewGenerationInProgress_.bind(this));
276      this.tracker.add(
277          this.previewArea_,
278          print_preview.PreviewArea.EventType.PREVIEW_GENERATION_DONE,
279          this.onPreviewGenerationDone_.bind(this));
280      this.tracker.add(
281          this.previewArea_,
282          print_preview.PreviewArea.EventType.PREVIEW_GENERATION_FAIL,
283          this.onPreviewGenerationFail_.bind(this));
284      this.tracker.add(
285          this.previewArea_,
286          print_preview.PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK,
287          this.openSystemPrintDialog_.bind(this));
288
289      this.tracker.add(
290          this.destinationStore_,
291          print_preview.DestinationStore.EventType.
292              SELECTED_DESTINATION_CAPABILITIES_READY,
293          this.printIfReady_.bind(this));
294      this.tracker.add(
295          this.destinationStore_,
296          print_preview.DestinationStore.EventType.DESTINATION_SELECT,
297          this.onDestinationSelect_.bind(this));
298      this.tracker.add(
299          this.destinationStore_,
300          print_preview.DestinationStore.EventType.DESTINATION_SEARCH_DONE,
301          this.onDestinationSearchDone_.bind(this));
302
303      this.tracker.add(
304          this.printHeader_,
305          print_preview.PrintHeader.EventType.PRINT_BUTTON_CLICK,
306          this.onPrintButtonClick_.bind(this));
307      this.tracker.add(
308          this.printHeader_,
309          print_preview.PrintHeader.EventType.CANCEL_BUTTON_CLICK,
310          this.onCancelButtonClick_.bind(this));
311
312      this.tracker.add(window, 'keydown', this.onKeyDown_.bind(this));
313
314      this.tracker.add(
315          this.destinationSettings_,
316          print_preview.DestinationSettings.EventType.CHANGE_BUTTON_ACTIVATE,
317          this.onDestinationChangeButtonActivate_.bind(this));
318
319      this.tracker.add(
320          this.destinationSearch_,
321          print_preview.DestinationSearch.EventType.MANAGE_CLOUD_DESTINATIONS,
322          this.onManageCloudDestinationsActivated_.bind(this));
323      this.tracker.add(
324          this.destinationSearch_,
325          print_preview.DestinationSearch.EventType.MANAGE_LOCAL_DESTINATIONS,
326          this.onManageLocalDestinationsActivated_.bind(this));
327      this.tracker.add(
328          this.destinationSearch_,
329          print_preview.DestinationSearch.EventType.SIGN_IN,
330          this.onCloudPrintSignInActivated_.bind(this));
331
332      // TODO(rltoscano): Move no-destinations-promo into its own component
333      // instead being part of PrintPreview.
334      this.tracker.add(
335          this.getChildElement('#no-destinations-promo .close-button'),
336          'click',
337          this.onNoDestinationsPromoClose_.bind(this));
338      this.tracker.add(
339          this.getChildElement('#no-destinations-promo .not-now-button'),
340          'click',
341          this.onNoDestinationsPromoClose_.bind(this));
342      this.tracker.add(
343          this.getChildElement('#no-destinations-promo .add-printer-button'),
344          'click',
345          this.onNoDestinationsPromoClick_.bind(this));
346    },
347
348    /** @override */
349    decorateInternal: function() {
350      this.printHeader_.decorate($('print-header'));
351      this.destinationSearch_.decorate($('destination-search'));
352      this.destinationSettings_.decorate($('destination-settings'));
353      this.pageSettings_.decorate($('page-settings'));
354      this.copiesSettings_.decorate($('copies-settings'));
355      this.layoutSettings_.decorate($('layout-settings'));
356      this.colorSettings_.decorate($('color-settings'));
357      this.marginSettings_.decorate($('margin-settings'));
358      this.otherOptionsSettings_.decorate($('other-options-settings'));
359      this.previewArea_.decorate($('preview-area'));
360
361      // Set some of the parameterized text in the no-destinations promotion.
362      var noDestsPromoGcpDescription =
363          this.getChildElement('#no-destinations-promo .gcp-description');
364      noDestsPromoGcpDescription.innerHTML = localStrings.getStringF(
365          'noDestsPromoGcpDesc',
366          '<a target="_blank" href="http://www.google.com/cloudprint/learn/">',
367          '</a>');
368
369      setIsVisible($('open-pdf-in-preview-link'), cr.isMac);
370    },
371
372    /**
373     * Sets whether the controls in the print preview are enabled.
374     * @param {boolean} isEnabled Whether the controls in the print preview are
375     *     enabled.
376     * @private
377     */
378    setIsEnabled_: function(isEnabled) {
379      $('system-dialog-link').disabled = !isEnabled;
380      $('cloud-print-dialog-link').disabled = !isEnabled;
381      $('open-pdf-in-preview-link').disabled = !isEnabled;
382      this.printHeader_.isEnabled = isEnabled;
383      this.destinationSettings_.isEnabled = isEnabled;
384      this.pageSettings_.isEnabled = isEnabled;
385      this.copiesSettings_.isEnabled = isEnabled;
386      this.layoutSettings_.isEnabled = isEnabled;
387      this.colorSettings_.isEnabled = isEnabled;
388      this.marginSettings_.isEnabled = isEnabled;
389      this.otherOptionsSettings_.isEnabled = isEnabled;
390    },
391
392    /**
393     * Prints the document or launches a pdf preview on the local system.
394     * @param {boolean} isPdfPreview Whether to launch the pdf preview.
395     * @private
396     */
397    printDocumentOrOpenPdfPreview_: function(isPdfPreview) {
398      assert(this.uiState_ == PrintPreview.UiState_.READY,
399             'Print document request received when not in ready state: ' +
400                 this.uiState_);
401      if (isPdfPreview) {
402        this.uiState_ = PrintPreview.UiState_.OPENING_PDF_PREVIEW;
403      } else if (this.destinationStore_.selectedDestination.id ==
404          print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) {
405        this.uiState_ = PrintPreview.UiState_.FILE_SELECTION;
406      } else {
407        this.uiState_ = PrintPreview.UiState_.PRINTING;
408      }
409      this.setIsEnabled_(false);
410      if (this.printIfReady_() &&
411          ((this.destinationStore_.selectedDestination.isLocal &&
412            this.destinationStore_.selectedDestination.id !=
413                print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) ||
414           this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW)) {
415        // Hide the dialog for now. The actual print command will be issued when
416        // the preview generation is done.
417        this.nativeLayer_.startHideDialog();
418      }
419    },
420
421    /**
422     * Attempts to print if needed and if ready.
423     * @return {boolean} Whether a print request was issued.
424     * @private
425     */
426    printIfReady_: function() {
427      if ((this.uiState_ == PrintPreview.UiState_.PRINTING ||
428              this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW ||
429              this.uiState_ == PrintPreview.UiState_.FILE_SELECTION ||
430              this.isInKioskAutoPrintMode_) &&
431          !this.isPreviewGenerationInProgress_ &&
432          this.destinationStore_.selectedDestination &&
433          this.destinationStore_.selectedDestination.capabilities) {
434        assert(this.printTicketStore_.isTicketValid(),
435               'Trying to print with invalid ticket');
436        this.nativeLayer_.startPrint(
437            this.destinationStore_.selectedDestination,
438            this.printTicketStore_,
439            this.cloudPrintInterface_,
440            this.documentInfo_,
441            this.uiState_ == PrintPreview.UiState_.OPENING_PDF_PREVIEW);
442        return true;
443      } else {
444        return false;
445      }
446    },
447
448    /**
449     * Closes the print preview.
450     * @private
451     */
452    close_: function() {
453      this.exitDocument();
454      this.uiState_ = PrintPreview.UiState_.CLOSING;
455      this.nativeLayer_.startCloseDialog();
456    },
457
458    /**
459     * Opens the native system print dialog after disabling all controls.
460     * @private
461     */
462    openSystemPrintDialog_: function() {
463      setIsVisible($('system-dialog-throbber'), true);
464      this.setIsEnabled_(false);
465      this.uiState_ = PrintPreview.UiState_.OPENING_NATIVE_PRINT_DIALOG;
466      this.nativeLayer_.startShowSystemDialog();
467    },
468
469    /**
470     * Called when the native layer has initial settings to set. Sets the
471     * initial settings of the print preview and begins fetching print
472     * destinations.
473     * @param {cr.Event} event Contains the initial print preview settings
474     *     persisted through the session.
475     * @private
476     */
477    onInitialSettingsSet_: function(event) {
478      assert(this.uiState_ == PrintPreview.UiState_.INITIALIZING,
479             'Updating initial settings when not in initializing state: ' +
480                 this.uiState_);
481      this.uiState_ = PrintPreview.UiState_.READY;
482
483      var settings = event.initialSettings;
484      this.isInKioskAutoPrintMode_ = settings.isInKioskAutoPrintMode;
485
486      // The following components must be initialized in this order.
487      this.appState_.init(settings.serializedAppStateStr);
488      this.documentInfo_.init(
489          settings.isDocumentModifiable,
490          settings.documentTitle,
491          settings.documentHasSelection);
492      this.printTicketStore_.init(
493          settings.thousandsDelimeter,
494          settings.decimalDelimeter,
495          settings.unitType,
496          settings.selectionOnly);
497      this.destinationStore_.init(settings.systemDefaultDestinationId);
498    },
499
500    /**
501     * Calls when the native layer enables Google Cloud Print integration.
502     * Fetches the user's cloud printers.
503     * @param {cr.Event} event Contains the base URL of the Google Cloud Print
504     *     service.
505     * @private
506     */
507    onCloudPrintEnable_: function(event) {
508      this.cloudPrintInterface_ =
509          new cloudprint.CloudPrintInterface(event.baseCloudPrintUrl,
510                                             this.nativeLayer_);
511      this.tracker.add(
512          this.cloudPrintInterface_,
513          cloudprint.CloudPrintInterface.EventType.SUBMIT_DONE,
514          this.onCloudPrintSubmitDone_.bind(this));
515      this.tracker.add(
516          this.cloudPrintInterface_,
517          cloudprint.CloudPrintInterface.EventType.SEARCH_FAILED,
518          this.onCloudPrintError_.bind(this));
519      this.tracker.add(
520          this.cloudPrintInterface_,
521          cloudprint.CloudPrintInterface.EventType.SUBMIT_FAILED,
522          this.onCloudPrintError_.bind(this));
523      this.tracker.add(
524          this.cloudPrintInterface_,
525          cloudprint.CloudPrintInterface.EventType.PRINTER_FAILED,
526          this.onCloudPrintError_.bind(this));
527      this.tracker.add(
528          this.cloudPrintInterface_,
529          cloudprint.CloudPrintInterface.EventType.
530              UPDATE_PRINTER_TOS_ACCEPTANCE_FAILED,
531          this.onCloudPrintError_.bind(this));
532
533      this.userInfo_.setCloudPrintInterface(this.cloudPrintInterface_);
534      this.destinationStore_.setCloudPrintInterface(this.cloudPrintInterface_);
535      this.destinationStore_.startLoadCloudDestinations(true);
536      if (this.destinationSearch_.getIsVisible()) {
537        this.destinationStore_.startLoadCloudDestinations(false);
538      }
539    },
540
541    /**
542     * Called from the native layer when ready to print to Google Cloud Print.
543     * @param {cr.Event} event Contains the body to send in the HTTP request.
544     * @private
545     */
546    onPrintToCloud_: function(event) {
547      assert(this.uiState_ == PrintPreview.UiState_.PRINTING,
548             'Document ready to be sent to the cloud when not in printing ' +
549                 'state: ' + this.uiState_);
550      assert(this.cloudPrintInterface_ != null,
551             'Google Cloud Print is not enabled');
552      this.cloudPrintInterface_.submit(
553          this.destinationStore_.selectedDestination,
554          this.printTicketStore_,
555          this.documentInfo_,
556          event.data);
557    },
558
559    /**
560     * Called from the native layer when the user cancels the save-to-pdf file
561     * selection dialog.
562     * @private
563     */
564    onFileSelectionCancel_: function() {
565      assert(this.uiState_ == PrintPreview.UiState_.FILE_SELECTION,
566             'File selection cancelled when not in file-selection state: ' +
567                 this.uiState_);
568      this.setIsEnabled_(true);
569      this.uiState_ = PrintPreview.UiState_.READY;
570    },
571
572    /**
573     * Called from the native layer when save-to-pdf file selection is complete.
574     * @private
575     */
576    onFileSelectionComplete_: function() {
577      assert(this.uiState_ == PrintPreview.UiState_.FILE_SELECTION,
578             'File selection completed when not in file-selection state: ' +
579                 this.uiState_);
580      this.previewArea_.showCustomMessage(
581          localStrings.getString('printingToPDFInProgress'));
582      this.uiState_ = PrintPreview.UiState_.PRINTING;
583    },
584
585    /**
586     * Called after successfully submitting a job to Google Cloud Print.
587     * @param {!cr.Event} event Contains the ID of the submitted print job.
588     * @private
589     */
590    onCloudPrintSubmitDone_: function(event) {
591      assert(this.uiState_ == PrintPreview.UiState_.PRINTING,
592             'Submited job to Google Cloud Print but not in printing state ' +
593                 this.uiState_);
594      if (this.destinationStore_.selectedDestination.id ==
595              print_preview.Destination.GooglePromotedId.FEDEX) {
596        this.nativeLayer_.startForceOpenNewTab(
597            'https://www.google.com/cloudprint/fedexcode.html?jobid=' +
598            event.jobId);
599      }
600      this.close_();
601    },
602
603    /**
604     * Called when there was an error communicating with Google Cloud print.
605     * Displays an error message in the print header.
606     * @param {!cr.Event} event Contains the error message.
607     * @private
608     */
609    onCloudPrintError_: function(event) {
610      if (event.status == 403) {
611        this.destinationSearch_.showCloudPrintPromo();
612      } else if (event.status == 0) {
613        return; // Ignore, the system does not have internet connectivity.
614      } else {
615        this.printHeader_.setErrorMessage(event.message);
616      }
617      if (event.status == 200) {
618        console.error('Google Cloud Print Error: (' + event.errorCode + ') ' +
619                      event.message);
620      } else {
621        console.error('Google Cloud Print Error: HTTP status ' + event.status);
622      }
623    },
624
625    /**
626     * Called when the preview area's preview generation is in progress.
627     * @private
628     */
629    onPreviewGenerationInProgress_: function() {
630      this.isPreviewGenerationInProgress_ = true;
631    },
632
633    /**
634     * Called when the preview area's preview generation is complete.
635     * @private
636     */
637    onPreviewGenerationDone_: function() {
638      this.isPreviewGenerationInProgress_ = false;
639      this.printHeader_.isPrintButtonEnabled = true;
640      this.printIfReady_();
641    },
642
643    /**
644     * Called when the preview area's preview failed to load.
645     * @private
646     */
647    onPreviewGenerationFail_: function() {
648      this.isPreviewGenerationInProgress_ = false;
649      this.printHeader_.isPrintButtonEnabled = false;
650      if (this.uiState_ == PrintPreview.UiState_.PRINTING) {
651        this.nativeLayer_.startCancelPendingPrint();
652      }
653    },
654
655    /**
656     * Called when the 'Open pdf in preview' link is clicked. Launches the pdf
657     * preview app.
658     * @private
659     */
660    onOpenPdfInPreviewLinkClick_: function() {
661      assert(this.uiState_ == PrintPreview.UiState_.READY,
662             'Trying to open pdf in preview when not in ready state: ' +
663                 this.uiState_);
664      setIsVisible($('open-preview-app-throbber'), true);
665      this.previewArea_.showCustomMessage(
666          localStrings.getString('openingPDFInPreview'));
667      this.printDocumentOrOpenPdfPreview_(true /*isPdfPreview*/);
668    },
669
670    /**
671     * Called when the print header's print button is clicked. Prints the
672     * document.
673     * @private
674     */
675    onPrintButtonClick_: function() {
676      assert(this.uiState_ == PrintPreview.UiState_.READY,
677             'Trying to print when not in ready state: ' + this.uiState_);
678      this.printDocumentOrOpenPdfPreview_(false /*isPdfPreview*/);
679    },
680
681    /**
682     * Called when the print header's cancel button is clicked. Closes the
683     * print dialog.
684     * @private
685     */
686    onCancelButtonClick_: function() {
687      this.close_();
688    },
689
690    /**
691     * Consume escape key presses and ctrl + shift + p. Delegate everything else
692     * to the preview area.
693     * @param {KeyboardEvent} e The keyboard event.
694     * @private
695     */
696    onKeyDown_: function(e) {
697      // Escape key closes the dialog.
698      if (e.keyCode == 27 && !e.shiftKey && !e.ctrlKey && !e.altKey &&
699          !e.metaKey) {
700        if (this.destinationSearch_.getIsVisible()) {
701          this.destinationSearch_.setIsVisible(false);
702          this.metrics_.incrementDestinationSearchBucket(
703              print_preview.Metrics.DestinationSearchBucket.CANCELED);
704        } else {
705          // <if expr="pp_ifdef('toolkit_views')">
706          // On the toolkit_views environment, ESC key is handled by C++-side
707          // instead of JS-side.
708          return;
709          // </if>
710          // <if expr="not pp_ifdef('toolkit_views')">
711          // Dummy comment to absorb previous line's comment symbol.
712          this.close_();
713          // </if>
714        }
715        e.preventDefault();
716        return;
717      }
718
719      // Ctrl + Shift + p / Mac equivalent.
720      if (e.keyCode == 80) {
721        if ((cr.isMac && e.metaKey && e.altKey && !e.shiftKey && !e.ctrlKey) ||
722            (!cr.isMac && e.shiftKey && e.ctrlKey && !e.altKey && !e.metaKey)) {
723          this.openSystemPrintDialog_();
724          e.preventDefault();
725          return;
726        }
727      }
728
729      if (e.keyCode == 13 /*enter*/ &&
730          !this.destinationSearch_.getIsVisible() &&
731          this.printTicketStore_.isTicketValid()) {
732        assert(this.uiState_ == PrintPreview.UiState_.READY,
733          'Trying to print when not in ready state: ' + this.uiState_);
734        this.printDocumentOrOpenPdfPreview_(false /*isPdfPreview*/);
735        e.preventDefault();
736        return;
737      }
738
739      // Pass certain directional keyboard events to the PDF viewer.
740      this.previewArea_.handleDirectionalKeyEvent(e);
741    },
742
743    /**
744     * Called when native layer receives invalid settings for a print request.
745     * @private
746     */
747    onSettingsInvalid_: function() {
748      this.uiState_ = PrintPreview.UiState_.ERROR;
749      console.error('Invalid settings error reported from native layer');
750      this.previewArea_.showCustomMessage(
751          localStrings.getString('invalidPrinterSettings'));
752    },
753
754    /**
755     * Called when the destination settings' change button is activated.
756     * Displays the destination search component.
757     * @private
758     */
759    onDestinationChangeButtonActivate_: function() {
760      this.destinationSearch_.setIsVisible(true);
761      this.destinationStore_.startLoadCloudDestinations(false);
762      this.metrics_.incrementDestinationSearchBucket(
763          print_preview.Metrics.DestinationSearchBucket.SHOWN);
764    },
765
766    /**
767     * Called when the destination search dispatches manage cloud destinations
768     * event. Calls corresponding native layer method.
769     * @private
770     */
771    onManageCloudDestinationsActivated_: function() {
772      this.nativeLayer_.startManageCloudDestinations();
773    },
774
775    /**
776     * Called when the destination search dispatches manage local destinations
777     * event. Calls corresponding native layer method.
778     * @private
779     */
780    onManageLocalDestinationsActivated_: function() {
781      this.nativeLayer_.startManageLocalDestinations();
782    },
783
784    /**
785     * Called when the user wants to sign in to Google Cloud Print. Calls the
786     * corresponding native layer event.
787     * @private
788     */
789    onCloudPrintSignInActivated_: function() {
790      this.nativeLayer_.startCloudPrintSignIn();
791    },
792
793    /**
794     * Called when the native layer dispatches a DISABLE_SCALING event. Updates
795     * the print ticket.
796     * @private
797     */
798    onDisableScaling_: function() {
799      // TODO(rltoscano): This should be a property of the document and should
800      // affect whether the fit-to-page capability is available. That way, we
801      // don't mistake this value for a user provided value.
802      // See crbug.com/234857
803      this.printTicketStore_.fitToPage.updateValue(false);
804    },
805
806    /**
807     * Called when the open-cloud-print-dialog link is clicked. Opens the Google
808     * Cloud Print web dialog.
809     * @private
810     */
811    onCloudPrintDialogLinkClick_: function() {
812      assert(this.uiState_ == PrintPreview.UiState_.READY,
813             'Opening Google Cloud Print dialog when not in ready state: ' +
814                 this.uiState_);
815      setIsVisible($('cloud-print-dialog-throbber'), true);
816      this.setIsEnabled_(false);
817      this.uiState_ = PrintPreview.UiState_.OPENING_NATIVE_PRINT_DIALOG;
818      this.nativeLayer_.startShowCloudPrintDialog();
819    },
820
821    /**
822     * Called when a print destination is selected. Shows/hides the "Print with
823     * Cloud Print" link in the navbar.
824     * @private
825     */
826    onDestinationSelect_: function() {
827      var selectedDest = this.destinationStore_.selectedDestination;
828      setIsVisible($('cloud-print-dialog-link'),
829                   !cr.isChromeOS && !selectedDest.isLocal);
830    },
831
832    /**
833     * Called when the destination store loads a group of destinations. Shows
834     * a promo on Chrome OS if the user has no print destinations promoting
835     * Google Cloud Print.
836     * @private
837     */
838    onDestinationSearchDone_: function() {
839      var isPromoVisible = cr.isChromeOS &&
840          this.cloudPrintInterface_ &&
841          this.userInfo_.getUserEmail() &&
842          !this.appState_.isGcpPromoDismissed &&
843          !this.destinationStore_.isLocalDestinationsSearchInProgress &&
844          !this.destinationStore_.isCloudDestinationsSearchInProgress &&
845          this.destinationStore_.hasOnlyDefaultCloudDestinations();
846      setIsVisible(this.getChildElement('#no-destinations-promo'),
847                   isPromoVisible);
848      if (isPromoVisible) {
849        this.metrics_.incrementGcpPromoBucket(
850            print_preview.Metrics.GcpPromoBucket.SHOWN);
851      }
852    },
853
854    /**
855     * Called when the close button on the no-destinations-promotion is clicked.
856     * Hides the promotion.
857     * @private
858     */
859    onNoDestinationsPromoClose_: function() {
860      this.metrics_.incrementGcpPromoBucket(
861          print_preview.Metrics.GcpPromoBucket.DISMISSED);
862      setIsVisible(this.getChildElement('#no-destinations-promo'), false);
863      this.appState_.persistIsGcpPromoDismissed(true);
864    },
865
866    /**
867     * Called when the no-destinations promotion link is clicked. Opens the
868     * Google Cloud Print management page and closes the print preview.
869     * @private
870     */
871    onNoDestinationsPromoClick_: function() {
872      this.metrics_.incrementGcpPromoBucket(
873          print_preview.Metrics.GcpPromoBucket.CLICKED);
874      this.appState_.persistIsGcpPromoDismissed(true);
875      window.open(this.cloudPrintInterface_.baseUrl + '?user=' +
876                  this.userInfo_.getUserEmail() + '#printers');
877      this.close_();
878    }
879  };
880
881  // Export
882  return {
883    PrintPreview: PrintPreview
884  };
885});
886
887// Pull in all other scripts in a single shot.
888<include src="data/page_number_set.js"/>
889<include src="data/destination.js"/>
890<include src="data/local_parsers.js"/>
891<include src="data/cloud_parsers.js"/>
892<include src="data/destination_store.js"/>
893<include src="data/margins.js"/>
894<include src="data/document_info.js"/>
895<include src="data/printable_area.js"/>
896<include src="data/measurement_system.js"/>
897<include src="data/print_ticket_store.js"/>
898<include src="data/coordinate2d.js"/>
899<include src="data/size.js"/>
900<include src="data/capabilities_holder.js"/>
901<include src="data/user_info.js"/>
902<include src="data/app_state.js"/>
903
904<include src="data/ticket_items/ticket_item.js"/>
905
906<include src="data/ticket_items/custom_margins.js"/>
907<include src="data/ticket_items/collate.js"/>
908<include src="data/ticket_items/color.js"/>
909<include src="data/ticket_items/copies.js"/>
910<include src="data/ticket_items/duplex.js"/>
911<include src="data/ticket_items/header_footer.js"/>
912<include src="data/ticket_items/landscape.js"/>
913<include src="data/ticket_items/margins_type.js"/>
914<include src="data/ticket_items/page_range.js"/>
915<include src="data/ticket_items/fit_to_page.js"/>
916<include src="data/ticket_items/css_background.js"/>
917<include src="data/ticket_items/selection_only.js"/>
918
919<include src="native_layer.js"/>
920<include src="print_preview_animations.js"/>
921<include src="cloud_print_interface.js"/>
922<include src="print_preview_utils.js"/>
923<include src="print_header.js"/>
924<include src="metrics.js"/>
925
926<include src="settings/page_settings.js"/>
927<include src="settings/copies_settings.js"/>
928<include src="settings/layout_settings.js"/>
929<include src="settings/color_settings.js"/>
930<include src="settings/margin_settings.js"/>
931<include src="settings/destination_settings.js"/>
932<include src="settings/other_options_settings.js"/>
933
934<include src="previewarea/margin_control.js"/>
935<include src="previewarea/margin_control_container.js"/>
936<include src="previewarea/preview_area.js"/>
937<include src="preview_generator.js"/>
938
939<include src="search/destination_list.js"/>
940<include src="search/cloud_destination_list.js"/>
941<include src="search/recent_destination_list.js"/>
942<include src="search/destination_list_item.js"/>
943<include src="search/destination_search.js"/>
944<include src="search/search_box.js"/>
945<include src="search/fedex_tos.js"/>
946
947window.addEventListener('DOMContentLoaded', function() {
948  printPreview = new print_preview.PrintPreview();
949  printPreview.initialize();
950});
951