1 /*
  2 *	Karma Framework
  3 *	http://wiki.sugarlabs.org/go/Karma
  4 *	
  5 *	Copyright (c)  2009
  6 *	Felipe López Toledo	zer.subzero@gmail.com
  7 *	Bryan W Berry		bryan@olenepal.org
  8 *      
  9 *	Under MIT License:
 10 *	Permission is hereby granted, free of charge, to any person
 11 *	obtaining a copy of this software and associated documentation
 12 *	files (the "Software"), to deal in the Software without
 13 *	restriction, including without limitation the rights to use,
 14 *	copy, modify, merge, publish, distribute, sublicense, and/or sell
 15 *	copies of the Software, and to permit persons to whom the
 16 *	Software is furnished to do so, subject to the following
 17 *	conditions:
 18 *	
 19 *	The above copyright notice and this permission notice shall be
 20 *	included in all copies or substantial portions of the Software.
 21 *	
 22 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 23 *	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 24 *	OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 25 *	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 26 *	HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 27 *	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 28 *	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 29 *	OTHER DEALINGS IN THE SOFTWARE.
 30 */
 31 
 32 /**
 33 * @fileOverview Contains karma library
 34 * @version 0.1
 35 * @author Felipe Lopez Toledo <zer.subzero@gmail.com>
 36 */
 37 
 38  
 39 /**
 40  * See <a href="http://jquery.com">jQuery</a>.
 41  * @name jQuery
 42  * @exports $ as jQuery
 43 */
 44 
 45 (function ($) {
 46 //helpers
 47 /**
 48 Checks if the argument 'arg' is set and if its type is 'type'.<br>
 49 1. if arg is set: it returns 'toReturn' if specified, otherwise it returns 
 50 	'true'
 51 2. if arg is not set: it returns 'false'
 52 @param arg The param to check
 53 @param {Object} [type] The expeted type of 'arg'
 54 @param [toReturn] object or value to return in case 1
 55 @returns true | false | toReturn
 56 @example
 57 var msg = "hi";
 58 valid(msg); //returns true
 59 valid(msg, "String" ); //returns true
 60 valid(msg, "Number"); //returns false
 61 valid(msg, "String",false ); //returns false
 62 valid(msg, "String", "hello" ); //returns "hello"
 63 valid(msg123); //returns false
 64 **/
 65 var valid = function ( arg, type, toReturn ) {
 66 	if ( type ) {
 67 		if ( typeof arg === type ) {
 68 			if ( toReturn )
 69 				return toReturn;
 70 			return true;
 71 		}
 72 		return false
 73 	}
 74 	if ( typeof arg !== "undefined" && arg!== "null" ) return true;
 75 	return false;
 76 }
 77 /**
 78 Clones an object
 79 @param {object} obj The source object
 80 @returns {object} The cloned object
 81 **/
 82 var clone = function( obj ){
 83     if(obj == null || typeof(obj) != 'object')
 84         return obj;
 85     var temp = new obj.constructor(); 
 86     for(var key in obj)
 87         temp[ key ] = clone( obj[ key ] );
 88     return temp;
 89 }
 90 
 91 /**
 92 Karma
 93 @name Karma
 94 @class Represents a Karma (master) object.
 95 @namespace 
 96 @param {String | Object } options Constructor arguments 
 97 @param {String | Object } [options.container] Target DIV-class that will contain
 98 	any canvas element created using Karma functions
 99 @param {String} [options.language] 
100 */
101 var Karma = function( options ) {
102 	var that = this;
103 	this.version = "0.01";
104 	//
105 	//relative path to the po, images, sounds, etc.  from the html
106 	//defined here: http://wiki.sugarlabs.org/go/Karma/Bundle_layout
107 	//localized is recalculated inside localizeContent ( $ = language.lang )
108 	this.paths = {
109 		po: "po/",
110 		images: {
111 				localized:	"assets/$/images/",
112 				generic:	"assets/generic/images/"
113 				},
114 		sounds: {
115 				localized:	"assets/$/sounds/",
116 				generic:	"assets/generic/sounds/"
117 				},
118 		videos: {
119 				localized:	"assets/$/videos/",
120 				generic:	"assets/generic/videos/"
121 				}
122 	};
123 	this.supportedLangFileTypes = [ 
124 		{ ext: "po",   type: 'application/x-po' },
125 		{ ext: "json", type: 'application/json'}
126 	];
127 	//
128 	//PRIVATE STUFF start
129 	/**
130 	Gets the language acording to the browser language
131 	@returns {Object} <br>
132 	lang: countryCode and langCode (if specified)
133 	langCode*: language code represented as xx, example: en.<br>
134 	countryCode*: country code represented as YY, example: US.<br>
135 	*optional
136 	**/
137 	var getLanguage = function () {
138 		//console.log +=  navigator.language +"\n";
139 		var lang = navigator.language || navigator.browserLanguage; //mozilla/ie
140 		lang = lang.replace(/_/, '-').toLowerCase();
141 		if (lang.length > 3 ) {
142 			var country = lang.substring(3, 5);
143 			lang = lang.substring(0, 2);
144 			if ( country.match(/[^a-zA-Z]/) === null ) {
145 				country = country.toUpperCase();
146 				return  { 
147 					"lang": lang + "-" + country, 
148 					"langCode": lang, 
149 					"countryCode": country 
150 				};
151 			}
152 		}
153 		return { "lang": lang };
154 	};
155 	/**
156 	Creates a new Gettext object and returns a shortcut function to localise 
157 	defined strings.<br>We use karma.Gettext.js it's a modification of 
158 	<a href=http://jsgettext.berlios.de/doc/html/Gettext.html> 
159 	Gettext.js</a> from beril OS.
160 	@requires karma.Gettext.js
161 	@param {Object}  options The arguments of the Gettext constructor
162 	@returns {Function} A generic function to call Gettext functions
163 	**/
164 	var i18nWrapper = function ( options ) {
165 		var gt = new Gettext( options );
166 		if ( typeof ( gt ) === 'undefined' )
167 			throw new Error("Unable to initialize Gettext object");
168 		return (function (str1, str2, str3, str4 ) {
169 			var n, context, singular, plural;
170 			if (typeof(str4) != 'undefined') {
171 				// number, context, singular, plural
172 				return gt.npgettext(str2, str3, str4, str1);
173 			} else if (typeof(str3) != 'undefined') {
174 				// number, singular, plural
175 				return gt.ngettext(str2, str3, str1);
176 			} else if (typeof(str2) != 'undefined') {
177 				// context, msgid
178 				return gt.pgettext(str1, str2);
179 			} else if (typeof(str1) != 'undefined') {
180 				// msgid
181 				return gt.gettext(str1);
182 			} else {
183 				// nothing passed in; return blank string.
184 				// XXX: we could error here, but that may cause more harm than good.
185 				return '';
186 			}
187 		});
188 	};
189 	/**
190 	Localises the inline html content and it creates the localised paths for 
191 	"images", "sounds" and "videos".
192 	<b>Note:</b>Inline html localisation under development<br> 
193 	@param {String} lang The language that will be used to localise the content
194 	@see <a href="http://wiki.sugarlabs.org/go/Karma/Bundle_layout">
195 		Karma Bundle_layout</a>
196 	**/
197 	var localiseContent = function ( lang ) {
198 		
199 		var toFix = ["images", "sounds", "videos"];
200 		for (var i = 0; i < toFix.length; i++) {
201 			that.paths[ toFix[ i ] ].localized = that.paths[ 
202 				toFix[ i ] ].localized.replace('\$', lang );
203 		}
204 		//dirty hack to support {lang}_AudioFile
205 		var prefix = lang+"_";
206 		that.paths[ "sounds" ].localized+=prefix;
207 	};
208 	/**
209 	It will attempt to load a language file, the posible languages are defined 
210 	on language.alternatives.
211 	<p>The language file type could be: .po (Pootle) or .json (JSON). The 
212 	precedence between file types is defined according to which is defined first 
213 	in supportedLangFileTypes. By default Pootle files has precedence over JSON,
214 	files.</p>
215 	@see Karma
216 	@returns {String} The name of the language file loaded. Example: en-US.po
217 	**/
218 	var loadAlternatives = function ( ) {
219 		var loaded = undefined;
220 		var tryNext = true;
221 		//try to load the po or json language file if it exists. 
222 		//the lang order is acording to options.language.alternatives
223 		//the type (po or json or ...) is defined in supportedLangFileTypes
224 		$.each( that.language.alternatives, function ( c, lang ) {
225 			for (var i=0; i < that.supportedLangFileTypes.length 
226 			&& tryNext === true; i++) {
227 				$.ajax({
228 					url: that.paths.po +  lang + "." + 
229 						 that.supportedLangFileTypes[i].ext,
230 					cache: true,
231 					dataType: "text",
232 					async: false, //important: touch it at your own risk
233 					success: function( data, textStatus ){
234 						
235 						loaded =  lang + "." + 
236 								  that.supportedLangFileTypes[i].ext;
237 					    //i18n
238 					    //we pass the data so we avoid re-loading the file
239 					    //creates the shorcout
240 					    //Bryan: we aren't using this right now
241 					   /* that.i18n.root[ that.i18n.shortcut ] =  i18nWrapper(
242 							{ 
243 								domain 	: lang, 
244 								file 	: { 
245 										  type: that.supportedLangFileTypes[i].type, 
246 										  uri: this.url, data: data 
247 										} 
248 							}
249 						);*/
250 						localiseContent( lang );
251 						tryNext = false; 
252 					},
253 					error: function ( XHR, textStatus, errorThrown ) {
254 						//the file doesn't exist or it wasn't possible to load it
255 						tryNext = true;
256 					}
257 				});
258 				return tryNext;
259 			}
260 		});
261 		return loaded;
262 	};
263 	//PRIVATE STUFF end
264 	// default options 
265 	var defaultOptions ={
266 		container:   "#karma-main",
267 		language:   { 
268 						lang: 			undefined,
269 						alternatives: 	['en-US', 'en'],
270 						countryCode: 	undefined,
271 						langCode:		undefined,
272 					},
273 		i18n:		{
274 						root: 			self, // self is global
275 						shortcut: 		"_"
276 					}
277 	};
278 	//
279 	this.library = { "images": [], "sounds": [], "videos":[], "shapes":[] };
280 	
281 	//initializes the defaultOptions argument
282 	//1 argument: string.  assume it's the container
283 	if ( typeof options === "string" ) {
284 		options = { container: options };
285 		options.language = getLanguage() ;
286 	} else if (typeof options === "object" ){
287 		if ( typeof options.lang === "string" ) {
288 			//if language is string, assume  it's the language.lang
289 			options.language = { lang: options.lang };
290 		}
291 	}
292 	$.extend( true, defaultOptions, options );
293 	//
294 	//copy defaultOptions to this, we use this.xyz instead this.defaultOptions.xyz 
295 	for (var i in defaultOptions ) {
296 		this[ i ] = defaultOptions[i];
297 	}
298 	
299 	//initializes i18n
300 	//add the localized language to the language.alternatives
301 	if ( typeof this.language.countryCode !== "undefined" ) {
302 		this.language.alternatives.unshift( 
303 			this.language.langCode, 
304 			this.language.countryCode 
305 		);
306 	}
307 	if ( typeof this.language.lang !== "undefined" ) {
308 		this.language.alternatives.unshift( this.language.lang );
309 	}
310 	//try to load the localized lang file (po or json or ...)
311 	this.language.fileLoaded = loadAlternatives( );
312 	//initializes the container
313 	if ( typeof this.container === "string" ) {
314 		this.container = $( this.container )[ 0 ];
315 		if ( !valid(this.container) ) delete this.container;
316 	}
317 	
318 	gk = {
319 		"paths": this.paths
320 	}
321 	this.surfaces = {};
322 };
323 
324 /**
325 @memberOf Karma 
326 @namespace Geometry functions.
327 **/
328 Karma.prototype.geometry = {
329 	/**
330 	Converts a value from degrees to radians.
331 	@param {Number} angle The angle in degrees 
332 	@returns {Number} The The angle in radians 
333 	**/
334 	radians : function( angle ){
335 		return ( angle / 180 ) * Math.PI;
336 	},
337 	/**
338 	Gets the square of the Euclidian (ordinary) distance between 2 points.
339 	@param {Number} Point Point No. 0 
340 	@param {Number} Point Point No. 1
341 	@returns {Number} The square of the Euclidian distance 
342 	**/
343 	distance2 : function ( p0, p1 ) {
344 		return   (p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p1.y) * (p1.y - p1.y); 
345 	},
346 	/**
347 	Gets the Euclidian (ordinary) distance between 2 points.<br>
348 	<b>Warning:</b> It's slower than distance2 function
349 	@param {Number} Point Point No. 0 
350 	@param {Number} Point Point No. 1
351 	@returns {Number} The Euclidian distance 
352 	**/
353 	distance : function ( p0, p1 ) {
354 		return   Math.sqrt( Karma.prototype.geometry.distance2( p0, p1 ) ); 
355 	}
356 
357 };
358 /**
359 @memberOf Karma
360 @namespace Graphics functions.
361 **/
362 Karma.prototype.graphics = {
363 	/**
364 	Creates a new rectangle. It's a shortcut for calling 'new KRectangle(..)'.
365 	**/
366 	rectangle: function ( args ) { return new KRectangle( args ); },
367 	/**
368 	Creates a new circle. It's a shortcut for calling 'new KCircle(..)'.
369 	**/
370 	circle: function ( args ) { return new KCircle( args ); }
371 }
372 /**
373 @memberOf Karma 
374 @namespace Math functions.
375 **/
376 Karma.prototype.math = {
377 	/**
378 	Generates a random bumber between lower bound and upper bound inclusive.
379 	@param {Number} lower The lower bound
380 	@param {Number} upper The upper bound
381 	@returns {Number} The generated number
382 	**/
383 	rand : function ( lower, upper ){
384 		return Math.round ( Math.random() * (upper - lower) + lower );
385 	}
386 }
387 //FIXME
388 //everything inside karma.graphics is exported to karma.prototype
389 $.extend( Karma.prototype, Karma.prototype.graphics);
390 //
391 /**
392 @param {Object} [toLoad] The Object that has the arrays for preloading.
393 @param {Array} [toLoad.images] The images 
394 @param {Array} [toLoad.sounds] The sounds 
395 @param {Array} [toLoad.videos] The videos 
396 @memberOf Karma 
397 @returns {Object} this
398 **/
399 Karma.prototype.init = function( toLoad ) {
400 	this.pendingToLoad = toLoad;
401 	return this; //chaining :)
402 };
403 
404 /**
405 Main function. Any Karma function call should be inside the callback function.
406 The callback function will be executed when the preloading finishes.
407 @param {Function} cb The callback funtion
408 @memberOf Karma 
409 @see Karma#init
410 **/
411 Karma.prototype.main = function ( cb ) {
412 	if ( valid( this.pendingToLoad ) ) {
413 		//loader
414 		var loaderDiv = $("body").append('<div id=\"karma-loader\">Karma is \
415 		loading ...<div id=\"karma-loader\" class=\"status\"></div></div>');
416 		var statusDiv = $("#karma-loader .status");
417 		
418 		var statusUpdate = function ( current, error, total) {
419 			statusDiv.html(current + "/" + total + (error > 0 ? " [ "+error+" ]":''));
420 		};
421 		
422 		var that = this;
423 		var categories = ["images", "sounds", "videos" ];
424 		var counters = { "loaded":0, "error": 0 };
425 		var totalItems = 0;
426 		//creates the surfaces
427 		if ( valid( this.pendingToLoad[ "surfaces" ] ) ) {
428 			$.each (this.pendingToLoad[ "surfaces" ], function( key, config ){
429 				Karma.prototype.surface.call( that, config );
430 			});
431 		}
432 		statusUpdate( 0, 0, totalItems);
433 		//get the total items
434 		for ( var i=0; i < categories.length; i++ ) {
435 			if ( valid ( this.pendingToLoad[ categories[ i ] ] ) ) {
436 				totalItems += this.pendingToLoad[ categories[ i ] ].length;
437 			}
438 		}
439 		
440 		/**
441 		callback to check if all the items were loaded or got an error when 
442 		loading
443 		**/
444 		var errors=[];
445 		var checkAllLoaded = function ( ev ) {
446 			if ( ev.type === "load") counters.loaded += 1;
447 			else {
448 				errors.push( ev.target.src );
449 				counters.error += 1; 
450 			}
451 			statusUpdate( counters.loaded, counters.error, totalItems);
452 			if ( counters.loaded + counters.error === totalItems ) {
453 				if ( counters.error > 0 ){
454 					throw ( "Media files not found: " + errors );
455 				}
456 				$("#karma-loader:hiden:first").fadeOut("slow",function(){ 
457 					$(this).remove();});
458 				if ( cb ) cb();
459 			}
460 		};
461 		
462 		for ( var i=0; i < categories.length; i++ ) {
463 			var category = categories[ i ];
464 		    if ( valid ( this.pendingToLoad[ category ] ) ) {
465 				//load all the category elements
466 				var type = category.substr( 0, category.length-1 )
467 				$.each (this.pendingToLoad[ category ], function( key, config ){
468 					var name = config.name;
469 					delete config.name;
470 					//register the elements into the library
471 					that.library[ category ][ name ] =  Karma.prototype[ type ]( 
472 						config
473 					);
474 					that.library[ category ][ name ].media.addEventListener(
475 						"load",checkAllLoaded,false
476 					);
477 					that.library[ category ][ name ].media.addEventListener(
478 						"error",checkAllLoaded,false
479 					);
480 				});
481 			}
482 		}
483 	}else {
484 		if ( cb ) cb();
485 	}
486 }
487 /**
488 A shortcut for calling 'KImage( )'
489 @see KImage
490 @memberOf Karma 
491 @returns {Object} new instance of KImage object
492 **/
493 Karma.prototype.image = function ( args ) { return new KImage( args ) };
494 /**
495 A shortcut for calling 'KSound( )'
496 @see KSound
497 @memberOf Karma 
498 @returns {Object} new instance of KSound object
499 **/
500 Karma.prototype.sound = function ( args ) { return new KSound( args ) };
501 /**
502 A shortcut for calling 'KVideo( )'
503 @see KVideo
504 @memberOf Karma 
505 @returns {Object} new instance of KVideo object
506 **/
507 Karma.prototype.video = function ( args ) { alert("Not implemented yet"); };
508 /**
509 A shortcut for calling 'KGroup( )'
510 @see KGroup
511 @memberOf Karma 
512 @returns {Object} new instance of KGroup object
513 **/
514 Karma.prototype.group = function ( args ) { return new KGroup( args ) };
515 /**
516 A shortcut for calling 'KButton( )'
517 @see KButton
518 @memberOf Karma 
519 @returns {Object} new instance of KButton object
520 **/
521 Karma.prototype.button = function ( args ) { return new KButton( args ) };
522 /**
523 A shortcut for calling 'KSurface(.. )'.  
524 @see KSurface
525 @memberOf Karma 
526 @returns {Object} new instance of KSurface object 
527 **/
528 Karma.prototype.surface = function ( options ) {
529 	if ( !valid(options, "object") ){
530 		var options = { name: "ksurface-"+ ( this.surfaces.length + 1 ) };
531 	}
532 	options.mainContainer = this.container;
533 	options.paths = this.paths;
534 	this.surfaces[ options.name ] = new KSurface( options ); 
535 	return this.surface[ options.name ];
536 };
537 
538 /**
539 Mouse
540 **/
541 var mouse = {};
542 /**
543 Gets the 'x' and 'y' mouse coordinates relatives to the canvas  
544 @returns {Object} An Object with 'x' and 'y' attributes
545 **/
546 mouse.getRelativeCanvasPosition = function ( ev ) {
547 	if ( !ev ) return;
548 	var xy ={x:0, y:0};
549 	xy.x = ev.layerX;
550     xy.y = ev.layerY;
551 	return xy;
552 };
553 
554 //Events stuff
555 var master ={};
556 master.buttons =[];
557 var handleEvents = function( ev ) {
558 	var xy = mouse.getRelativeCanvasPosition( ev  );
559 	for (var i in master.buttons) {
560 		if (master.buttons[i].isPointInPath( xy.x, xy.y) ){
561 			master.buttons[i].onClick( ev );
562 		}
563 	}
564 	/*switch(ev.type){
565 		case "click": break;
566 	}*/
567 	/*var s="";
568 	for (var i in ev) {
569 		s+=i+"="+ev[i]+"\n";
570 	}
571 	alert(s);*/
572 };
573 
574 /**
575 Master class creator. It will merge all the properties and methods of the 
576 recived arguments (objects) into one new class that wil be returned.
577 @returns {Object} The new class 
578 **/
579 var Class = function ( ) {
580 	var log="";
581 	var parents = [];
582 	for ( var i = 0; i < arguments.length; i++ ) {
583 		if ( arguments[i].prototype && arguments[i].init ) {
584 			parents.push( arguments[i].init );
585 		}
586 	}
587 	var o = function ( ) {
588 		//we inject all the init functions 
589 		/*for ( var i = 0; i < this.__parents.length; i++ ) {
590 			this.__parents[ i ].apply ( this, arguments );
591 		}*/
592 		//call the real  class init
593 		if ( this.init )
594 			this.init.apply( this, arguments );
595 	};
596 
597 	o.prototype ={};
598 	var a;
599 	for ( var i =0; i < arguments.length; i++) {
600 		a = arguments[i];
601 		log += "**" + typeof a+"\n";
602 		//if ( a === "function") {
603 		if (a.prototype) {
604 			for ( var j in a.prototype ) {
605 				//log += j+" = "+a.prototype[j]+"\n";
606 				o[ j ] = o.prototype[ j ] = a.prototype [ j ];
607 			}
608 		}
609 		else {
610 		//if ( typeof a === "object") {
611 			for (var j in a) {
612 				//log += j+" = "+a[j]+"\n";
613 				o[ j ] = o.prototype[ j ] = a [ j ];
614 			}
615 		}
616 		
617 	}
618 	o.prototype.__parents = parents;
619 	//alert( log );
620 	return  o; //(function ( ) { return new o( arguments );});
621 };
622 
623 /**
624 Creates a new surface. A surface is a 'canvas' element with additional methods
625 that makes easier its manipulation. <br>
626 There are 2 ways to create a new KSurface:
627 <ol>
628 	<li><b>Using an existing canvas element:</b>You must provide at least 
629 		the 'canvas' parameter. The 'name' is optional (if it's not provided the
630 		'canvas' parameter will be used).
631 	</li>
632 	<li><b>Creating a new canvas element:</b> A new 'canvas' element will be
633 		created and it will be appended to the specific 'container'. 
634 		You must provide at least the 'name' and 'container' parameters.
635 	</li>
636 </ol>
637 @name KSurface
638 @class KSurface class
639 @param {object} options Constructor options.
640 @param {string} [options.name]  The desired name for the surface. The value must
641 	be unique among others KSurfaces-name objects.
642 @param {string} [options.canvas]  The name of the element. Commonly the 
643 	canvas-id value.
644 @param {string | object} [options.container] The the name of the container 
645 	element. Commonly a div-id value.
646 @param {number} [width=100] The width of the canvas.
647 @param {number} [height=100] The height of the canvas.
648 @param {number} [fps=24] The frames per second for any refresh operation.
649 @param {boolean} [visible=true] 'true' if the content is visible (will be drawn).
650 @memberOf Karma 
651 **/
652 var KSurface = Class(
653 	{
654 		init: function( options ){
655 			//fix the container
656 			if ( valid( options.container, "string" ) && !valid( options.canvas)
657 			) {
658 				var name=options.container;
659 				options.container = $( options.container )[ 0 ];
660 				if ( !valid (options.container) ){
661 					// the container must be created inside the mainContainer
662 					if ( !valid( options.mainContainer ) ){
663 						throw ("You need to create the Karma master container");
664 					}
665 					var div = document.createElement("div");
666 					div.id = name;
667 					options.container=options.mainContainer.appendChild( div );
668 				} 
669 			}else {
670 				if ( !valid( options.mainContainer ) ){
671 					throw ("You need to create the Karma master container");
672 				}
673 				options.container = options.mainContainer;
674 			}
675 			
676 			var defaultOptions = {
677 				//mainContainer: '',//must be overwritten by Karma.container
678 				name: '',//must be overwritten by the Karma.surface OR user
679 				container: '', //must be overwritten by Karma.container OR user
680 				
681 				width: 100,
682 				height: 100,
683 				fps: 24,
684 				visible: true
685 			};
686 			$.extend( this, defaultOptions, options);
687 			
688 			if ( !this.canvas ) {
689 				this.canvas = document.createElement("canvas");
690 				this.canvas.width  = this.width; 
691 				this.canvas.height = this.height;
692 				this.canvas.id = this.name;
693 				this.container.appendChild( this.canvas );
694 			}else {
695 			    this.canvas = document.getElementById( options.canvas );
696 			    if ( !this.canvas ){
697 					throw new Error ("The canvas id doesn't exist");
698 			    }
699 			    this.width = this.canvas.width;
700 			    this.height = this.canvas.height;
701 			    if (!this.name){
702 					this.name = this.canvas.id;
703 			    }
704 			}
705 			if ( this.canvas.getContext ) {
706 				this.ctx = this.canvas.getContext("2d");
707 			}else {
708 				throw new Error ("Your browser doesn't support canvas, \
709 				try the newest Firefox, Safari or Google Chrome");
710 			}
711 			//ctx methods chaining stuff
712 			var toChain = [
713 			"globalAlpha", "globalCompositeOperation", "lineWidth", "lineCap", 
714 			"lineJoin", "miterLimit", "font", "textAlign", "textBaseline", "save", 
715 			"restore", "scale", "rotate", "translate", "transform", "setTransform", 
716 			"clearRect", "fillRect", "strokeRect", "beginPath", "closePath", 
717 			"moveTo", "lineTo", "quadraticCurveTo", "bezierCurveTo", "arcTo", 
718 			"arc", "rect", "fill", "stroke", "clip", "fillText", "strokeText", 
719 			"measureText", "isPointInPath", "strokeStyle", "fillStyle", 
720 			"createLinearGradient", "createRadialGradient", "createPattern", 
721 			"shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", 
722 			//"mozTextStyle", "mozDrawText", "mozMeasureText", "mozPathText", 
723 			"mozTextAlongPath", "drawImage", "getImageData", "putImageData", 
724 			"createImageData", "drawWindow"
725 			];
726 			var that=this;
727 			var chainMaker = function ( name ){
728 				that[ name ] = function ( ){
729 					var type = typeof that.ctx[name];
730 					if ( type === "function") {
731 						that.ctx[ name ].apply( that.ctx, arguments );
732 					}else if ( type === "string" ){
733 						that.ctx[ name ] = arguments[0];
734 					}else {
735 						throw ("wtf?!: impossible to chain " + name + "!");
736 					}
737 					return that;
738 				};
739 			};
740 			for (var i=0; i<toChain.length; i++){
741 				chainMaker(  toChain[ i ] );
742 			}
743 
744 			
745 			//events
746 			this.canvas.addEventListener("contextmenu", function(ev){
747 				//
748 				},false
749 			);
750 			this.canvas.addEventListener("click", 
751 				handleEvents,
752 				false
753 			);
754 			
755 		},
756 		/**
757 		Adds an event listener to the surface
758 		@param {string} type Event type
759 		@param {function} cb Function call back
760 		@param {boolean} [bubble=false] If the event must be captured on
761 			bubbling phase
762 		**/
763 		addEventListener : function ( type, cb, bubble ) {
764 			this.canvas.addEventListener( type, cb, bubble || false );
765 		},
766 		/**
767 		Removes an event listener attached to the surface
768 		@param {string} type Event type
769 		@param {function} cb Function call back
770 		@param {boolean} [bubble=false] If the event must be captured on
771 			bubbling phase
772 		**/
773 		removeEventListener : function ( type, cb, bubble ) {
774 			this.canvas.removeEventListener( type, cb, bubble || false );
775 		},
776 		/**
777 		Clears a rectangular area within the canvas
778 		@param {Number} [x=0] Start position of x
779 		@param {Number} [y=0] Start position of y
780 		@param {Number} [width=canvas width] Square width
781 		@param {Number} [height=canvas height] Square height
782 		**/
783 		clear : function ( x, y, width, height ) {
784 			this.ctx.clearRect(
785 				x || 0,
786 				y || 0, 
787 				width  || this.width, 
788 				height || this.height
789 			);
790 		    return this;
791 		},
792 		draw: function (  ) {
793 			
794 		}
795 	}
796 );
797 
798 /**
799 Karma basic Object 
800 @name KObject
801 @class The basic Karma object
802 @param {Object} [options] Options 
803 @param {String} [options.localized = true] The object will be localized
804 @memberOf Karma
805 **/
806 var KObject = Class(
807 	{
808 		init: function ( options ) {
809 			if ( valid(options.localized, "boolean" ) ) {
810 				this.localized = options.localized;
811 			}else {
812 				this.localized = true;
813 			}
814 		}
815 	}
816 );
817 /**
818 Graphics basic Object
819 @name KGraphic
820 @class General methods for any Graphic object
821 @param {object} [options] Options 
822 @param {number} [options.x = 0] The 'x' position of the object
823 @param {number} [options.y = 0] The 'y' position of the object
824 @param {number} [options.z = 0] The 'z' index of the object
825 @param {number} [options.width = 0] The 'width' of the object
826 @param {number} [options.height = 0] The 'height' of the object
827 @param {boolean} [options.visible = true] Defines if the object will be visible 
828 	when drawing
829 @memberOf Karma
830 @augments KObject
831 **/
832 var KGraphic = Class(
833 	KObject,
834 	{
835 		init: function ( options ) {
836 			if ( valid( options.localized ) ) 
837 				KObject.init.call(this, options.localized );
838 			var defaultOptions = {
839 				x : 0,
840 				y : 0,
841 				z : 0,
842 				width: 0,
843 				height: 0,
844 				visible : true
845 			}
846 			$.extend( this, defaultOptions, options);
847 		},
848 		/**
849 		@memberOf KGraphic
850 		Determines if the 'x' and 'y' coodinates are inside the object.
851 		@returns {boolean} 'true' if the coordinates are inside or on the border
852 			of the object, otherwise 'false'
853 		**/
854 		isPointInPath : function( x, y ) {
855 			return (this.x <= x &&  (this.x + this.width) >= x && 
856 					this.y <= y &&  (this.y+this.width)>=y); 
857 		},
858 		addEventListener : function (type, cb, bubble) {
859 			//FIXME
860 		}
861 	}
862 );
863 /**
864 An object that collects multiple KGraphic objects. Supports multiple objects.
865 @name KGroup
866 @class An object that collects multiple KGraphic objects 
867 @augments KGraphic
868 @memberOf Karma 
869 **/
870 var KGroup = Class(
871 	KGraphic,
872 	{
873 		init: function ( options ) {
874 			this.childNodes = [];
875 			this.sorted = true;
876 		},
877 		/**
878 		@memberOf KGroup
879 		Adds each argument passed to the funtion to chilNodes.
880 		@param {Array:KGraphic} arguments The elements to add to childNodes		
881 		@see KGroup#draw
882 		**/
883 		appendChild : function (  ) {
884 			if ( arguments.length > 0 ) {
885 				for ( var i = 0; i< arguments.length; i++) {
886 					this.childNodes.push ( arguments[ i ] );
887 				}
888 				this.sorted = false;
889 				
890 			}
891 		},
892 		removeChild: function () {
893 			//FIXME
894 		},
895 		/**
896 		@memberOf KGroup
897 		Draws all the elements in childNodes. The elements are drawn according
898 		to its 'z' (z-index) value.
899 		@see KGroup#appendChild
900 		**/
901 		draw : function() {
902 			if ( this.visible && this.childNodes.length > 0 ) {
903 				if ( !this.sorted ) {
904 					this.childNodes.sort ( function ( g1, g2 ) {
905 						return g1.z - g2.z;
906 					});
907 					this.sorted = true;
908 				}
909 				for (var i in this.childNodes) {
910 					this.childNodes[ i ].draw();
911 				}
912 			}
913 		},
914 		isPointInPath : function() {
915 			//TODO 
916 		}
917 		
918 	}
919 );
920 
921 /**
922 Graphics basic Media object.
923 @name KMedia
924 @class General methods for any Graphic object
925 @param {String} file  The name of the file that must be loaded
926 @param {String} type 'image', 'sound' or 'video'
927 @param {Object} [options] Options that will be passed to the media element
928 	constructor  
929 @augments KObject
930 @memberOf Karma 
931 **/
932 var KMedia = Class(
933 	KObject,
934 	{
935 		init: function (file, type, options ) {
936 			if ( !file || !type ) {
937 				throw new Error ("file and type needed");
938 			}
939 			if ( valid ( options ) ) 
940 				KObject.init.call (this, options);
941 				
942 			this.file = file;
943 			this.type = type;
944 			
945 			this.status = undefined;
946 			this.path = undefined;
947 			this.media = undefined;
948 			switch ( this.type ) {
949 				case "image": this.media = new Image(); break;
950 				case "sound": this.media = new Audio(); break;
951 				default: throw new Error ("Media type not supported"); 
952 			}
953 			this.path = gk.paths[ this.type + "s" ][ 
954 				this.localized ? "localized": "generic" 
955 			];
956 			this.media.src = this.src = this.path + this.file;
957 
958 			var that = this;
959 			this.media.addEventListener("load", 
960 			function (e) { that.status = "loaded";}, false);
961 			this.media.addEventListener("error", 
962 			function (e) { that.status = "error";}, false);
963 			this.media.addEventListener("abort", 
964 			function (e) { that.status = "aborted";}, false);
965 		}
966 	}
967 );
968 
969 /**
970 Image object
971 @name KImage
972 @class General methods for any Image object
973 @param {Object} options Constructor arguments.
974 @param {Object} options.file The image file that will be loaded.
975 @augments KGraphic
976 @augments KMedia
977 @memberOf Karma 
978 **/
979 var KImage = Class(
980 	KGraphic,
981 	KMedia,
982 	{
983 		init: function ( options ) {
984 			if ( valid ( options, "string" ) ) {
985 				options = { file:options };
986 			}
987 			if ( valid( options ) ) {
988 				KGraphic.init.call(this, options);
989 				KMedia.init.call(this, options.file, "image", options );
990 			}
991 			var defaultOptions = {
992 				//w : undefined,
993 				//h : undefined,
994 			};
995 			$.extend( this, defaultOptions, options);
996 		},
997 		draw : function( ctx, x, y ) {
998 			if ( this.visible && this.isReady() ) {
999 				this.x = x || this.x;
1000 				this.y = y || this.y;
1001 				ctx.drawImage( this.media, this.x , this.y );
1002 			}
1003 		},
1004 		/**
1005 		Checks if the image has been loaded and fully decoded.
1006 		@returns {boolean} 'true' or 'false' 
1007 		**/
1008 		isReady : function () {
1009 			if ( !this.media.complete ) return false;
1010 			if ( !this.media.naturalWidth || this.media.naturalWidth === 0) 
1011 				return false;
1012 			return true;
1013 		}
1014 	}
1015 );
1016 
1017 /**
1018 Sound object
1019 @name KSound
1020 @class General methods for any Sound object
1021 @param {Object} options Constructor arguments.
1022 @param {Object} options.file The image file that will be loaded.
1023 @augments KMedia
1024 @memberOf Karma 
1025 **/
1026 var KSound = Class(
1027 	/**@lends_ KMedia*/
1028 	KMedia,
1029 	{
1030 		init: function( options ) {
1031 			if ( valid ( options, "string" ) ) {
1032 				options = { file: options };
1033 			}
1034 			if ( valid( options ) ) {
1035 				KMedia.init.call(this, options.file, "sound", options );
1036 				//next line is important!
1037 				this.media.load();
1038 			}
1039 		},
1040 		/**
1041 		Checks if the image has been loaded and fully decoded.
1042 		@returns {boolean} 'true' or 'false' 
1043 		**/
1044 		isReady: function () {
1045 			return this.readyState === 4;
1046 		},
1047 		play: function (){
1048 			//hack to fix the audio "stuttering" problem
1049 			//more info: https://bugs.launchpad.net/karma/+bug/426108
1050 			this.media.currentTime = 0.1;
1051 			this.media.play();
1052 		}
1053 	}
1054 );
1055 
1056 /**
1057 Shape object
1058 @name KShape
1059 @class General methods for any Shape object
1060 @param {object} options Constructor arguments.
1061 @param {boolean} [options.fill=true] 'true' if the Shape will be filled when 
1062 	drawing.
1063 @param {boolean} [options.stroke=true] 'true' if the stroke will be drawn.
1064 @param {color|string} [options.fillStyle="#000"] The fill style of the shape.
1065 @param {color|string} [options.strokeStyle="#000"] The stroke style of the shape.
1066 @augments KMedia
1067 @memberOf Karma 
1068 **/
1069 var KShape = Class(
1070 	/**@lends_ KGraphic*/
1071 	KGraphic,
1072 	{
1073 		init : function ( options ) {
1074 			if ( valid( options ) ) {
1075 				KGraphic.init.call(this, options );
1076 			}
1077 			var defaultOptions = {
1078 				fill:	true,
1079 				stroke: true,
1080 				fillStyle: '#000',
1081 				strokeStyle: '#000',
1082 				openPath : false
1083 			};
1084 			$.extend( this, defaultOptions, options);
1085 		},
1086 		draw : function ( ctx ) {
1087 			if ( this.visible ) {
1088 				ctx.fillStyle = this.fillStyle
1089 				ctx.strokeStyle= this.strokeStyle
1090 					if ( this.fill )
1091 						ctx.fill();
1092 					if ( this.stroke )
1093 						ctx.stroke();
1094 					if ( !this.openPath )
1095 						ctx.closePath();
1096 				ctx.restore();
1097 			}
1098 		}
1099 	}
1100 );
1101 /**
1102 Rectangle object
1103 @name KRectangle
1104 @class General methods for a rectangle object
1105 @param {object} options Constructor arguments.
1106 @param {number} options.x The 'x' position.
1107 @param {number} options.y The 'y' position.
1108 @param {number} options.w The width of the rectangle. 
1109 @param {number} options.h The height of the rectangle.
1110 @augments KShape
1111 @memberOf Karma 
1112 **/
1113 var KRectangle = Class(
1114 	KShape,
1115 	{
1116 		init : function ( options ) {
1117 			//ADD multiple constructors support
1118 			//x,y,w,h
1119 			//w,y,w,h,options
1120 			if ( valid( options ) ) {
1121 				KShape.init.call(this, options );
1122 			}
1123 		},
1124 		draw : function ( ctx ) {
1125 			if ( this.visible ) {
1126 				ctx.save();
1127 				ctx.beginPath();
1128 				ctx.rect( this.x, this.y, this.width, this.height);
1129 				KShape.draw.call( this, ctx );
1130 			}
1131 		},
1132 	    clear : function ( ) {
1133 			if ( this.visible ) {
1134 				
1135 			}
1136 	    }	
1137 	}
1138 	
1139 );
1140 
1141 /**@class_ */
1142 var KButton = Class(
1143 	/**@lends_ KGraphic*/
1144 	KGraphic,
1145 	{
1146 		
1147 		init : function ( options ) {
1148 			//ADD multiple constructors support
1149 			//x,y,w,h
1150 			//w,y,w,h,options
1151 			if ( valid( options ) ) {
1152 				KGraphic.init.call(this, options );
1153 			}
1154 			this.name = options.name;
1155 			master.buttons.push(this);
1156 		},
1157 		draw : function ( ) {},
1158 		onClick : function() { } //callback
1159 	}
1160 );
1161 //
1162 /**
1163 Karma function. It's a shotcut for calling 'new Karma(..)'
1164 @param [options] Options passed to the Karma constructor
1165 @returns {Object} a new Karma object
1166 @see Karma
1167 **/
1168 $.karma = function (options) {
1169 	var k =new Karma( options );
1170 	return k;
1171 }
1172 })(jQuery);