Allow only one bookmark to be added for multiple fast starring
[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}
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
33 this.documentInfo_ = documentInfo;
35 /**
36 * Margins type ticket item used to read predefined margins type.
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
45 this.customMarginsTicketItem_ = customMarginsTicketItem;
47 /**
48 * Used to convert between the system's local units and points.
49 * @type {!print_preview.MeasurementSystem}
50 * @private
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
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
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);
79 /**
80 * Margin control currently being dragged. Null if no control is being
81 * dragged.
82 * @type {print_preview.MarginControl}
83 * @private
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
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
103 this.scaleTransform_ = 1;
106 * Clipping size for clipping the margin controls.
107 * @type {print_preview.Size}
108 * @private
110 this.clippingSize_ = null;
114 * CSS classes used by the custom margins component.
115 * @enum {string}
116 * @private
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.
127 * @private
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;
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) {
172 if (!clipSize) {
173 return;
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);
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]));
232 /** @override */
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.
241 * @private
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
254 * to in pixels.
255 * @private
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);
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));
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
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;
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));
296 return null;
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
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
314 * drag.
315 * @private
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.
330 * @private
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.
346 * @private
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);
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
371 * controls.
372 * @private
374 onMouseOver_: function() {
375 var fromElement = event.fromElement;
376 while (fromElement != null) {
377 if (fromElement == this.getElement()) {
378 return;
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
391 * controls.
392 * @private
394 onMouseOut_: function(event) {
395 var toElement = event.toElement;
396 while (toElement != null) {
397 if (toElement == this.getElement()) {
398 return;
400 toElement = toElement.parentElement;
402 if (this.draggedControl_ != null) {
403 return;
405 for (var orientation in this.controls_) {
406 if (this.controls_[orientation].getIsFocused() ||
407 this.controls_[orientation].getIsInError()) {
408 return;
411 this.setIsMarginControlsVisible_(false);
415 * Called when the print ticket changes. Updates the position of the margin
416 * controls.
417 * @private
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.
442 * @private
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);
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;
466 // Enable other controls.
467 for (var o in this.controls_) {
468 if (control.getOrientation() != o) {
469 this.controls_[o].setIsEnabled(enableOtherControls);
476 // Export
477 return {
478 MarginControlContainer: MarginControlContainer