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() {
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.
21 * @extends {print_preview.Component}
23 function MarginControlContainer(documentInfo
, marginsTypeTicketItem
,
24 customMarginsTicketItem
, measurementSystem
,
25 dragChangedCallback
) {
26 print_preview
.Component
.call(this);
29 * Document data model.
30 * @type {!print_preview.DocumentInfo}
33 this.documentInfo_
= documentInfo
;
36 * Margins type ticket item used to read predefined margins type.
38 this.marginsTypeTicketItem_
= marginsTypeTicketItem
;
41 * Custom margins ticket item used to read/write custom margin values.
42 * @type {!print_preview.ticket_items.CustomMargins}
45 this.customMarginsTicketItem_
= customMarginsTicketItem
;
48 * Used to convert between the system's local units and points.
49 * @type {!print_preview.MeasurementSystem}
52 this.measurementSystem_
= measurementSystem
;
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
58 * @type {function(boolean)}
61 this.dragChangedCallback_
= dragChangedCallback
;
64 * Convenience array that contains all of the margin controls.
66 * !print_preview.ticket_items.CustomMargins.Orientation,
67 * !print_preview.MarginControl>}
71 for (var key
in print_preview
.ticket_items
.CustomMargins
.Orientation
) {
72 var orientation
= print_preview
.ticket_items
.CustomMargins
.Orientation
[
74 var control
= new print_preview
.MarginControl(orientation
);
75 this.controls_
[orientation
] = control
;
76 this.addChild(control
);
80 * Margin control currently being dragged. Null if no control is being
82 * @type {print_preview.MarginControl}
85 this.draggedControl_
= null;
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
91 * @type {!print_preview.Coordinate2d}
94 this.translateTransform_
= new print_preview
.Coordinate2d(0, 0);
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.
103 this.scaleTransform_
= 1;
106 * Clipping size for clipping the margin controls.
107 * @type {print_preview.Size}
110 this.clippingSize_
= null;
114 * CSS classes used by the custom margins component.
118 MarginControlContainer
.Classes_
= {
119 DRAGGING_HORIZONTAL
: 'margin-control-container-dragging-horizontal',
120 DRAGGING_VERTICAL
: 'margin-control-container-dragging-vertical'
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.
129 MarginControlContainer
.isTopOrBottom_ = function(orientation
) {
130 return orientation
==
131 print_preview
.ticket_items
.CustomMargins
.Orientation
.TOP
||
133 print_preview
.ticket_items
.CustomMargins
.Orientation
.BOTTOM
;
136 MarginControlContainer
.prototype = {
137 __proto__
: print_preview
.Component
.prototype,
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.
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
);
155 * Updates the scaling transform that scales pixels values to point values.
156 * @param {number} scaleTransform Updated value of the scale transform.
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
);
168 * Clips margin controls to the given clip size in pixels.
169 * @param {print_preview.Size} clipSize Size to clip the margin controls to.
171 updateClippingMask: function(clipSize
) {
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)';
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);
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
200 this.tracker
.add(window
, 'mouseup', this.onMouseUp_
.bind(this));
201 this.tracker
.add(window
, 'mousemove', this.onMouseMove_
.bind(this));
203 this.getElement(), 'mouseover', this.onMouseOver_
.bind(this));
205 this.getElement(), 'mouseout', this.onMouseOut_
.bind(this));
209 print_preview
.DocumentInfo
.EventType
.CHANGE
,
210 this.onTicketChange_
.bind(this));
212 this.marginsTypeTicketItem_
,
213 print_preview
.ticket_items
.TicketItem
.EventType
.CHANGE
,
214 this.onTicketChange_
.bind(this));
216 this.customMarginsTicketItem_
,
217 print_preview
.ticket_items
.TicketItem
.EventType
.CHANGE
,
218 this.onTicketChange_
.bind(this));
220 for (var orientation
in this.controls_
) {
222 this.controls_
[orientation
],
223 print_preview
.MarginControl
.EventType
.DRAG_START
,
224 this.onControlDragStart_
.bind(this, this.controls_
[orientation
]));
226 this.controls_
[orientation
],
227 print_preview
.MarginControl
.EventType
.TEXT_CHANGE
,
228 this.onControlTextChange_
.bind(this, this.controls_
[orientation
]));
233 decorateInternal: function() {
234 for (var orientation
in this.controls_
) {
235 this.controls_
[orientation
].render(this.getElement());
240 * @param {boolean} isVisible Whether the margin controls are visible.
243 setIsMarginControlsVisible_: function(isVisible
) {
244 for (var orientation
in this.controls_
) {
245 this.controls_
[orientation
].setIsVisible(isVisible
);
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
257 moveControlWithConstraints_: function(control
, posInPixels
) {
259 if (MarginControlContainer
.isTopOrBottom_(control
.getOrientation())) {
260 newPosInPts
= control
.convertPixelsToPts(posInPixels
.y
);
262 newPosInPts
= control
.convertPixelsToPts(posInPixels
.x
);
264 newPosInPts
= Math
.min(this.customMarginsTicketItem_
.getMarginMax(
265 control
.getOrientation()),
267 newPosInPts
= Math
.max(0, newPosInPts
);
268 newPosInPts
= Math
.round(newPosInPts
);
269 control
.setPositionInPts(newPosInPts
);
270 control
.setTextboxValue(this.serializeValueFromPts_(newPosInPts
));
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.
278 parseValueToPts_: function(value
) {
279 // Removing whitespace anywhere in the string.
280 value
= value
.replace(/\s*/g, '');
281 if (value
.length
== 0) {
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
));
300 * @param {number} value Value in points to serialize.
301 * @return {string} String representation of the value in the system's local
305 serializeValueFromPts_: function(value
) {
306 value
= this.measurementSystem_
.convertFromPoints(value
);
307 value
= this.measurementSystem_
.roundValue(value
);
308 return value
+ this.measurementSystem_
.unitSymbol
;
312 * Called when a margin control starts to drag.
313 * @param {print_preview.MarginControl} control The control which started to
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);
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.
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_
);
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.
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
);
356 this.draggedControl_
.translateMouseToPositionInPixels(
357 new print_preview
.Coordinate2d(event
.x
, event
.y
));
358 this.moveControlWithConstraints_(this.draggedControl_
, posInPixels
);
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);
370 * Called when the mouse moves onto the component. Shows the margin
374 onMouseOver_: function() {
375 var fromElement
= event
.fromElement
;
376 while (fromElement
!= null) {
377 if (fromElement
== this.getElement()) {
380 fromElement
= fromElement
.parentElement
;
382 if (this.marginsTypeTicketItem_
.isCapabilityAvailable() &&
383 this.marginsTypeTicketItem_
.getValue() ==
384 print_preview
.ticket_items
.MarginsType
.Value
.CUSTOM
) {
385 this.setIsMarginControlsVisible_(true);
390 * Called when the mouse moves off of the component. Hides the margin
394 onMouseOut_: function(event
) {
395 var toElement
= event
.toElement
;
396 while (toElement
!= null) {
397 if (toElement
== this.getElement()) {
400 toElement
= toElement
.parentElement
;
402 if (this.draggedControl_
!= null) {
405 for (var orientation
in this.controls_
) {
406 if (this.controls_
[orientation
].getIsFocused() ||
407 this.controls_
[orientation
].getIsInError()) {
411 this.setIsMarginControlsVisible_(false);
415 * Called when the print ticket changes. Updates the position of the margin
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);
430 this.updateClippingMask(this.clippingSize_
);
431 if (this.marginsTypeTicketItem_
.getValue() !=
432 print_preview
.ticket_items
.MarginsType
.Value
.CUSTOM
) {
433 this.setIsMarginControlsVisible_(false);
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.
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);
453 control
.setIsInError(false);
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;
463 control
.setIsInError(true);
464 enableOtherControls
= false;
466 // Enable other controls.
467 for (var o
in this.controls_
) {
468 if (control
.getOrientation() != o
) {
469 this.controls_
[o
].setIsEnabled(enableOtherControls
);
478 MarginControlContainer
: MarginControlContainer