margin_control.js revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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.
4
5cr.define('print_preview', function() {
6  'use strict';
7
8  /**
9   * Draggable control for setting a page margin.
10   * @param {!print_preview.ticket_items.CustomMargins.Orientation} orientation
11   *     Orientation of the margin control that determines where the margin
12   *     textbox will be placed.
13   * @constructor
14   * @extends {print_preview.Component}
15   */
16  function MarginControl(orientation) {
17    print_preview.Component.call(this);
18
19    /**
20     * Determines where the margin textbox will be placed.
21     * @type {!print_preview.ticket_items.CustomMargins.Orientation}
22     * @private
23     */
24    this.orientation_ = orientation;
25
26    /**
27     * Position of the margin control in points.
28     * @type {number}
29     * @private
30     */
31    this.positionInPts_ = 0;
32
33    /**
34     * Page size of the document to print.
35     * @type {!print_preview.Size}
36     * @private
37     */
38    this.pageSize_ = new print_preview.Size(0, 0);
39
40    /**
41     * Amount to scale pixel values by to convert to pixel space.
42     * @type {number}
43     * @private
44     */
45    this.scaleTransform_ = 1;
46
47    /**
48     * Amount to translate values in pixel space.
49     * @type {!print_preview.Coordinate2d}
50     * @private
51     */
52    this.translateTransform_ = new print_preview.Coordinate2d(0, 0);
53
54    /**
55     * Position of the margin control when dragging starts.
56     * @type {print_preview.Coordinate2d}
57     * @private
58     */
59    this.marginStartPositionInPixels_ = null;
60
61    /**
62     * Position of the mouse when the dragging starts.
63     * @type {print_preview.Coordinate2d}
64     * @private
65     */
66    this.mouseStartPositionInPixels_ = null;
67
68    /**
69     * Processing timeout for the textbox.
70     * @type {?number}
71     * @private
72     */
73    this.textTimeout_ = null;
74
75    /**
76     * Value of the textbox when the timeout was started.
77     * @type {?string}
78     * @private
79     */
80    this.preTimeoutValue_ = null;
81
82    /**
83     * Textbox used to display and receive the value of the margin.
84     * @type {HTMLInputElement}
85     * @private
86     */
87    this.textbox_ = null;
88
89    /**
90     * Element of the margin control line.
91     * @type {HTMLElement}
92     * @private
93     */
94    this.marginLineEl_ = null;
95
96    /**
97     * Whether this margin control's textbox has keyboard focus.
98     * @type {boolean}
99     * @private
100     */
101    this.isFocused_ = false;
102
103    /**
104     * Whether the margin control is in an error state.
105     * @type {boolean}
106     * @private
107     */
108    this.isInError_ = false;
109  };
110
111  /**
112   * Event types dispatched by the margin control.
113   * @enum {string}
114   */
115  MarginControl.EventType = {
116    // Dispatched when the margin control starts dragging.
117    DRAG_START: 'print_preview.MarginControl.DRAG_START',
118
119    // Dispatched when the text in the margin control's textbox changes.
120    TEXT_CHANGE: 'print_preview.MarginControl.TEXT_CHANGE'
121  };
122
123  /**
124   * CSS classes used by this component.
125   * @enum {string}
126   * @private
127   */
128  MarginControl.Classes_ = {
129    TOP: 'margin-control-top',
130    RIGHT: 'margin-control-right',
131    BOTTOM: 'margin-control-bottom',
132    LEFT: 'margin-control-left',
133    TEXTBOX: 'margin-control-textbox',
134    INVALID: 'invalid',
135    INVISIBLE: 'invisible',
136    DISABLED: 'margin-control-disabled',
137    DRAGGING: 'margin-control-dragging',
138    LINE: 'margin-control-line'
139  };
140
141  /**
142   * Map from orientation to CSS class name.
143   * @type {!Object.<
144   *     !print_preview.ticket_items.CustomMargins.Orientation,
145   *     !MarginControl.Classes_>}
146   * @private
147   */
148  MarginControl.OrientationToClass_ = {};
149  MarginControl.OrientationToClass_[
150      print_preview.ticket_items.CustomMargins.Orientation.TOP] =
151      MarginControl.Classes_.TOP;
152  MarginControl.OrientationToClass_[
153      print_preview.ticket_items.CustomMargins.Orientation.RIGHT] =
154      MarginControl.Classes_.RIGHT;
155  MarginControl.OrientationToClass_[
156      print_preview.ticket_items.CustomMargins.Orientation.BOTTOM] =
157      MarginControl.Classes_.BOTTOM;
158  MarginControl.OrientationToClass_[
159      print_preview.ticket_items.CustomMargins.Orientation.LEFT] =
160      MarginControl.Classes_.LEFT;
161
162  /**
163   * Radius of the margin control in pixels. Padding of control + 1 for border.
164   * @type {number}
165   * @const
166   * @private
167   */
168  MarginControl.RADIUS_ = 9;
169
170  /**
171   * Timeout after a text change after which the text in the textbox is saved to
172   * the print ticket. Value in milliseconds.
173   * @type {number}
174   * @const
175   * @private
176   */
177  MarginControl.TEXTBOX_TIMEOUT_ = 1000;
178
179  MarginControl.prototype = {
180    __proto__: print_preview.Component.prototype,
181
182    /** @return {boolean} Whether this margin control is in focus. */
183    getIsFocused: function() {
184      return this.isFocused_;
185    },
186
187    /**
188     * @return {!print_preview.ticket_items.CustomMargins.Orientation}
189     *     Orientation of the margin control.
190     */
191    getOrientation: function() {
192      return this.orientation_;
193    },
194
195    /**
196     * @param {number} scaleTransform New scale transform of the margin control.
197     */
198    setScaleTransform: function(scaleTransform) {
199      this.scaleTransform_ = scaleTransform;
200      // Reset position
201      this.setPositionInPts(this.positionInPts_);
202    },
203
204    /**
205     * @param {!print_preview.Coordinate2d} translateTransform New translate
206     *     transform of the margin control.
207     */
208    setTranslateTransform: function(translateTransform) {
209      this.translateTransform_ = translateTransform;
210      // Reset position
211      this.setPositionInPts(this.positionInPts_);
212    },
213
214    /**
215     * @param {!print_preview.Size} pageSize New size of the document's pages.
216     */
217    setPageSize: function(pageSize) {
218      this.pageSize_ = pageSize;
219      this.setPositionInPts(this.positionInPts_);
220    },
221
222    /** @param {boolean} isVisible Whether the margin control is visible. */
223    setIsVisible: function(isVisible) {
224      if (isVisible) {
225        this.getElement().classList.remove(MarginControl.Classes_.INVISIBLE);
226      } else {
227        this.getElement().classList.add(MarginControl.Classes_.INVISIBLE);
228      }
229    },
230
231    /** @return {boolean} Whether the margin control is in an error state. */
232    getIsInError: function() {
233      return this.isInError_;
234    },
235
236    /**
237     * @param {boolean} isInError Whether the margin control is in an error
238     *     state.
239     */
240    setIsInError: function(isInError) {
241      this.isInError_ = isInError;
242      if (isInError) {
243        this.textbox_.classList.add(MarginControl.Classes_.INVALID);
244      } else {
245        this.textbox_.classList.remove(MarginControl.Classes_.INVALID);
246      }
247    },
248
249    /** @param {boolean} isEnabled Whether to enable the margin control. */
250    setIsEnabled: function(isEnabled) {
251      this.textbox_.disabled = !isEnabled;
252      if (isEnabled) {
253        this.getElement().classList.remove(MarginControl.Classes_.DISABLED);
254      } else {
255        this.getElement().classList.add(MarginControl.Classes_.DISABLED);
256      }
257    },
258
259    /** @return {number} Current position of the margin control in points. */
260    getPositionInPts: function() {
261      return this.positionInPts_;
262    },
263
264    /**
265     * @param {number} posInPts New position of the margin control in points.
266     */
267    setPositionInPts: function(posInPts) {
268      this.positionInPts_ = posInPts;
269      var orientationEnum =
270          print_preview.ticket_items.CustomMargins.Orientation;
271      var x = this.translateTransform_.x;
272      var y = this.translateTransform_.y;
273      var width = null, height = null;
274      if (this.orientation_ == orientationEnum.TOP) {
275        y = this.scaleTransform_ * posInPts + this.translateTransform_.y -
276            MarginControl.RADIUS_;
277        width = this.scaleTransform_ * this.pageSize_.width;
278      } else if (this.orientation_ == orientationEnum.RIGHT) {
279        x = this.scaleTransform_ * (this.pageSize_.width - posInPts) +
280            this.translateTransform_.x - MarginControl.RADIUS_;
281        height = this.scaleTransform_ * this.pageSize_.height;
282      } else if (this.orientation_ == orientationEnum.BOTTOM) {
283        y = this.scaleTransform_ * (this.pageSize_.height - posInPts) +
284            this.translateTransform_.y - MarginControl.RADIUS_;
285        width = this.scaleTransform_ * this.pageSize_.width;
286      } else {
287        x = this.scaleTransform_ * posInPts + this.translateTransform_.x -
288            MarginControl.RADIUS_;
289        height = this.scaleTransform_ * this.pageSize_.height;
290      }
291      this.getElement().style.left = Math.round(x) + 'px';
292      this.getElement().style.top = Math.round(y) + 'px';
293      if (width != null) {
294        this.getElement().style.width = Math.round(width) + 'px';
295      }
296      if (height != null) {
297        this.getElement().style.height = Math.round(height) + 'px';
298      }
299    },
300
301    /** @return {string} The value in the margin control's textbox. */
302    getTextboxValue: function() {
303      return this.textbox_.value;
304    },
305
306    /** @param {string} value New value of the margin control's textbox. */
307    setTextboxValue: function(value) {
308      if (this.textbox_.value != value) {
309        this.textbox_.value = value;
310      }
311    },
312
313    /**
314     * Converts a value in pixels to points.
315     * @param {number} Pixel value to convert.
316     * @return {number} Given value expressed in points.
317     */
318    convertPixelsToPts: function(pixels) {
319      var pts;
320      var orientationEnum =
321          print_preview.ticket_items.CustomMargins.Orientation;
322      if (this.orientation_ == orientationEnum.TOP) {
323        pts = pixels - this.translateTransform_.y + MarginControl.RADIUS_;
324        pts /= this.scaleTransform_;
325      } else if (this.orientation_ == orientationEnum.RIGHT) {
326        pts = pixels - this.translateTransform_.x + MarginControl.RADIUS_;
327        pts /= this.scaleTransform_;
328        pts = this.pageSize_.width - pts;
329      } else if (this.orientation_ == orientationEnum.BOTTOM) {
330        pts = pixels - this.translateTransform_.y + MarginControl.RADIUS_;
331        pts /= this.scaleTransform_;
332        pts = this.pageSize_.height - pts;
333      } else {
334        pts = pixels - this.translateTransform_.x + MarginControl.RADIUS_;
335        pts /= this.scaleTransform_;
336      }
337      return pts;
338    },
339
340    /**
341     * Translates the position of the margin control relative to the mouse
342     * position in pixels.
343     * @param {!print_preview.Coordinate2d} mousePosition New position of
344     *     the mouse.
345     * @return {!print_preview.Coordinate2d} New position of the margin control.
346     */
347    translateMouseToPositionInPixels: function(mousePosition) {
348      return new print_preview.Coordinate2d(
349          mousePosition.x - this.mouseStartPositionInPixels_.x +
350              this.marginStartPositionInPixels_.x,
351          mousePosition.y - this.mouseStartPositionInPixels_.y +
352              this.marginStartPositionInPixels_.y);
353    },
354
355    /** @override */
356    createDom: function() {
357      this.setElementInternal(this.cloneTemplateInternal(
358          'margin-control-template'));
359      this.getElement().classList.add(MarginControl.OrientationToClass_[
360          this.orientation_]);
361      this.textbox_ = this.getElement().getElementsByClassName(
362          MarginControl.Classes_.TEXTBOX)[0];
363      this.marginLineEl_ = this.getElement().getElementsByClassName(
364          MarginControl.Classes_.LINE)[0];
365    },
366
367    /** @override */
368    enterDocument: function() {
369      print_preview.Component.prototype.enterDocument.call(this);
370      this.tracker.add(
371          this.getElement(), 'mousedown', this.onMouseDown_.bind(this));
372      this.tracker.add(
373          this.textbox_, 'keydown', this.onTextboxKeyDown_.bind(this));
374      this.tracker.add(
375          this.textbox_, 'focus', this.setIsFocused_.bind(this, true));
376      this.tracker.add(this.textbox_, 'blur', this.onTexboxBlur_.bind(this));
377    },
378
379    /** @override */
380    exitDocument: function() {
381      print_preview.Component.prototype.exitDocument.call(this);
382      this.textbox_ = null;
383      this.marginLineEl_ = null;
384    },
385
386    /**
387     * @param {boolean} isFocused Whether the margin control is in focus.
388     * @private
389     */
390    setIsFocused_: function(isFocused) {
391      this.isFocused_ = isFocused;
392    },
393
394    /**
395     * Called whenever a mousedown event occurs on the component.
396     * @param {MouseEvent} event The event that occured.
397     * @private
398     */
399    onMouseDown_: function(event) {
400      if (!this.textbox_.disabled &&
401          event.button == 0 &&
402          (event.target == this.getElement() ||
403              event.target == this.marginLineEl_)) {
404        this.mouseStartPositionInPixels_ =
405            new print_preview.Coordinate2d(event.x, event.y);
406        this.marginStartPositionInPixels_ = new print_preview.Coordinate2d(
407            this.getElement().offsetLeft, this.getElement().offsetTop);
408        this.setIsInError(false);
409        cr.dispatchSimpleEvent(this, MarginControl.EventType.DRAG_START);
410      }
411    },
412
413    /**
414     * Called when a key down event occurs on the textbox. Dispatches a
415     * TEXT_CHANGE event if the "Enter" key was pressed.
416     * @param {Event} event Contains the key that was pressed.
417     * @private
418     */
419    onTextboxKeyDown_: function(event) {
420      if (this.textTimeout_) {
421        clearTimeout(this.textTimeout_);
422        this.textTimeout_ = null;
423      }
424      if (event.keyCode == 13 /*enter*/) {
425        this.preTimeoutValue_ = null;
426        cr.dispatchSimpleEvent(this, MarginControl.EventType.TEXT_CHANGE);
427      } else {
428        if (this.preTimeoutValue_ == null) {
429          this.preTimeoutValue_ = this.textbox_.value;
430        }
431        this.textTimeout_ = setTimeout(
432            this.onTextboxTimeout_.bind(this), MarginControl.TEXTBOX_TIMEOUT_);
433      }
434    },
435
436    /**
437     * Called after a timeout after the text in the textbox has changed. Saves
438     * the textbox's value to the print ticket.
439     * @private
440     */
441    onTextboxTimeout_: function() {
442      this.textTimeout_ = null;
443      if (this.textbox_.value != this.preTimeoutValue_) {
444        cr.dispatchSimpleEvent(this, MarginControl.EventType.TEXT_CHANGE);
445      }
446      this.preTimeoutValue_ = null;
447    },
448
449    /**
450     * Called when the textbox loses focus. Dispatches a TEXT_CHANGE event.
451     */
452    onTexboxBlur_: function() {
453      if (this.textTimeout_) {
454        clearTimeout(this.textTimeout_);
455        this.textTimeout_ = null;
456        this.preTimeoutValue_ = null;
457      }
458      this.setIsFocused_(false);
459      cr.dispatchSimpleEvent(this, MarginControl.EventType.TEXT_CHANGE);
460    }
461  };
462
463  // Export
464  return {
465    MarginControl: MarginControl
466  };
467});
468