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

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

Powered by Google App Engine
This is Rietveld 408576698