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