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