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