Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / print_preview / previewarea / margin_control_container.js
blob5615cddb93c7d6182efc9a9e785fa6a1494d46c8
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.
5 cr.define('print_preview', function() {
6   'use strict';
8   /**
9    * UI component used for setting custom print margins.
10    * @param {!print_preview.DocumentInfo} documentInfo Document data model.
11    * @param {!print_preview.ticket_items.MarginsType} marginsTypeTicketItem
12    *     Used to read margins type.
13    * @param {!print_preview.ticket_items.CustomMargins} customMarginsTicketItem
14    *     Used to read and write custom margin values.
15    * @param {!print_preview.MeasurementSystem} measurementSystem Used to convert
16    *     between the system's local units and points.
17    * @param {function(boolean)} dragChangedCallback A function which is called
18    *     when dragging margins starts or stops. True is passed to the function
19    *     if the margin is currently being dragged and false otherwise.
20    * @constructor
21    * @extends {print_preview.Component}
22    */
23   function MarginControlContainer(documentInfo, marginsTypeTicketItem,
24                                   customMarginsTicketItem, measurementSystem,
25                                   dragChangedCallback) {
26     print_preview.Component.call(this);
28     /**
29      * Document data model.
30      * @type {!print_preview.DocumentInfo}
31      * @private
32      */
33     this.documentInfo_ = documentInfo;
35     /**
36      * Margins type ticket item used to read predefined margins type.
37      */
38     this.marginsTypeTicketItem_ = marginsTypeTicketItem;
40     /**
41      * Custom margins ticket item used to read/write custom margin values.
42      * @type {!print_preview.ticket_items.CustomMargins}
43      * @private
44      */
45     this.customMarginsTicketItem_ = customMarginsTicketItem;
47     /**
48      * Used to convert between the system's local units and points.
49      * @type {!print_preview.MeasurementSystem}
50      * @private
51      */
52     this.measurementSystem_ = measurementSystem;
54     /**
55      * Called in response to dragging the margins starting or stopping. True is
56      * passed to the function if the margin is currently being dragged and false
57      * otherwise.
58      * @type {function(boolean)}
59      * @private
60      */
61     this.dragChangedCallback_ = dragChangedCallback;
63     /**
64      * Convenience array that contains all of the margin controls.
65      * @type {!Object<
66      *     !print_preview.ticket_items.CustomMargins.Orientation,
67      *     !print_preview.MarginControl>}
68      * @private
69      */
70     this.controls_ = {};
71     for (var key in print_preview.ticket_items.CustomMargins.Orientation) {
72       var orientation = print_preview.ticket_items.CustomMargins.Orientation[
73           key];
74       var control = new print_preview.MarginControl(orientation);
75       this.controls_[orientation] = control;
76       this.addChild(control);
77     }
79     /**
80      * Margin control currently being dragged. Null if no control is being
81      * dragged.
82      * @type {print_preview.MarginControl}
83      * @private
84      */
85     this.draggedControl_ = null;
87     /**
88      * Translation transformation in pixels to translate from the origin of the
89      * custom margins component to the top-left corner of the most visible
90      * preview page.
91      * @type {!print_preview.Coordinate2d}
92      * @private
93      */
94     this.translateTransform_ = new print_preview.Coordinate2d(0, 0);
96     /**
97      * Scaling transformation to scale from pixels to the units which the
98      * print preview is in. The scaling factor is the same in both dimensions,
99      * so this field is just a single number.
100      * @type {number}
101      * @private
102      */
103     this.scaleTransform_ = 1;
105     /**
106      * Clipping size for clipping the margin controls.
107      * @type {print_preview.Size}
108      * @private
109      */
110     this.clippingSize_ = null;
111   };
113   /**
114    * CSS classes used by the custom margins component.
115    * @enum {string}
116    * @private
117    */
118   MarginControlContainer.Classes_ = {
119     DRAGGING_HORIZONTAL: 'margin-control-container-dragging-horizontal',
120     DRAGGING_VERTICAL: 'margin-control-container-dragging-vertical'
121   };
123   /**
124    * @param {!print_preview.ticket_items.CustomMargins.Orientation} orientation
125    *     Orientation value to test.
126    * @return {boolean} Whether the given orientation is TOP or BOTTOM.
127    * @private
128    */
129   MarginControlContainer.isTopOrBottom_ = function(orientation) {
130     return orientation ==
131         print_preview.ticket_items.CustomMargins.Orientation.TOP ||
132         orientation ==
133             print_preview.ticket_items.CustomMargins.Orientation.BOTTOM;
134   };
136   MarginControlContainer.prototype = {
137     __proto__: print_preview.Component.prototype,
139     /**
140      * Updates the translation transformation that translates pixel values in
141      * the space of the HTML DOM.
142      * @param {print_preview.Coordinate2d} translateTransform Updated value of
143      *     the translation transformation.
144      */
145     updateTranslationTransform: function(translateTransform) {
146       if (!translateTransform.equals(this.translateTransform_)) {
147         this.translateTransform_ = translateTransform;
148         for (var orientation in this.controls_) {
149           this.controls_[orientation].setTranslateTransform(translateTransform);
150         }
151       }
152     },
154     /**
155      * Updates the scaling transform that scales pixels values to point values.
156      * @param {number} scaleTransform Updated value of the scale transform.
157      */
158     updateScaleTransform: function(scaleTransform) {
159       if (scaleTransform != this.scaleTransform_) {
160         this.scaleTransform_ = scaleTransform;
161         for (var orientation in this.controls_) {
162           this.controls_[orientation].setScaleTransform(scaleTransform);
163         }
164       }
165     },
167     /**
168      * Clips margin controls to the given clip size in pixels.
169      * @param {print_preview.Size} clipSize Size to clip the margin controls to.
170      */
171     updateClippingMask: function(clipSize) {
172       if (!clipSize) {
173         return;
174       }
175       this.clippingSize_ = clipSize;
176       for (var orientation in this.controls_) {
177         var el = this.controls_[orientation].getElement();
178         el.style.clip = 'rect(' +
179             (-el.offsetTop) + 'px, ' +
180             (clipSize.width - el.offsetLeft) + 'px, ' +
181             (clipSize.height - el.offsetTop) + 'px, ' +
182             (-el.offsetLeft) + 'px)';
183       }
184     },
186     /** Shows the margin controls if the need to be shown. */
187     showMarginControlsIfNeeded: function() {
188       if (this.marginsTypeTicketItem_.getValue() ==
189           print_preview.ticket_items.MarginsType.Value.CUSTOM) {
190         this.setIsMarginControlsVisible_(true);
191       }
192     },
194     /** @override */
195     enterDocument: function() {
196       print_preview.Component.prototype.enterDocument.call(this);
198       // We want to respond to mouse up events even beyond the component's
199       // element.
200       this.tracker.add(window, 'mouseup', this.onMouseUp_.bind(this));
201       this.tracker.add(window, 'mousemove', this.onMouseMove_.bind(this));
202       this.tracker.add(
203           this.getElement(), 'mouseover', this.onMouseOver_.bind(this));
204       this.tracker.add(
205           this.getElement(), 'mouseout', this.onMouseOut_.bind(this));
207       this.tracker.add(
208           this.documentInfo_,
209           print_preview.DocumentInfo.EventType.CHANGE,
210           this.onTicketChange_.bind(this));
211       this.tracker.add(
212           this.marginsTypeTicketItem_,
213           print_preview.ticket_items.TicketItem.EventType.CHANGE,
214           this.onTicketChange_.bind(this));
215       this.tracker.add(
216           this.customMarginsTicketItem_,
217           print_preview.ticket_items.TicketItem.EventType.CHANGE,
218           this.onTicketChange_.bind(this));
220       for (var orientation in this.controls_) {
221         this.tracker.add(
222             this.controls_[orientation],
223             print_preview.MarginControl.EventType.DRAG_START,
224             this.onControlDragStart_.bind(this, this.controls_[orientation]));
225         this.tracker.add(
226             this.controls_[orientation],
227             print_preview.MarginControl.EventType.TEXT_CHANGE,
228             this.onControlTextChange_.bind(this, this.controls_[orientation]));
229       }
230     },
232     /** @override */
233     decorateInternal: function() {
234       for (var orientation in this.controls_) {
235         this.controls_[orientation].render(this.getElement());
236       }
237     },
239     /**
240      * @param {boolean} isVisible Whether the margin controls are visible.
241      * @private
242      */
243     setIsMarginControlsVisible_: function(isVisible) {
244       for (var orientation in this.controls_) {
245         this.controls_[orientation].setIsVisible(isVisible);
246       }
247     },
249     /**
250      * Moves the position of the given control to the desired position in
251      * pixels within some constraint minimum and maximum.
252      * @param {!print_preview.MarginControl} control Control to move.
253      * @param {!print_preview.Coordinate2d} posInPixels Desired position to move
254      *     to in pixels.
255      * @private
256      */
257     moveControlWithConstraints_: function(control, posInPixels) {
258       var newPosInPts;
259       if (MarginControlContainer.isTopOrBottom_(control.getOrientation())) {
260         newPosInPts = control.convertPixelsToPts(posInPixels.y);
261       } else {
262         newPosInPts = control.convertPixelsToPts(posInPixels.x);
263       }
264       newPosInPts = Math.min(this.customMarginsTicketItem_.getMarginMax(
265                                  control.getOrientation()),
266                              newPosInPts);
267       newPosInPts = Math.max(0, newPosInPts);
268       newPosInPts = Math.round(newPosInPts);
269       control.setPositionInPts(newPosInPts);
270       control.setTextboxValue(this.serializeValueFromPts_(newPosInPts));
271     },
273     /**
274      * @param {string} value Value to parse to points. E.g. '3.40"' or '200mm'.
275      * @return {number} Value in points represented by the input value.
276      * @private
277      */
278     parseValueToPts_: function(value) {
279       // Removing whitespace anywhere in the string.
280       value = value.replace(/\s*/g, '');
281       if (value.length == 0) {
282         return null;
283       }
284       var validationRegex = new RegExp('^(^-?)(\\d)+(\\' +
285           this.measurementSystem_.thousandsDelimeter + '\\d{3})*(\\' +
286           this.measurementSystem_.decimalDelimeter + '\\d*)?' +
287           '(' + this.measurementSystem_.unitSymbol + ')?$');
288       if (validationRegex.test(value)) {
289         // Replacing decimal point with the dot symbol in order to use
290         // parseFloat() properly.
291         var replacementRegex =
292             new RegExp('\\' + this.measurementSystem_.decimalDelimeter + '{1}');
293         value = value.replace(replacementRegex, '.');
294         return this.measurementSystem_.convertToPoints(parseFloat(value));
295       }
296       return null;
297     },
299     /**
300      * @param {number} value Value in points to serialize.
301      * @return {string} String representation of the value in the system's local
302      *     units.
303      * @private
304      */
305     serializeValueFromPts_: function(value) {
306       value = this.measurementSystem_.convertFromPoints(value);
307       value = this.measurementSystem_.roundValue(value);
308       return value + this.measurementSystem_.unitSymbol;
309     },
311     /**
312      * Called when a margin control starts to drag.
313      * @param {print_preview.MarginControl} control The control which started to
314      *     drag.
315      * @private
316      */
317     onControlDragStart_: function(control) {
318       this.draggedControl_ = control;
319       this.getElement().classList.add(
320           MarginControlContainer.isTopOrBottom_(control.getOrientation()) ?
321               MarginControlContainer.Classes_.DRAGGING_VERTICAL :
322               MarginControlContainer.Classes_.DRAGGING_HORIZONTAL);
323       this.dragChangedCallback_(true);
324     },
326     /**
327      * Called when the mouse moves in the custom margins component. Moves the
328      * dragged margin control.
329      * @param {MouseEvent} event Contains the position of the mouse.
330      * @private
331      */
332     onMouseMove_: function(event) {
333       if (this.draggedControl_) {
334         this.moveControlWithConstraints_(
335             this.draggedControl_,
336             this.draggedControl_.translateMouseToPositionInPixels(
337                 new print_preview.Coordinate2d(event.x, event.y)));
338         this.updateClippingMask(this.clippingSize_);
339       }
340     },
342     /**
343      * Called when the mouse is released in the custom margins component.
344      * Releases the dragged margin control.
345      * @param {MouseEvent} event Contains the position of the mouse.
346      * @private
347      */
348     onMouseUp_: function(event) {
349       if (this.draggedControl_) {
350         this.getElement().classList.remove(
351             MarginControlContainer.Classes_.DRAGGING_VERTICAL);
352         this.getElement().classList.remove(
353             MarginControlContainer.Classes_.DRAGGING_HORIZONTAL);
354         if (event) {
355           var posInPixels =
356               this.draggedControl_.translateMouseToPositionInPixels(
357                   new print_preview.Coordinate2d(event.x, event.y));
358           this.moveControlWithConstraints_(this.draggedControl_, posInPixels);
359         }
360         this.updateClippingMask(this.clippingSize_);
361         this.customMarginsTicketItem_.updateMargin(
362             this.draggedControl_.getOrientation(),
363             this.draggedControl_.getPositionInPts());
364         this.draggedControl_ = null;
365         this.dragChangedCallback_(false);
366       }
367     },
369     /**
370      * Called when the mouse moves onto the component. Shows the margin
371      * controls.
372      * @private
373      */
374     onMouseOver_: function() {
375       var fromElement = event.fromElement;
376       while (fromElement != null) {
377         if (fromElement == this.getElement()) {
378           return;
379         }
380         fromElement = fromElement.parentElement;
381       }
382       if (this.marginsTypeTicketItem_.isCapabilityAvailable() &&
383           this.marginsTypeTicketItem_.getValue() ==
384               print_preview.ticket_items.MarginsType.Value.CUSTOM) {
385         this.setIsMarginControlsVisible_(true);
386       }
387     },
389     /**
390      * Called when the mouse moves off of the component. Hides the margin
391      * controls.
392      * @private
393      */
394     onMouseOut_: function(event) {
395       var toElement = event.toElement;
396       while (toElement != null) {
397         if (toElement == this.getElement()) {
398           return;
399         }
400         toElement = toElement.parentElement;
401       }
402       if (this.draggedControl_ != null) {
403         return;
404       }
405       for (var orientation in this.controls_) {
406         if (this.controls_[orientation].getIsFocused() ||
407             this.controls_[orientation].getIsInError()) {
408           return;
409         }
410       }
411       this.setIsMarginControlsVisible_(false);
412     },
414     /**
415      * Called when the print ticket changes. Updates the position of the margin
416      * controls.
417      * @private
418      */
419     onTicketChange_: function() {
420       var margins = this.customMarginsTicketItem_.getValue();
421       for (var orientation in this.controls_) {
422         var control = this.controls_[orientation];
423         control.setPageSize(this.documentInfo_.pageSize);
424         control.setTextboxValue(
425             this.serializeValueFromPts_(margins.get(orientation)));
426         control.setPositionInPts(margins.get(orientation));
427         control.setIsInError(false);
428         control.setIsEnabled(true);
429       }
430       this.updateClippingMask(this.clippingSize_);
431       if (this.marginsTypeTicketItem_.getValue() !=
432           print_preview.ticket_items.MarginsType.Value.CUSTOM) {
433         this.setIsMarginControlsVisible_(false);
434       }
435     },
437     /**
438      * Called when the text in a textbox of a margin control changes or the
439      * textbox loses focus.
440      * Updates the print ticket store.
441      * @param {!print_preview.MarginControl} control Updated control.
442      * @private
443      */
444     onControlTextChange_: function(control) {
445       var marginValue = this.parseValueToPts_(control.getTextboxValue());
446       if (marginValue != null) {
447         this.customMarginsTicketItem_.updateMargin(
448             control.getOrientation(), marginValue);
449         // Enable all controls.
450         for (var o in this.controls_) {
451           this.controls_[o].setIsEnabled(true);
452         }
453         control.setIsInError(false);
454       } else {
455         var enableOtherControls;
456         if (!control.getIsFocused()) {
457           // If control no longer in focus, revert to previous valid value.
458           control.setTextboxValue(
459               this.serializeValueFromPts_(control.getPositionInPts()));
460           control.setIsInError(false);
461           enableOtherControls = true;
462         } else {
463           control.setIsInError(true);
464           enableOtherControls = false;
465         }
466         // Enable other controls.
467         for (var o in this.controls_) {
468           if (control.getOrientation() != o) {
469             this.controls_[o].setIsEnabled(enableOtherControls);
470           }
471         }
472       }
473     }
474   };
476   // Export
477   return {
478     MarginControlContainer: MarginControlContainer
479   };