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

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

Powered by Google App Engine
This is Rietveld 408576698