Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(280)

Side by Side Diff: chrome/browser/resources/print_preview/previewarea/preview_area.js

Issue 10108001: Refactor print preview web ui (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Resolve conflicts Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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 cr.define('print_preview', function() {
6 'use strict';
7
8 /**
9 * Creates a PreviewArea object. It represents the area where the preview
10 * document is displayed.
11 * @param {!print_preview.DestinationStore} destinationStore Used to get the
12 * currently selected destination.
13 * @param {!print_preview.PrintTicketStore} printTicketStore Used to get
14 * information about how the preview should be displayed.
15 * @param {!print_preview.NativeLayer} nativeLayer Needed to communicate with
16 * Chromium's preview generation system.
17 * @constructor
18 * @extends {print_preview.Component}
19 */
20 function PreviewArea(destinationStore, printTicketStore, nativeLayer) {
21 print_preview.Component.call(this);
22
23 /**
24 * Used to get the currently selected destination.
25 * @type {!print_preview.DestinationStore}
26 * @private
27 */
28 this.destinationStore_ = destinationStore;
29
30 /**
31 * Used to get information about how the preview should be displayed.
32 * @type {!print_preview.PrintTicketStore}
33 * @private
34 */
35 this.printTicketStore_ = printTicketStore;
36
37 /**
38 * Used to contruct the preview generator.
39 * @type {!print_preview.NativeLayer}
40 * @private
41 */
42 this.nativeLayer_ = nativeLayer;
43
44 /**
45 * Used to read generated page previews.
46 * @type {print_preview.PreviewGenerator}
47 * @private
48 */
49 this.previewGenerator_ = null;
50
51 /**
52 * The embedded pdf plugin object. It's value is null if not yet loaded.
53 * @type {HTMLEmbedElement}
54 * @private
55 */
56 this.plugin_ = null;
57
58 /**
59 * Custom margins component superimposed on the preview plugin.
60 * @type {!print_preview.MarginControlContainer}
61 * @private
62 */
63 this.marginControlContainer_ =
64 new print_preview.MarginControlContainer(this.printTicketStore_);
65 this.addChild(this.marginControlContainer_);
66
67 /**
68 * Current zoom level as a percentage.
69 * @type {?number}
70 * @private
71 */
72 this.zoomLevel_ = null;
73
74 /**
75 * Current page offset which can be used to calculate scroll amount.
76 * @type {print_preview.Coordinate2d}
77 * @private
78 */
79 this.pageOffset_ = null;
80
81 /**
82 * Whether the plugin has finished reloading.
83 * @type {boolean}
84 * @private
85 */
86 this.isPluginReloaded_ = false;
87
88 /**
89 * Whether the document preview is ready.
90 * @type {boolean}
91 * @private
92 */
93 this.isDocumentReady_ = false;
94
95 /**
96 * Timeout object used to display a loading message if the preview is taking
97 * a long time to generate.
98 * @type {Object}
99 * @private
100 */
101 this.loadingTimeout_ = null;
102
103 /**
104 * Overlay element.
105 * @type {HTMLElement}
106 * @private
107 */
108 this.overlayEl_ = null;
109
110 /**
111 * The "Open system dialog" button.
112 * @type {HTMLButtonElement}
113 * @private
114 */
115 this.openSystemDialogButton_ = null;
116 };
117
118 /**
119 * Event types dispatched by the preview area.
120 * @enum {string}
121 */
122 PreviewArea.EventType = {
123 // Dispatched when the "Open system dialog" button is clicked.
124 OPEN_SYSTEM_DIALOG_CLICK:
125 'print_preview.PreviewArea.OPEN_SYSTEM_DIALOG_CLICK',
126
127 // Dispatched when the document preview is complete.
128 PREVIEW_GENERATION_DONE:
129 'print_preview.PreviewArea.PREVIEW_GENERATION_DONE',
130
131 // Dispatched when the document preview failed to be generated.
132 PREVIEW_GENERATION_FAIL:
133 'print_preview.PreviewArea.PREVIEW_GENERATION_FAIL',
134
135 // Dispatched when a new document preview is being generated.
136 PREVIEW_GENERATION_IN_PROGRESS:
137 'print_preview.PreviewArea.PREVIEW_GENERATION_IN_PROGRESS'
138 };
139
140 /**
141 * CSS classes used by the preview area.
142 * @enum {string}
143 * @private
144 */
145 PreviewArea.Classes_ = {
146 COMPATIBILITY_OBJECT: 'preview-area-compatibility-object',
147 CUSTOM_MESSAGE_TEXT: 'preview-area-custom-message-text',
148 MESSAGE: 'preview-area-message',
149 INVISIBLE: 'invisible',
150 OPEN_SYSTEM_DIALOG_BUTTON: 'preview-area-open-system-dialog-button',
151 OPEN_SYSTEM_DIALOG_BUTTON_THROBBER:
152 'preview-area-open-system-dialog-button-throbber',
153 OVERLAY: 'preview-area-overlay-layer',
154 PDF_PLUGIN: 'preview-area-pdf-plugin'
155 };
156
157 /**
158 * Enumeration of IDs shown in the preview area.
159 * @enum {string}
160 * @private
161 */
162 PreviewArea.MessageId_ = {
163 CUSTOM: 'custom',
164 LOADING: 'loading',
165 PREVIEW_FAILED: 'preview-failed'
166 };
167
168 /**
169 * Maps message IDs to the CSS class that contains them.
170 * @type {object.<PreviewArea.MessageId_, string>}
171 * @private
172 */
173 PreviewArea.MessageIdClassMap_ = {};
174 PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.CUSTOM] =
175 'preview-area-custom-message';
176 PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.LOADING] =
177 'preview-area-loading-message';
178 PreviewArea.MessageIdClassMap_[PreviewArea.MessageId_.PREVIEW_FAILED] =
179 'preview-area-preview-failed-message';
180
181 /**
182 * Amount of time in milliseconds to wait after issueing a new preview before
183 * the loading message is shown.
184 * @type {number}
185 * @const
186 * @private
187 */
188 PreviewArea.LOADING_TIMEOUT_ = 200;
189
190 PreviewArea.prototype = {
191 __proto__: print_preview.Component.prototype,
192
193 /**
194 * Should only be called after calling this.render().
195 * @return {boolean} Whether the preview area has a compatible plugin to
196 * display the print preview in.
197 */
198 get hasCompatiblePlugin() {
199 return this.previewGenerator_ != null;
200 },
201
202 /**
203 * Processes a keyboard event that could possibly be used to change state of
204 * the preview plugin.
205 * @param {MouseEvent} e Mouse event to process.
206 */
207 handleDirectionalKeyEvent: function(e) {
208 // Make sure the PDF plugin is there.
209 // We only care about: PageUp, PageDown, Left, Up, Right, Down.
210 // If the user is holding a modifier key, ignore.
211 if (!this.plugin_ ||
212 !arrayContains([33, 34, 37, 38, 39, 40], e.keyCode) ||
213 e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) {
214 return;
215 }
216
217 // Don't handle the key event for these elements.
218 var tagName = document.activeElement.tagName;
219 if (arrayContains(['INPUT', 'SELECT', 'EMBED'], tagName)) {
220 return;
221 }
222
223 // For the most part, if any div of header was the last clicked element,
224 // then the active element is the body. Starting with the last clicked
225 // element, and work up the DOM tree to see if any element has a
226 // scrollbar. If there exists a scrollbar, do not handle the key event
227 // here.
228 var element = e.target;
229 while (element) {
230 if (element.scrollHeight > element.clientHeight ||
231 element.scrollWidth > element.clientWidth) {
232 return;
233 }
234 element = element.parentElement;
235 }
236
237 // No scroll bar anywhere, or the active element is something else, like a
238 // button. Note: buttons have a bigger scrollHeight than clientHeight.
239 this.plugin_.sendKeyEvent(e.keyCode);
240 e.preventDefault();
241 },
242
243 /**
244 * Shows a custom message on the preview area's overlay.
245 * @param {string} message Custom message to show.
246 */
247 showCustomMessage: function(message) {
248 this.showMessage_(PreviewArea.MessageId_.CUSTOM, message);
249 },
250
251 /** @override */
252 enterDocument: function() {
253 print_preview.Component.prototype.enterDocument.call(this);
254 this.tracker.add(
255 this.openSystemDialogButton_,
256 'click',
257 this.onOpenSystemDialogButtonClick_.bind(this));
258
259 this.tracker.add(
260 this.printTicketStore_,
261 print_preview.PrintTicketStore.EventType.INITIALIZE,
262 this.onTicketChange_.bind(this));
263 this.tracker.add(
264 this.printTicketStore_,
265 print_preview.PrintTicketStore.EventType.TICKET_CHANGE,
266 this.onTicketChange_.bind(this));
267 this.tracker.add(
268 this.printTicketStore_,
269 print_preview.PrintTicketStore.EventType.CAPABILITIES_CHANGE,
270 this.onTicketChange_.bind(this));
271 this.tracker.add(
272 this.printTicketStore_,
273 print_preview.PrintTicketStore.EventType.DOCUMENT_CHANGE,
274 this.onTicketChange_.bind(this));
275
276 if (this.checkPluginCompatibility_()) {
277 this.previewGenerator_ = new print_preview.PreviewGenerator(
278 this.destinationStore_, this.printTicketStore_, this.nativeLayer_);
279 this.tracker.add(
280 this.previewGenerator_,
281 print_preview.PreviewGenerator.EventType.PREVIEW_START,
282 this.onPreviewStart_.bind(this));
283 this.tracker.add(
284 this.previewGenerator_,
285 print_preview.PreviewGenerator.EventType.PAGE_READY,
286 this.onPagePreviewReady_.bind(this));
287 this.tracker.add(
288 this.previewGenerator_,
289 print_preview.PreviewGenerator.EventType.FAIL,
290 this.onPreviewGenerationFail_.bind(this));
291 this.tracker.add(
292 this.previewGenerator_,
293 print_preview.PreviewGenerator.EventType.DOCUMENT_READY,
294 this.onDocumentReady_.bind(this));
295 } else {
296 this.showCustomMessage(localStrings.getString('noPlugin'));
297 }
298 },
299
300 /** @override */
301 exitDocument: function() {
302 print_preview.Component.prototype.exitDocument.call(this);
303 if (this.previewGenerator_) {
304 this.previewGenerator_.removeEventListeners();
305 }
306 this.overlayEl_ = null;
307 this.openSystemDialogButton_ = null;
308 },
309
310 /** @override */
311 decorateInternal: function() {
312 this.marginControlContainer_.decorate(this.getElement());
313 this.overlayEl_ = this.getElement().getElementsByClassName(
314 PreviewArea.Classes_.OVERLAY)[0];
315 this.openSystemDialogButton_ = this.getElement().getElementsByClassName(
316 PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON)[0];
317 },
318
319 /**
320 * Checks to see if a suitable plugin for rendering the preview exists. If
321 * one does not exist, then an error message will be displayed.
322 * @return {boolean} Whether Chromium has a suitable plugin for rendering
323 * the preview.
324 * @private
325 */
326 checkPluginCompatibility_: function() {
327 var compatObj = this.getElement().getElementsByClassName(
328 PreviewArea.Classes_.COMPATIBILITY_OBJECT)[0];
329 var isCompatible =
330 compatObj.onload &&
331 compatObj.goToPage &&
332 compatObj.removePrintButton &&
333 compatObj.loadPreviewPage &&
334 compatObj.printPreviewPageCount &&
335 compatObj.resetPrintPreviewUrl &&
336 compatObj.onPluginSizeChanged &&
337 compatObj.onScroll &&
338 compatObj.pageXOffset &&
339 compatObj.pageYOffset &&
340 compatObj.setZoomLevel &&
341 compatObj.setPageNumbers &&
342 compatObj.setPageXOffset &&
343 compatObj.setPageYOffset &&
344 compatObj.getHorizontalScrollbarThickness &&
345 compatObj.getVerticalScrollbarThickness &&
346 compatObj.getPageLocationNormalized &&
347 compatObj.getHeight &&
348 compatObj.getWidth;
349 compatObj.parentElement.removeChild(compatObj);
350 return isCompatible;
351 },
352
353 /**
354 * Shows a given message on the overlay.
355 * @param {print_preview.PreviewArea.MessageId_} messageId ID of the message
356 * to show.
357 * @param {string=} opt_message Optional message to show that can be used
358 * by some message IDs.
359 * @private
360 */
361 showMessage_: function(messageId, opt_message) {
362 // Hide all messages.
363 var messageEls = this.getElement().getElementsByClassName(
364 PreviewArea.Classes_.MESSAGE);
365 for (var i = 0, messageEl; messageEl = messageEls[i]; i++) {
366 setIsVisible(messageEl, false);
367 }
368 // Disable jumping animation to conserve cycles.
369 var jumpingDotsEl = this.getElement().querySelector(
370 '.preview-area-loading-message-jumping-dots');
371 jumpingDotsEl.classList.remove('jumping-dots');
372
373 // Show specific message.
374 if (messageId == PreviewArea.MessageId_.CUSTOM) {
375 var customMessageTextEl = this.getElement().getElementsByClassName(
376 PreviewArea.Classes_.CUSTOM_MESSAGE_TEXT)[0];
377 customMessageTextEl.textContent = opt_message;
378 } else if (messageId == PreviewArea.MessageId_.LOADING) {
379 jumpingDotsEl.classList.add('jumping-dots');
380 }
381 var messageEl = this.getElement().getElementsByClassName(
382 PreviewArea.MessageIdClassMap_[messageId])[0];
383 setIsVisible(messageEl, true);
384
385 // Show overlay.
386 this.overlayEl_.classList.remove(PreviewArea.Classes_.INVISIBLE);
387 },
388
389 /**
390 * Hides the message overlay.
391 * @private
392 */
393 hideOverlay_: function() {
394 this.overlayEl_.classList.add(PreviewArea.Classes_.INVISIBLE);
395 // Disable jumping animation to conserve cycles.
396 var jumpingDotsEl = this.getElement().querySelector(
397 '.preview-area-loading-message-jumping-dots');
398 jumpingDotsEl.classList.remove('jumping-dots');
399 },
400
401 /**
402 * Creates a preview plugin and adds it to the DOM.
403 * @param {string} srcUrl Initial URL of the plugin.
404 * @private
405 */
406 createPlugin_: function(srcUrl) {
407 if (this.plugin_) {
408 console.warn('Pdf preview plugin already created');
409 return;
410 }
411 this.plugin_ = document.createElement('embed');
412 // NOTE: The plugin's 'id' field must be set to 'pdf-viewer' since
413 // chrome/renderer/print_web_view_helper.cc actually references it.
414 this.plugin_.setAttribute('id', 'pdf-viewer');
415 this.plugin_.setAttribute('class', 'preview-area-plugin');
416 this.plugin_.setAttribute(
417 'type', 'application/x-google-chrome-print-preview-pdf');
418 this.plugin_.setAttribute('src', srcUrl);
419 this.plugin_.setAttribute('aria-live', 'polite');
420 this.plugin_.setAttribute('aria-atomic', 'true');
421 this.getElement().appendChild(this.plugin_);
422
423 global['onPreviewPluginLoad'] = this.onPluginLoad_.bind(this);
424 this.plugin_.onload('onPreviewPluginLoad()');
425
426 global['onPreviewPluginVisualStateChange'] =
427 this.onPreviewVisualStateChange_.bind(this);
428 this.plugin_.onScroll('onPreviewPluginVisualStateChange()');
429 this.plugin_.onPluginSizeChanged('onPreviewPluginVisualStateChange()');
430
431 this.plugin_.removePrintButton();
432 this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled());
433 },
434
435 /**
436 * Dispatches a PREVIEW_GENERATION_DONE event if all conditions are met.
437 * @private
438 */
439 dispatchPreviewGenerationDoneIfReady_: function() {
440 if (this.isDocumentReady_ && this.isPluginReloaded_) {
441 cr.dispatchSimpleEvent(
442 this, PreviewArea.EventType.PREVIEW_GENERATION_DONE);
443 this.marginControlContainer_.showMarginControlsIfNeeded();
444 }
445 },
446
447 /**
448 * Called when the open-system-dialog button is clicked. Disables the
449 * button, shows the throbber, and dispatches the OPEN_SYSTEM_DIALOG_CLICK
450 * event.
451 * @private
452 */
453 onOpenSystemDialogButtonClick_: function() {
454 this.openSystemDialogButton_.disabled = true;
455 var openSystemDialogThrobber = this.getElement().getElementsByClassName(
456 PreviewArea.Classes_.OPEN_SYSTEM_DIALOG_BUTTON_THROBBER)[0];
457 setIsVisible(openSystemDialogThrobber, true);
458 cr.dispatchSimpleEvent(
459 this, PreviewArea.EventType.OPEN_SYSTEM_DIALOG_CLICK);
460 },
461
462 /**
463 * Called when the print ticket changes. Updates the preview.
464 * @private
465 */
466 onTicketChange_: function() {
467 if (this.previewGenerator_ && this.previewGenerator_.requestPreview()) {
468 if (this.loadingTimeout_ == null) {
469 this.loadingTimeout_ = setTimeout(
470 this.showMessage_.bind(this, PreviewArea.MessageId_.LOADING),
471 PreviewArea.LOADING_TIMEOUT_);
472 }
473 } else {
474 this.marginControlContainer_.showMarginControlsIfNeeded();
475 }
476 },
477
478 /**
479 * Called when the preview generator begins loading the preview.
480 * @param {cr.Event} Contains the URL to initialize the plugin to.
481 * @private
482 */
483 onPreviewStart_: function(event) {
484 this.isDocumentReady_ = false;
485 this.isPluginReloaded_ = false;
486 if (!this.plugin_) {
487 this.createPlugin_(event.previewUrl);
488 }
489 this.plugin_.goToPage('0');
490 this.plugin_.resetPrintPreviewUrl(event.previewUrl);
491 this.plugin_.reload();
492 this.plugin_.grayscale(!this.printTicketStore_.isColorEnabled());
493 cr.dispatchSimpleEvent(
494 this, PreviewArea.EventType.PREVIEW_GENERATION_IN_PROGRESS);
495 },
496
497 /**
498 * Called when a page preview has been generated. Updates the plugin with
499 * the new page.
500 * @param {cr.Event} event Contains information about the page preview.
501 * @private
502 */
503 onPagePreviewReady_: function(event) {
504 this.plugin_.loadPreviewPage(event.previewUrl, event.previewIndex);
505 },
506
507 /**
508 * Called when the preview generation is complete and the document is ready
509 * to print.
510 * @private
511 */
512 onDocumentReady_: function(event) {
513 this.isDocumentReady_ = true;
514 this.dispatchPreviewGenerationDoneIfReady_();
515 },
516
517 /**
518 * Called when the generation of a preview fails. Shows an error message.
519 * @private
520 */
521 onPreviewGenerationFail_: function() {
522 this.showMessage_(PreviewArea.MessageId_.PREVIEW_FAILED);
523 cr.dispatchSimpleEvent(
524 this, PreviewArea.EventType.PREVIEW_GENERATION_FAIL);
525 },
526
527 /**
528 * Called when the plugin loads. This is a consequence of calling
529 * plugin.reload(). Certain plugin state can only be set after the plugin
530 * has loaded.
531 * @private
532 */
533 onPluginLoad_: function() {
534 if (this.loadingTimeout_) {
535 clearTimeout(this.loadingTimeout_);
536 this.loadingTimeout_ = null;
537 }
538 // Setting the plugin's page count can only be called after the plugin is
539 // loaded and the document must be modifiable.
540 if (this.printTicketStore_.isDocumentModifiable) {
541 this.plugin_.printPreviewPageCount(
542 this.printTicketStore_.getPageNumberSet().size);
543 }
544 this.plugin_.setPageNumbers(JSON.stringify(
545 this.printTicketStore_.getPageNumberSet().asArray()));
546 if (this.zoomLevel_ != null && this.pageOffset_ != null) {
547 this.plugin_.setZoomLevel(this.zoomLevel_);
548 this.plugin_.setPageXOffset(this.pageOffset_.x);
549 this.plugin_.setPageYOffset(this.pageOffset_.y);
550 } else {
551 this.plugin_.fitToHeight();
552 }
553 this.hideOverlay_();
554 this.isPluginReloaded_ = true;
555 this.dispatchPreviewGenerationDoneIfReady_();
556 },
557
558 /**
559 * Called when the preview plugin's visual state has changed. This is a
560 * consequence of scrolling or zooming the plugin. Updates the custom
561 * margins component if shown.
562 * @private
563 */
564 onPreviewVisualStateChange_: function() {
565 if (this.isPluginReloaded_) {
566 this.zoomLevel_ = this.plugin_.getZoomLevel();
567 this.pageOffset_ = new print_preview.Coordinate2d(
568 this.plugin_.pageXOffset(), this.plugin_.pageYOffset());
569 }
570 var normalized = this.plugin_.getPageLocationNormalized().split(';');
571 var pluginWidth = this.plugin_.getWidth();
572 var pluginHeight = this.plugin_.getHeight();
573 var translationTransform = new print_preview.Coordinate2d(
574 parseFloat(normalized[0]) * pluginWidth,
575 parseFloat(normalized[1]) * pluginHeight);
576 this.marginControlContainer_.updateTranslationTransform(
577 translationTransform);
578 var pageWidthInPixels = parseFloat(normalized[2]) * pluginWidth;
579 this.marginControlContainer_.updateScaleTransform(
580 pageWidthInPixels / this.printTicketStore_.pageSize.width);
581 this.marginControlContainer_.updateClippingMask(
582 new print_preview.Size(
583 pluginWidth - this.plugin_.getVerticalScrollbarThickness(),
584 pluginHeight - this.plugin_.getHorizontalScrollbarThickness()));
585 }
586 };
587
588 // Export
589 return {
590 PreviewArea: PreviewArea
591 };
592 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698