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
5cr.define('print_preview', function() {
6  'use strict';
7
8  // TODO(rltoscano): Maybe clear print ticket when destination changes. Or
9  // better yet, carry over any print ticket state that is possible. I.e. if
10  // destination changes, the new destination might not support duplex anymore,
11  // so we should clear the ticket's isDuplexEnabled state.
12
13  /**
14   * Storage of the print ticket and document statistics. Dispatches events when
15   * the contents of the print ticket or document statistics change. Also
16   * handles validation of the print ticket against destination capabilities and
17   * against the document.
18   * @param {!print_preview.DestinationStore} destinationStore Used to
19   *     understand which printer is selected.
20   * @param {!print_preview.AppState} appState Print preview application state.
21   * @param {!print_preview.DocumentInfo} documentInfo Document data model.
22   * @constructor
23   * @extends {cr.EventTarget}
24   */
25  function PrintTicketStore(destinationStore, appState, documentInfo) {
26    cr.EventTarget.call(this);
27
28    /**
29     * Destination store used to understand which printer is selected.
30     * @type {!print_preview.DestinationStore}
31     * @private
32     */
33    this.destinationStore_ = destinationStore;
34
35    /**
36     * App state used to persist and load ticket values.
37     * @type {!print_preview.AppState}
38     * @private
39     */
40    this.appState_ = appState;
41
42    /**
43     * Information about the document to print.
44     * @type {!print_preview.DocumentInfo}
45     * @private
46     */
47    this.documentInfo_ = documentInfo;
48
49    /**
50     * Printing capabilities of Chromium and the currently selected destination.
51     * @type {!print_preview.CapabilitiesHolder}
52     * @private
53     */
54    this.capabilitiesHolder_ = new print_preview.CapabilitiesHolder();
55
56    /**
57     * Current measurement system. Used to work with margin measurements.
58     * @type {!print_preview.MeasurementSystem}
59     * @private
60     */
61    this.measurementSystem_ = new print_preview.MeasurementSystem(
62        ',', '.', print_preview.MeasurementSystem.UnitType.IMPERIAL);
63
64    /**
65     * Collate ticket item.
66     * @type {!print_preview.ticket_items.Collate}
67     * @private
68     */
69    this.collate_ = new print_preview.ticket_items.Collate(
70        this.appState_, this.destinationStore_);
71
72    /**
73     * Color ticket item.
74     * @type {!print_preview.ticket_items.Color}
75     * @private
76     */
77    this.color_ = new print_preview.ticket_items.Color(
78        this.appState_, this.destinationStore_);
79
80    /**
81     * Copies ticket item.
82     * @type {!print_preview.ticket_items.Copies}
83     * @private
84     */
85    this.copies_ =
86        new print_preview.ticket_items.Copies(this.destinationStore_);
87
88    /**
89     * Duplex ticket item.
90     * @type {!print_preview.ticket_items.Duplex}
91     * @private
92     */
93    this.duplex_ = new print_preview.ticket_items.Duplex(
94        this.appState_, this.destinationStore_);
95
96    /**
97     * Page range ticket item.
98     * @type {!print_preview.ticket_items.PageRange}
99     * @private
100     */
101    this.pageRange_ =
102        new print_preview.ticket_items.PageRange(this.documentInfo_);
103
104    /**
105     * Custom margins ticket item.
106     * @type {!print_preview.ticket_items.CustomMargins}
107     * @private
108     */
109    this.customMargins_ = new print_preview.ticket_items.CustomMargins(
110        this.appState_, this.documentInfo_);
111
112    /**
113     * Margins type ticket item.
114     * @type {!print_preview.ticket_items.MarginsType}
115     * @private
116     */
117    this.marginsType_ = new print_preview.ticket_items.MarginsType(
118        this.appState_, this.documentInfo_, this.customMargins_);
119
120    /**
121     * Media size ticket item.
122     * @type {!print_preview.ticket_items.MediaSize}
123     * @private
124     */
125    this.mediaSize_ = new print_preview.ticket_items.MediaSize(
126        this.appState_,
127        this.destinationStore_,
128        this.documentInfo_,
129        this.marginsType_,
130        this.customMargins_);
131
132    /**
133     * Landscape ticket item.
134     * @type {!print_preview.ticket_items.Landscape}
135     * @private
136     */
137    this.landscape_ = new print_preview.ticket_items.Landscape(
138        this.appState_,
139        this.destinationStore_,
140        this.documentInfo_,
141        this.marginsType_,
142        this.customMargins_);
143
144    /**
145     * Header-footer ticket item.
146     * @type {!print_preview.ticket_items.HeaderFooter}
147     * @private
148     */
149    this.headerFooter_ = new print_preview.ticket_items.HeaderFooter(
150        this.appState_,
151        this.documentInfo_,
152        this.marginsType_,
153        this.customMargins_);
154
155    /**
156     * Fit-to-page ticket item.
157     * @type {!print_preview.ticket_items.FitToPage}
158     * @private
159     */
160    this.fitToPage_ = new print_preview.ticket_items.FitToPage(
161        this.documentInfo_, this.destinationStore_);
162
163    /**
164     * Print CSS backgrounds ticket item.
165     * @type {!print_preview.ticket_items.CssBackground}
166     * @private
167     */
168    this.cssBackground_ = new print_preview.ticket_items.CssBackground(
169        this.appState_, this.documentInfo_);
170
171    /**
172     * Print selection only ticket item.
173     * @type {!print_preview.ticket_items.SelectionOnly}
174     * @private
175     */
176    this.selectionOnly_ =
177        new print_preview.ticket_items.SelectionOnly(this.documentInfo_);
178
179    /**
180     * Vendor ticket items.
181     * @type {!print_preview.ticket_items.VendorItems}
182     * @private
183     */
184    this.vendorItems_ = new print_preview.ticket_items.VendorItems(
185        this.appState_, this.destinationStore_);
186
187    /**
188     * Keeps track of event listeners for the print ticket store.
189     * @type {!EventTracker}
190     * @private
191     */
192    this.tracker_ = new EventTracker();
193
194    /**
195     * Whether the print preview has been initialized.
196     * @type {boolean}
197     * @private
198     */
199    this.isInitialized_ = false;
200
201    this.addEventListeners_();
202  };
203
204  /**
205   * Event types dispatched by the print ticket store.
206   * @enum {string}
207   */
208  PrintTicketStore.EventType = {
209    CAPABILITIES_CHANGE: 'print_preview.PrintTicketStore.CAPABILITIES_CHANGE',
210    DOCUMENT_CHANGE: 'print_preview.PrintTicketStore.DOCUMENT_CHANGE',
211    INITIALIZE: 'print_preview.PrintTicketStore.INITIALIZE',
212    TICKET_CHANGE: 'print_preview.PrintTicketStore.TICKET_CHANGE'
213  };
214
215  PrintTicketStore.prototype = {
216    __proto__: cr.EventTarget.prototype,
217
218    /**
219     * Whether the print preview has been initialized.
220     * @type {boolean}
221     */
222    get isInitialized() {
223      return this.isInitialized_;
224    },
225
226    get collate() {
227      return this.collate_;
228    },
229
230    get color() {
231      return this.color_;
232    },
233
234    get copies() {
235      return this.copies_;
236    },
237
238    get cssBackground() {
239      return this.cssBackground_;
240    },
241
242    get customMargins() {
243      return this.customMargins_;
244    },
245
246    get duplex() {
247      return this.duplex_;
248    },
249
250    get fitToPage() {
251      return this.fitToPage_;
252    },
253
254    get headerFooter() {
255      return this.headerFooter_;
256    },
257
258    get mediaSize() {
259      return this.mediaSize_;
260    },
261
262    get landscape() {
263      return this.landscape_;
264    },
265
266    get marginsType() {
267      return this.marginsType_;
268    },
269
270    get pageRange() {
271      return this.pageRange_;
272    },
273
274    get selectionOnly() {
275      return this.selectionOnly_;
276    },
277
278    get vendorItems() {
279      return this.vendorItems_;
280    },
281
282    /**
283     * @return {!print_preview.MeasurementSystem} Measurement system of the
284     *     local system.
285     */
286    get measurementSystem() {
287      return this.measurementSystem_;
288    },
289
290    /**
291     * Initializes the print ticket store. Dispatches an INITIALIZE event.
292     * @param {string} thousandsDelimeter Delimeter of the thousands place.
293     * @param {string} decimalDelimeter Delimeter of the decimal point.
294     * @param {!print_preview.MeasurementSystem.UnitType} unitType Type of unit
295     *     of the local measurement system.
296     * @param {boolean} selectionOnly Whether only selected content should be
297     *     printed.
298     */
299    init: function(
300        thousandsDelimeter, decimalDelimeter, unitType, selectionOnly) {
301      this.measurementSystem_.setSystem(thousandsDelimeter, decimalDelimeter,
302                                        unitType);
303      this.selectionOnly_.updateValue(selectionOnly);
304
305      // Initialize ticket with user's previous values.
306      if (this.appState_.hasField(
307          print_preview.AppState.Field.IS_COLOR_ENABLED)) {
308        this.color_.updateValue(this.appState_.getField(
309            print_preview.AppState.Field.IS_COLOR_ENABLED));
310      }
311      if (this.appState_.hasField(
312          print_preview.AppState.Field.IS_DUPLEX_ENABLED)) {
313        this.duplex_.updateValue(this.appState_.getField(
314            print_preview.AppState.Field.IS_DUPLEX_ENABLED));
315      }
316      if (this.appState_.hasField(print_preview.AppState.Field.MEDIA_SIZE)) {
317        this.mediaSize_.updateValue(this.appState_.getField(
318            print_preview.AppState.Field.MEDIA_SIZE));
319      }
320      if (this.appState_.hasField(
321          print_preview.AppState.Field.IS_LANDSCAPE_ENABLED)) {
322        this.landscape_.updateValue(this.appState_.getField(
323            print_preview.AppState.Field.IS_LANDSCAPE_ENABLED));
324      }
325      // Initialize margins after landscape because landscape may reset margins.
326      if (this.appState_.hasField(print_preview.AppState.Field.MARGINS_TYPE)) {
327        this.marginsType_.updateValue(
328            this.appState_.getField(print_preview.AppState.Field.MARGINS_TYPE));
329      }
330      if (this.appState_.hasField(
331          print_preview.AppState.Field.CUSTOM_MARGINS)) {
332        this.customMargins_.updateValue(this.appState_.getField(
333            print_preview.AppState.Field.CUSTOM_MARGINS));
334      }
335      if (this.appState_.hasField(
336          print_preview.AppState.Field.IS_HEADER_FOOTER_ENABLED)) {
337        this.headerFooter_.updateValue(this.appState_.getField(
338            print_preview.AppState.Field.IS_HEADER_FOOTER_ENABLED));
339      }
340      if (this.appState_.hasField(
341          print_preview.AppState.Field.IS_COLLATE_ENABLED)) {
342        this.collate_.updateValue(this.appState_.getField(
343            print_preview.AppState.Field.IS_COLLATE_ENABLED));
344      }
345      if (this.appState_.hasField(
346          print_preview.AppState.Field.IS_CSS_BACKGROUND_ENABLED)) {
347        this.cssBackground_.updateValue(this.appState_.getField(
348            print_preview.AppState.Field.IS_CSS_BACKGROUND_ENABLED));
349      }
350      if (this.appState_.hasField(
351          print_preview.AppState.Field.VENDOR_OPTIONS)) {
352        this.vendorItems_.updateValue(this.appState_.getField(
353            print_preview.AppState.Field.VENDOR_OPTIONS));
354    }
355    },
356
357    /**
358     * @return {boolean} {@code true} if the stored print ticket is valid,
359     *     {@code false} otherwise.
360     */
361    isTicketValid: function() {
362      return this.isTicketValidForPreview() &&
363          (!this.copies_.isCapabilityAvailable() || this.copies_.isValid()) &&
364          (!this.pageRange_.isCapabilityAvailable() ||
365              this.pageRange_.isValid());
366    },
367
368    /** @return {boolean} Whether the ticket is valid for preview generation. */
369    isTicketValidForPreview: function() {
370      return (!this.marginsType_.isCapabilityAvailable() ||
371              !this.marginsType_.isValueEqual(
372                  print_preview.ticket_items.MarginsType.Value.CUSTOM) ||
373              this.customMargins_.isValid());
374    },
375
376    /**
377     * Creates an object that represents a Google Cloud Print print ticket.
378     * @param {!print_preview.Destination} destination Destination to print to.
379     * @return {!Object} Google Cloud Print print ticket.
380     */
381    createPrintTicket: function(destination) {
382      assert(!destination.isLocal || destination.isPrivet,
383             'Trying to create a Google Cloud Print print ticket for a local ' +
384                 ' non-privet destination');
385
386      assert(destination.capabilities,
387             'Trying to create a Google Cloud Print print ticket for a ' +
388                 'destination with no print capabilities');
389      var cjt = {
390        version: '1.0',
391        print: {}
392      };
393      if (this.collate.isCapabilityAvailable() && this.collate.isUserEdited()) {
394        cjt.print.collate = {collate: this.collate.getValue()};
395      }
396      if (this.color.isCapabilityAvailable() && this.color.isUserEdited()) {
397        var selectedOption = this.color.getSelectedOption();
398        if (!selectedOption) {
399          console.error('Could not find correct color option');
400        } else {
401          cjt.print.color = {type: selectedOption.type};
402          if (selectedOption.hasOwnProperty('vendor_id')) {
403            cjt.print.color.vendor_id = selectedOption.vendor_id;
404          }
405        }
406      }
407      if (this.copies.isCapabilityAvailable() && this.copies.isUserEdited()) {
408        cjt.print.copies = {copies: this.copies.getValueAsNumber()};
409      }
410      if (this.duplex.isCapabilityAvailable() && this.duplex.isUserEdited()) {
411        cjt.print.duplex =
412            {type: this.duplex.getValue() ? 'LONG_EDGE' : 'NO_DUPLEX'};
413      }
414      if (this.mediaSize.isCapabilityAvailable()) {
415        var value = this.mediaSize.getValue();
416        cjt.print.media_size = {
417          width_microns: value.width_microns,
418          height_microns: value.height_microns,
419          is_continuous_feed: value.is_continuous_feed,
420          vendor_id: value.vendor_id
421        };
422      }
423      if (!this.landscape.isCapabilityAvailable()) {
424        // In this case "orientation" option is hidden from user, so user can't
425        // adjust it for page content, see Landscape.isCapabilityAvailable().
426        // We can improve results if we set AUTO here.
427        if (this.landscape.hasOption('AUTO'))
428          cjt.print.page_orientation = { type: 'AUTO' };
429      } else if (this.landscape.isUserEdited()) {
430        cjt.print.page_orientation =
431            {type: this.landscape.getValue() ? 'LANDSCAPE' : 'PORTRAIT'};
432      }
433      if (this.vendorItems.isCapabilityAvailable() &&
434          this.vendorItems.isUserEdited()) {
435        var items = this.vendorItems.ticketItems;
436        cjt.print.vendor_ticket_item = [];
437        for (var itemId in items) {
438          if (items.hasOwnProperty(itemId)) {
439            cjt.print.vendor_ticket_item.push(
440                {id: itemId, value: items[itemId]});
441          }
442        }
443      }
444      return JSON.stringify(cjt);
445    },
446
447    /**
448     * Adds event listeners for the print ticket store.
449     * @private
450     */
451    addEventListeners_: function() {
452      this.tracker_.add(
453          this.destinationStore_,
454          print_preview.DestinationStore.EventType.DESTINATION_SELECT,
455          this.onDestinationSelect_.bind(this));
456
457      this.tracker_.add(
458          this.destinationStore_,
459          print_preview.DestinationStore.EventType.
460              SELECTED_DESTINATION_CAPABILITIES_READY,
461          this.onSelectedDestinationCapabilitiesReady_.bind(this));
462
463      this.tracker_.add(
464          this.destinationStore_,
465          print_preview.DestinationStore.EventType.
466              CACHED_SELECTED_DESTINATION_INFO_READY,
467          this.onSelectedDestinationCapabilitiesReady_.bind(this));
468
469      // TODO(rltoscano): Print ticket store shouldn't be re-dispatching these
470      // events, the consumers of the print ticket store events should listen
471      // for the events from document info instead. Will move this when
472      // consumers are all migrated.
473      this.tracker_.add(
474          this.documentInfo_,
475          print_preview.DocumentInfo.EventType.CHANGE,
476          this.onDocumentInfoChange_.bind(this));
477    },
478
479    /**
480     * Called when the destination selected.
481     * @private
482     */
483    onDestinationSelect_: function() {
484      // Reset user selection for certain ticket items.
485      if (this.capabilitiesHolder_.get() != null) {
486        this.customMargins_.updateValue(null);
487        if (this.marginsType_.getValue() ==
488            print_preview.ticket_items.MarginsType.Value.CUSTOM) {
489          this.marginsType_.updateValue(
490              print_preview.ticket_items.MarginsType.Value.DEFAULT);
491        }
492        this.vendorItems_.updateValue({});
493      }
494    },
495
496    /**
497     * Called when the capabilities of the selected destination are ready.
498     * @private
499     */
500    onSelectedDestinationCapabilitiesReady_: function() {
501      var caps = this.destinationStore_.selectedDestination.capabilities;
502      var isFirstUpdate = this.capabilitiesHolder_.get() == null;
503      this.capabilitiesHolder_.set(caps);
504      if (isFirstUpdate) {
505        this.isInitialized_ = true;
506        cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.INITIALIZE);
507      } else {
508        cr.dispatchSimpleEvent(
509            this, PrintTicketStore.EventType.CAPABILITIES_CHANGE);
510      }
511    },
512
513    /**
514     * Called when document data model has changed. Dispatches a print ticket
515     * store event.
516     * @private
517     */
518    onDocumentInfoChange_: function() {
519      cr.dispatchSimpleEvent(this, PrintTicketStore.EventType.DOCUMENT_CHANGE);
520    },
521  };
522
523  // Export
524  return {
525    PrintTicketStore: PrintTicketStore
526  };
527});
528