1 /*
  2  *  Copyright © 2009 Apple Inc. All rights reserved.
  3  */
  4 
  5 /**
  6  *  The keyboard identifier for the backspace key.
  7  *  @constant
  8  *  @type int
  9  */
 10 const KEYBOARD_BACKSPACE = 8;
 11 /**
 12  *  The keyboard identifier for the left key.
 13  *  @constant
 14  *  @type int
 15  */
 16 const KEYBOARD_LEFT = 37;
 17 /**
 18  *  The keyboard identifier for the right key.
 19  *  @constant
 20  *  @type int
 21  */
 22 const KEYBOARD_RIGHT = 39;
 23 /**
 24  *  The keyboard identifier for the up key.
 25  *  @constant
 26  *  @type int
 27  */
 28 const KEYBOARD_UP = 38;
 29 /**
 30  *  The keyboard identifier for the down key.
 31  *  @constant
 32  *  @type int
 33  */
 34 const KEYBOARD_DOWN = 40;
 35 /**
 36  *  The keyboard identifier for the return key.
 37  *  @constant
 38  *  @type int
 39  */
 40 const KEYBOARD_RETURN = 13;
 41 
 42 /**
 43  *  Indicates whether TuneKit is running on Apple TV.
 44  *  @constant
 45  *  @type bool
 46  */
 47 const IS_APPLE_TV = (window.iTunes !== undefined && window.iTunes.platform == 'AppleTV');
 48 /**
 49  *  The Apple TV-specific iTunes system sounds interface.
 50  *  @constant
 51  *  @type bool
 52  *  @private
 53  */
 54 const ATV_SOUNDS = IS_APPLE_TV ? window.iTunes.getSystemSounds() : null;
 55 
 56 /**
 57  *  The move sound of the Apple TV user interface.
 58  *  @constant
 59  *  @type Object
 60  */
 61 const SOUND_MOVED = IS_APPLE_TV ? ATV_SOUNDS.SystemSoundScrollStart : new Audio('../TuneKit/sounds/SelectionChange.aif');
 62 /**
 63  *  The selection sound of the Apple TV user interface.
 64  *  @constant
 65  *  @type Object
 66  */
 67 const SOUND_ACTIVATED = IS_APPLE_TV ? ATV_SOUNDS.SystemSoundSelect : new Audio('../TuneKit/sounds/Selection.aif');
 68 /**
 69  *  The limit sound of the Apple TV user interface.
 70  *  @constant
 71  *  @type Object
 72  */
 73 const SOUND_LIMIT = IS_APPLE_TV ? ATV_SOUNDS.SystemSoundScrollLimit : new Audio('../TuneKit/sounds/Limit.aif');
 74 /**
 75  *  The exit sound of the Apple TV user interface.
 76  *  @constant
 77  *  @type Object
 78  */
 79 const SOUND_EXIT = IS_APPLE_TV ? ATV_SOUNDS.SystemSoundExit : new Audio('../TuneKit/sounds/Exit.aif');
 80 
 81 /**
 82  *  @class
 83  *  @name TKUtils
 84  *
 85  *  @since TuneKit 1.0
 86  */
 87 function TKUtils () {
 88 };
 89 
 90 /* ==================== SOUNDS ==================== */
 91 
 92 /**
 93  *  Plays a sound.
 94  *
 95  *  @param {Object} sound The sound to play, which is either an <code>audio</code> element or an iTunes system sound identifier on Apple TV.
 96  */
 97 TKUtils.playSound = function (sound) {
 98   if (IS_APPLE_TV) {
 99     ATV_SOUNDS.playSystemSound(sound);
100   }
101   else {
102     sound.play();
103   }
104 };
105 
106 /* ==================== TRANSFORMS SHORTHANDS ==================== */
107 
108 /**
109  *  Prints a <code>translate3d()</code> command that can be used as input for a <code>-webkit-transform</code> property.
110  *  
111  *  @param {int} tx The x coordinate for the translation.
112  *  @param {int} ty The y coordinate for the translation
113  *
114  *  @returns {String} The <code>translate3d()</code> command
115  */
116 TKUtils.t = function (tx, ty) {
117   return 'translate3d(' + tx + 'px, ' + ty + 'px, 0)';
118 };
119 
120 /**
121  *  Creates a CSS string representation for a number in pixels.
122  *  
123  *  @param {number} value The value to be converted.
124  *
125  *  @returns {String} A CSS string representation for <code>value</code> in pixels.
126  */
127 TKUtils.px = function (value) {
128   return value + 'px';
129 };
130 
131 /* ==================== Array ==================== */
132 
133 /**
134  *  Copies all properties from one object onto another.
135  *  
136  *  @param {Object} sourceObject The object from which we will copy properties.
137  *  @param {Object} targetObject The array onto which we will copy properties.
138  */
139 TKUtils.copyPropertiesFromSourceToTarget = function (source, target) {
140   for (var property in source) {
141     target[property] = source[property];
142   }
143 };
144 
145 /* ==================== Delegates ==================== */
146 
147 /**
148  *  Indicates whether an object is a <code>Function</code>.
149  *  
150  *  @param {Object} object The object purported to be a <code>Function</code>.
151  *
152  *  @returns {bool} Whether the object is a <code>Function</code>.
153  */
154 TKUtils.objectIsFunction = function (object) {
155   return (typeof object == 'function');
156 };
157 
158 /**
159  *  Indicates whether an object is <code>undefined</code>.
160  *  
161  *  @param {Object} object The object purported to be <code>undefined</code>.
162  *
163  *  @returns {bool} Whether the object is <code>undefined</code>.
164  */
165 TKUtils.objectIsUndefined = function (object) {
166   return (object === undefined);
167 };
168 
169 /**
170  *  Indicates whether an object is a string literal or a <code>String</code> instance.
171  *  
172  *  @param {Object} object The object purported to be a string literal or a <code>String</code> instance.
173  *
174  *  @returns {bool} Whether the object is a string literal or a <code>String</code> instance.
175  */
176 TKUtils.objectIsString = function (object) {
177   return (typeof object == 'string' || object instanceof String);
178 };
179 
180 /**
181  *  Indicates whether an object is an <code>Array</code>.
182  *  
183  *  @param {Object} object The object purported to be an <code>Array</code>.
184  *
185  *  @returns {bool} Whether the object is an <code>Array</code>.
186  */
187 TKUtils.objectIsArray = function (object) {
188   return (object instanceof Array);
189 };
190 
191 /**
192  *  Indicates whether an object implements a given method, useful to check if a delegate
193  *  object implements a given delegate method.
194  *  
195  *  @param {Object} object The object purported to implement a given method.
196  *  @param {String} methodNameAsString The method name as a <code>String</code>.
197  *
198  *  @returns {bool} Whether the object implements the given method.
199  */
200 TKUtils.objectHasMethod = function (object, methodNameAsString) {
201   return (  object !== null &&
202             !this.objectIsUndefined(object) &&
203             !this.objectIsUndefined(object[methodNameAsString]) &&
204             this.objectIsFunction(object[methodNameAsString])
205          );
206 };
207 
208 /* ==================== INIT ==================== */
209 
210 /**
211  *  Sets up the .displayNames for all functions defined on the specified class, including its prototype.
212  *  
213  *  @param {Object} class The class.
214  *  @param {String} className The class name as a string, in case it can not be derived from <code>class</code>. Optional.
215  */
216 TKUtils.setupDisplayNames = function (object, className) {
217   var class_name = className || object.name;
218   for (var i in object) {
219     // make sure we don't touch properties that were synthetized
220     if (object.__lookupGetter__(i)) {
221       continue;
222     }
223     var prop = object[i];
224     if (TKUtils.objectIsFunction(prop)) {
225       prop.displayName = TKUtils.createDisplayName(class_name, i);
226     }
227   }
228   for (var i in object.prototype) {
229     // make sure we don't touch properties that were synthetized
230     if (object.prototype.__lookupGetter__(i)) {
231       continue;
232     }
233     var prop = object.prototype[i];
234     if (TKUtils.objectIsFunction(prop)) {
235       prop.displayName = TKUtils.createDisplayName(class_name, i);
236     }
237   }
238 };
239 
240 TKUtils.createDisplayName = function (className, methodName) {
241   return className + '.' + methodName + '()';
242 };
243 
244 TKUtils.buildElement = function (elementData) {
245   // nothing to do if we don't have useful data
246   if (!elementData || !elementData.type) {
247     return null;
248   }
249   //
250   var element = null;
251   switch (elementData.type) {
252     case "emptyDiv":
253       element = document.createElement("div");
254       break;
255     case "container":
256       element = document.createElement("div");
257       for (var i=0; i < elementData.children.length; i++) {
258         element.appendChild(TKUtils.buildElement(elementData.children[i]));
259       }
260       break;
261     case "image":
262       element = document.createElement("img");
263       element.src = elementData.src;
264       break;
265     case "text":
266       element = document.createElement("div");
267       var p = document.createElement("p");
268       p.innerText = elementData.text;
269       element.appendChild(p);
270       break;
271     default:
272       element = document.createElement(elementData.type);
273       element.innerHTML = elementData.content;
274   }
275   // add optional id 
276   if (elementData.id) {
277     element.id = elementData.id;
278   }
279   // add optional class 
280   if (elementData.className) {
281     element.className = elementData.className;
282   }
283   
284   // wrap in optional link
285   if (elementData.link){
286     var subElement = element;
287     element = document.createElement("a");
288     element.href = elementData.link;
289     element.target = "_blank";
290     element.appendChild(subElement);
291   }
292   
293   return element;
294 };
295 
296 /**
297  *  Creates a DOM event.
298  *
299  *  @param {String} eventType The event type.
300  *  @param {Element} relatedTarget The optional related target for this event.
301  *
302  *  @returns {Event} The event.
303  */
304 TKUtils.createEvent = function (eventType, relatedTarget) {
305   var event = document.createEvent('Event');
306   event.initEvent(eventType, true, true);
307   event.relatedTarget = relatedTarget;
308   return event;
309 };
310 
311 /**
312  *  Indicates whether a node is in some other node's subtree.
313  *
314  *  @param {Node} childNode The alleged child node.
315  *  @param {Node} allegedParentNode The alleged parent node.
316  *
317  *  @returns {bool} Whether <code>childNode</code> is a child of <code>allegedParentNode</code>.
318  */
319 TKUtils.isNodeChildOfOtherNode = function (childNode, allegedParentNode) {
320   var node = childNode.parentNode;
321   while (node !== null) {
322     if (node === allegedParentNode) {
323       return true;
324       break;
325     }
326     node = node.parentNode;
327   }
328   return false;
329 };
330 
331 TKUtils.setupDisplayNames(TKUtils, 'TKUtils');
332 
333 /* ==================== TKRect ==================== */
334 
335 /**
336  *  The top left corner of a rectangle.
337  *  @constant
338  *  @type int
339  *  @private
340  */
341 const TKRectTopLeftCorner = 0;
342 /**
343  *  The middle point on the top edge of a rectangle.
344  *  @constant
345  *  @type int
346  *  @private
347  */
348 const TKRectMiddleOfTopEdge = 1;
349 /**
350  *  The top right corner of a rectangle.
351  *  @constant
352  *  @type int
353  *  @private
354  */
355 const TKRectTopRightCorner = 2;
356 /**
357  *  The middle point on the right edge of a rectangle.
358  *  @constant
359  *  @type int
360  *  @private
361  */
362 const TKRectMiddleOfRightEdge = 3;
363 /**
364  *  The bottom right corner of a rectangle.
365  *  @constant
366  *  @type int
367  *  @private
368  */
369 const TKRectBottomRightCorner = 4;
370 /**
371  *  The middle point on the bottom edge of a rectangle.
372  *  @constant
373  *  @type int
374  *  @private
375  */
376 const TKRectMiddleOfBottomEdge = 5;
377 /**
378  *  The bottom left corner of a rectangle.
379  *  @constant
380  *  @type int
381  *  @private
382  */
383 const TKRectBottomLeftCorner = 6;
384 /**
385  *  The middle point on the left edge of a rectangle.
386  *  @constant
387  *  @type int
388  *  @private
389  */
390 const TKRectMiddleOfLeftEdge = 7;
391 /**
392  *  The center of a rectangle.
393  *  @constant
394  *  @type int
395  *  @private
396  */
397 const TKRectCenter = 8;
398 /**
399  *  The top edge of a rectangle.
400  *  @constant
401  *  @type int
402  *  @private
403  */
404 const TKRectTopEdge = 9;
405 /**
406  *  The right edge of a rectangle.
407  *  @constant
408  *  @type int
409  *  @private
410  */
411 const TKRectRightEdge = 10;
412 /**
413  *  The bottom edge of a rectangle.
414  *  @constant
415  *  @type int
416  *  @private
417  */
418 const TKRectBottomEdge = 11;
419 /**
420  *  The left edge of a rectangle.
421  *  @constant
422  *  @type int
423  *  @private
424  */
425 const TKRectLeftEdge = 12;
426 
427 /**
428  *  @class
429  *
430  *  <p>The <code>TKRect</code> provides some utilities to deal with a rectangle data type, allowing to obtain coordinates of the rectangle's points
431  *  of interest as {@link TKPoint} objects and its edges as {@link TKSegment} objects, or obtaining a rectangle resulting in the union of several others.</p>
432  *
433  *  @since TuneKit 1.0
434  *
435  *  @param {float} x The x coordinate.
436  *  @param {float} y The y coordinate.
437  *  @param {float} width The width.
438  *  @param {float} height The height.
439  */
440 function TKRect (x, y, width, height) {
441   /**
442    *  The x coordinate.
443    *  @type float
444    */
445   this.x = x || 0;
446   /**
447    *  The y coordinate.
448    *  @type float
449    */
450   this.y = y || 0;
451   /**
452    *  The width.
453    *  @type float
454    */
455   this.width = width || 0;
456   /**
457    *  The height.
458    *  @type float
459    */
460   this.height = height || 0;
461 };
462 
463 /**
464  *  @private
465  *  Provides the coordinates of a given point of interest.
466  *
467  *  @param {int} index The point of interest.
468  *
469  *  @returns {TKPoint} The point at the given point of interest.
470  */
471 TKRect.prototype.pointAtPosition = function (index) {
472   var point;
473   if (index == TKRectTopLeftCorner) {
474     point = new TKPoint(this.x, this.y);
475   }
476   else if (index == TKRectMiddleOfTopEdge) {
477     point = new TKPoint(this.x + this.width / 2, this.y);
478   }
479   else if (index == TKRectTopRightCorner) {
480     point = new TKPoint(this.x + this.width, this.y);
481   }
482   else if (index == TKRectMiddleOfRightEdge) {
483     point = new TKPoint(this.x + this.width, this.y + this.height / 2);
484   }
485   else if (index == TKRectBottomRightCorner) {
486     point = new TKPoint(this.x + this.width, this.y + this.height);
487   }
488   else if (index == TKRectMiddleOfBottomEdge) {
489     point = new TKPoint(this.x + this.width / 2, this.y + this.height);
490   }
491   else if (index == TKRectBottomLeftCorner) {
492     point = new TKPoint(this.x, this.y + this.height);
493   }
494   else if (index == TKRectMiddleOfLeftEdge) {
495     point = new TKPoint(this.x, this.y + this.height / 2);
496   }
497   else if (index == TKRectCenter) {
498     point = new TKPoint(this.x + this.width / 2, this.y + this.height / 2);
499   }
500   return point;
501 };
502 
503 /**
504  *  @private
505  *  Provides the segment for a given edge.
506  *
507  *  @param {int} index The edge.
508  *
509  *  @returns {TKSegment} The segment for the given edge.
510  */
511 TKRect.prototype.edge = function (index) {
512   var edge;
513   if (index == TKRectTopEdge) {
514     edge = new TKSegment(this.pointAtPosition(TKRectTopLeftCorner), this.pointAtPosition(TKRectTopRightCorner));
515   }
516   else if (index == TKRectRightEdge) {
517     edge = new TKSegment(this.pointAtPosition(TKRectTopRightCorner), this.pointAtPosition(TKRectBottomRightCorner));
518   }
519   else if (index == TKRectBottomEdge) {
520     edge = new TKSegment(this.pointAtPosition(TKRectBottomLeftCorner), this.pointAtPosition(TKRectBottomRightCorner));
521   }
522   else if (index == TKRectLeftEdge) {
523     edge = new TKSegment(this.pointAtPosition(TKRectTopLeftCorner), this.pointAtPosition(TKRectBottomLeftCorner));
524   }
525   return edge;
526 };
527 
528 /**
529  *  Returns a {@link TKRect} from a rectangle returned by the <code>Node.getBoundingClientRect</code> method.
530  *
531  *  @param {ClientRect} rect The CSS client rectangle.
532  *
533  *  @returns {TKRect} The equivalent rectangle as a TuneKit data type.
534  */
535 TKRect.rectFromClientRect = function (rect) {
536   return new TKRect(rect.left, rect.top, rect.width, rect.height);
537 };
538 
539 /**
540  *  @private
541  *  Returns a {@link TKRect} encompassing the union of a list of other rectangles.
542  *
543  *  @param {Array} rects The various rectangles we'd like the union of.
544  *
545  *  @returns {TKRect} The rectangle encompassing the union of a list of the provided rectangles.
546  */
547 TKRect.rectFromUnionOfRects = function (rects) {
548   if (rects.length < 1) {
549     return new TKRect();
550   }
551   var union = rects[0];
552   var rect;
553   for (var i = 1; i < rects.length; i++) {
554     rect = rects[i];
555     union.x = Math.min(union.x, rect.x);
556     union.y = Math.min(union.y, rect.y);
557     union.width = Math.max(union.width, rect.x + rect.width);
558     union.height = Math.max(union.height, rect.y + rect.height);
559   }
560   return union;
561 };
562 
563 /* ==================== TKPoint ==================== */
564 
565 /**
566  *  @private
567  *  @class
568  *
569  *  <p>The <code>TKPoint</code> provides a TuneKit data type to deal with points in 2D space and some utilities to work with them, such as figuring out
570  *  the distance between two points.</p>
571  *
572  *  @since TuneKit 1.0
573  *
574  *  @param {float} x The x coordinate.
575  *  @param {float} y The y coordinate.
576  */
577 function TKPoint (x, y) {
578   /**
579    *  The x coordinate.
580    *  @type float
581    */
582   this.x = x || 0;
583   /**
584    *  The y coordinate.
585    *  @type float
586    */
587   this.y = y || 0;
588 };
589 
590 /**
591  *  Provides the distance between this point and another.
592  *
593  *  @param {TKPoint} aPoint The point to which we'd like to figure out the distance.
594  *
595  *  @returns {float} The distance between the receiver the provided point.
596  */
597 TKPoint.prototype.distanceToPoint = function (aPoint) {
598   return Math.sqrt(Math.pow(aPoint.x - this.x, 2) + Math.pow(aPoint.y - this.y, 2));
599 };
600 
601 /* ==================== TKSegment ==================== */
602 
603 /**
604  *  @class
605  *  @private
606  *
607  *  <p>The <code>TKSegment</code> provides a TuneKit data type to deal with segments in 2D space and some utilities to work with them, such as figuring out
608  *  the shortest distance between a segment and a point.</p>
609  *
610  *  @since TuneKit 1.0
611  *
612  *  @param {TKPoint} a The first extremity of the segment.
613  *  @param {TKPoint} b The other extremity of the segment.
614  */
615 function TKSegment (a, b) {
616   /**
617    *  The first extremity of the segment.
618    *  @type TKPoint
619    */
620   this.a = a;
621   /**
622    *  The other extremity of the segment.
623    *  @type TKPoint
624    */
625   this.b = b;
626   this.ab = new TKPoint(b.x - a.x, b.y - a.y);
627   /**
628    *  The segment's length.
629    *  @type float
630    */
631   this.length = b.distanceToPoint(a);
632 };
633 
634 // XXX: this only deals with horizontal and vertical lines
635 TKSegment.prototype.crossPoint = function (c) {
636   return (this.a.y == this.b.y) ? new TKPoint(c.x, this.a.y) : new TKPoint(this.a.x, c.y);
637 };
638 
639 /**
640  *  Computes the shortest distance between this segment and the given point.
641  *
642  *  @param {TKPoint} aPoint
643  *
644  *  @returns {float} The shortest distance between this segment and the given point.
645  */
646 TKSegment.prototype.distanceToPoint = function (aPoint) {
647   var d;
648   var cross_point = this.crossPoint(aPoint);
649   // is it inside the segment?
650   if (cross_point.distanceToPoint(this.a) + cross_point.distanceToPoint(this.b) == this.length) {
651     d = aPoint.distanceToPoint(cross_point);
652   }
653   else {
654     d = Math.min(aPoint.distanceToPoint(this.a), aPoint.distanceToPoint(this.b));
655   }
656   return d;
657 };
658 
659 /* ================= Debugging ======================== */
660 
661 var DEBUG = false;
662 
663 function debug(msg) {
664   if (window.DEBUG !== undefined && window.DEBUG) {
665     console.log("DEBUG: " + msg);
666   }
667 };
668