// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('print_preview', function() { 'use strict'; /** * UI component used for setting custom print margins. * @param {!print_preview.DocumentInfo} documentInfo Document data model. * @param {!print_preview.ticket_items.MarginsType} marginsTypeTicketItem * Used to read margins type. * @param {!print_preview.ticket_items.CustomMargins} customMarginsTicketItem * Used to read and write custom margin values. * @param {!print_preview.MeasurementSystem} measurementSystem Used to convert * between the system's local units and points. * @constructor * @extends {print_preview.Component} */ function MarginControlContainer(documentInfo, marginsTypeTicketItem, customMarginsTicketItem, measurementSystem) { print_preview.Component.call(this); /** * Document data model. * @type {!print_preview.DocumentInfo} * @private */ this.documentInfo_ = documentInfo; /** * Margins type ticket item used to read predefined margins type. */ this.marginsTypeTicketItem_ = marginsTypeTicketItem; /** * Custom margins ticket item used to read/write custom margin values. * @type {!print_preview.ticket_items.CustomMargins} * @private */ this.customMarginsTicketItem_ = customMarginsTicketItem; /** * Used to convert between the system's local units and points. * @type {!print_preview.MeasurementSystem} * @private */ this.measurementSystem_ = measurementSystem; /** * Convenience array that contains all of the margin controls. * @type {!Object.< * !print_preview.ticket_items.CustomMargins.Orientation, * !print_preview.MarginControl>} * @private */ this.controls_ = {}; for (var key in print_preview.ticket_items.CustomMargins.Orientation) { var orientation = print_preview.ticket_items.CustomMargins.Orientation[ key]; var control = new print_preview.MarginControl(orientation); this.controls_[orientation] = control; this.addChild(control); } /** * Margin control currently being dragged. Null if no control is being * dragged. * @type {print_preview.MarginControl} * @private */ this.draggedControl_ = null; /** * Translation transformation in pixels to translate from the origin of the * custom margins component to the top-left corner of the most visible * preview page. * @type {!print_preview.Coordinate2d} * @private */ this.translateTransform_ = new print_preview.Coordinate2d(0, 0); /** * Scaling transformation to scale from pixels to the units which the * print preview is in. The scaling factor is the same in both dimensions, * so this field is just a single number. * @type {number} * @private */ this.scaleTransform_ = 1; /** * Clipping size for clipping the margin controls. * @type {print_preview.Size} * @private */ this.clippingSize_ = null; }; /** * CSS classes used by the custom margins component. * @enum {string} * @private */ MarginControlContainer.Classes_ = { DRAGGING_HORIZONTAL: 'margin-control-container-dragging-horizontal', DRAGGING_VERTICAL: 'margin-control-container-dragging-vertical' }; /** * @param {!print_preview.ticket_items.CustomMargins.Orientation} orientation * Orientation value to test. * @return {boolean} Whether the given orientation is TOP or BOTTOM. * @private */ MarginControlContainer.isTopOrBottom_ = function(orientation) { return orientation == print_preview.ticket_items.CustomMargins.Orientation.TOP || orientation == print_preview.ticket_items.CustomMargins.Orientation.BOTTOM; }; MarginControlContainer.prototype = { __proto__: print_preview.Component.prototype, /** * Updates the translation transformation that translates pixel values in * the space of the HTML DOM. * @param {print_preview.Coordinate2d} translateTransform Updated value of * the translation transformation. */ updateTranslationTransform: function(translateTransform) { if (!translateTransform.equals(this.translateTransform_)) { this.translateTransform_ = translateTransform; for (var orientation in this.controls_) { this.controls_[orientation].setTranslateTransform(translateTransform); } } }, /** * Updates the scaling transform that scales pixels values to point values. * @param {number} scaleTransform Updated value of the scale transform. */ updateScaleTransform: function(scaleTransform) { if (scaleTransform != this.scaleTransform_) { this.scaleTransform_ = scaleTransform; for (var orientation in this.controls_) { this.controls_[orientation].setScaleTransform(scaleTransform); } } }, /** * Clips margin controls to the given clip size in pixels. * @param {print_preview.Size} Size to clip the margin controls to. */ updateClippingMask: function(clipSize) { if (!clipSize) { return; } this.clippingSize_ = clipSize; for (var orientation in this.controls_) { var el = this.controls_[orientation].getElement(); el.style.clip = 'rect(' + (-el.offsetTop) + 'px, ' + (clipSize.width - el.offsetLeft) + 'px, ' + (clipSize.height - el.offsetTop) + 'px, ' + (-el.offsetLeft) + 'px)'; } }, /** Shows the margin controls if the need to be shown. */ showMarginControlsIfNeeded: function() { if (this.marginsTypeTicketItem_.getValue() == print_preview.ticket_items.MarginsType.Value.CUSTOM) { this.setIsMarginControlsVisible_(true); } }, /** @override */ enterDocument: function() { print_preview.Component.prototype.enterDocument.call(this); // We want to respond to mouse up events even beyond the component's // element. this.tracker.add(window, 'mouseup', this.onMouseUp_.bind(this)); this.tracker.add(window, 'mousemove', this.onMouseMove_.bind(this)); this.tracker.add( this.getElement(), 'mouseover', this.onMouseOver_.bind(this)); this.tracker.add( this.getElement(), 'mouseout', this.onMouseOut_.bind(this)); this.tracker.add( this.documentInfo_, print_preview.DocumentInfo.EventType.CHANGE, this.onTicketChange_.bind(this)); this.tracker.add( this.marginsTypeTicketItem_, print_preview.ticket_items.TicketItem.EventType.CHANGE, this.onTicketChange_.bind(this)); this.tracker.add( this.customMarginsTicketItem_, print_preview.ticket_items.TicketItem.EventType.CHANGE, this.onTicketChange_.bind(this)); for (var orientation in this.controls_) { this.tracker.add( this.controls_[orientation], print_preview.MarginControl.EventType.DRAG_START, this.onControlDragStart_.bind(this, this.controls_[orientation])); this.tracker.add( this.controls_[orientation], print_preview.MarginControl.EventType.TEXT_CHANGE, this.onControlTextChange_.bind(this, this.controls_[orientation])); } }, /** @override */ decorateInternal: function() { for (var orientation in this.controls_) { this.controls_[orientation].render(this.getElement()); } }, /** * @param {boolean} isVisible Whether the margin controls are visible. * @private */ setIsMarginControlsVisible_: function(isVisible) { for (var orientation in this.controls_) { this.controls_[orientation].setIsVisible(isVisible); } }, /** * Moves the position of the given control to the desired position in * pixels within some constraint minimum and maximum. * @param {!print_preview.MarginControl} control Control to move. * @param {!print_preview.Coordinate2d} posInPixels Desired position to move * to in pixels. * @private */ moveControlWithConstraints_: function(control, posInPixels) { var newPosInPts; if (MarginControlContainer.isTopOrBottom_(control.getOrientation())) { newPosInPts = control.convertPixelsToPts(posInPixels.y); } else { newPosInPts = control.convertPixelsToPts(posInPixels.x); } newPosInPts = Math.min(this.customMarginsTicketItem_.getMarginMax( control.getOrientation()), newPosInPts); newPosInPts = Math.max(0, newPosInPts); newPosInPts = Math.round(newPosInPts); control.setPositionInPts(newPosInPts); control.setTextboxValue(this.serializeValueFromPts_(newPosInPts)); }, /** * @param {string} value Value to parse to points. E.g. '3.40"' or '200mm'. * @return {number} Value in points represented by the input value. * @private */ parseValueToPts_: function(value) { // Removing whitespace anywhere in the string. value = value.replace(/\s*/g, ''); if (value.length == 0) { return null; } var validationRegex = new RegExp('^(^-?)(\\d)+(\\' + this.measurementSystem_.thousandsDelimeter + '\\d{3})*(\\' + this.measurementSystem_.decimalDelimeter + '\\d*)?' + '(' + this.measurementSystem_.unitSymbol + ')?$'); if (validationRegex.test(value)) { // Replacing decimal point with the dot symbol in order to use // parseFloat() properly. var replacementRegex = new RegExp('\\' + this.measurementSystem_.decimalDelimeter + '{1}'); value = value.replace(replacementRegex, '.'); return this.measurementSystem_.convertToPoints(parseFloat(value)); } return null; }, /** * @param {number} value Value in points to serialize. * @return {string} String representation of the value in the system's local * units. * @private */ serializeValueFromPts_: function(value) { value = this.measurementSystem_.convertFromPoints(value); value = this.measurementSystem_.roundValue(value); return value + this.measurementSystem_.unitSymbol; }, /** * Called when a margin control starts to drag. * @param {print_preview.MarginControl} control The control which started to * drag. * @private */ onControlDragStart_: function(control) { this.draggedControl_ = control; this.getElement().classList.add( MarginControlContainer.isTopOrBottom_(control.getOrientation()) ? MarginControlContainer.Classes_.DRAGGING_VERTICAL : MarginControlContainer.Classes_.DRAGGING_HORIZONTAL); }, /** * Called when the mouse moves in the custom margins component. Moves the * dragged margin control. * @param {MouseEvent} event Contains the position of the mouse. * @private */ onMouseMove_: function(event) { if (this.draggedControl_) { this.moveControlWithConstraints_( this.draggedControl_, this.draggedControl_.translateMouseToPositionInPixels( new print_preview.Coordinate2d(event.x, event.y))); this.updateClippingMask(this.clippingSize_); } }, /** * Called when the mouse is released in the custom margins component. * Releases the dragged margin control. * @param {MouseEvent} event Contains the position of the mouse. * @private */ onMouseUp_: function(event) { if (this.draggedControl_) { this.getElement().classList.remove( MarginControlContainer.Classes_.DRAGGING_VERTICAL); this.getElement().classList.remove( MarginControlContainer.Classes_.DRAGGING_HORIZONTAL); if (event) { var posInPixels = this.draggedControl_.translateMouseToPositionInPixels( new print_preview.Coordinate2d(event.x, event.y)); this.moveControlWithConstraints_(this.draggedControl_, posInPixels); } this.updateClippingMask(this.clippingSize_); this.customMarginsTicketItem_.updateMargin( this.draggedControl_.getOrientation(), this.draggedControl_.getPositionInPts()); this.draggedControl_ = null; } }, /** * Called when the mouse moves onto the component. Shows the margin * controls. * @private */ onMouseOver_: function() { var fromElement = event.fromElement; while (fromElement != null) { if (fromElement == this.getElement()) { return; } fromElement = fromElement.parentElement; } if (this.marginsTypeTicketItem_.isCapabilityAvailable() && this.marginsTypeTicketItem_.getValue() == print_preview.ticket_items.MarginsType.Value.CUSTOM) { this.setIsMarginControlsVisible_(true); } }, /** * Called when the mouse moves off of the component. Hides the margin * controls. * @private */ onMouseOut_: function(event) { var toElement = event.toElement; while (toElement != null) { if (toElement == this.getElement()) { return; } toElement = toElement.parentElement; } if (this.draggedControl_ != null) { return; } for (var orientation in this.controls_) { if (this.controls_[orientation].getIsFocused() || this.controls_[orientation].getIsInError()) { return; } } this.setIsMarginControlsVisible_(false); }, /** * Called when the print ticket changes. Updates the position of the margin * controls. * @private */ onTicketChange_: function() { var margins = this.customMarginsTicketItem_.getValue(); for (var orientation in this.controls_) { var control = this.controls_[orientation]; control.setPageSize(this.documentInfo_.pageSize); control.setTextboxValue( this.serializeValueFromPts_(margins.get(orientation))); control.setPositionInPts(margins.get(orientation)); control.setIsInError(false); control.setIsEnabled(true); } this.updateClippingMask(this.clippingSize_); if (this.marginsTypeTicketItem_.getValue() != print_preview.ticket_items.MarginsType.Value.CUSTOM) { this.setIsMarginControlsVisible_(false); } }, /** * Called when the text in a textbox of a margin control changes or the * textbox loses focus. * Updates the print ticket store. * @param {!print_preview.MarginControl} control Updated control. * @private */ onControlTextChange_: function(control) { var marginValue = this.parseValueToPts_(control.getTextboxValue()); if (marginValue != null) { this.customMarginsTicketItem_.updateMargin( control.getOrientation(), marginValue); } else { var enableOtherControls; if (!control.getIsFocused()) { // If control no longer in focus, revert to previous valid value. control.setTextboxValue( this.serializeValueFromPts_(control.getPositionInPts())); control.setIsInError(false); enableOtherControls = true; } else { control.setIsInError(true); enableOtherControls = false; } // Enable other controls. for (var o in this.controls_) { if (control.getOrientation() != o) { this.controls_[o].setIsEnabled(enableOtherControls); } } } } }; // Export return { MarginControlContainer: MarginControlContainer }; });