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  * @class
 42  * @name jQuery
 43  * @exports $ as jQuery
 44 */
 45 
 46 (function ($) {
 47 //helpers
 48 /**
 49 Checks if the argument 'arg' is set and if its type is 'type'.<br>
 50 1. if arg is set: it returns 'toReturn' if specified, otherwise it returns 
 51 	'true'
 52 2. if arg is not set: it returns 'false'
 53 @param arg The param to check
 54 @param {Object} [type] The expeted type of 'arg'
 55 @param [toReturn] object or value to return in case 1
 56 @returns true | false | toReturn
 57 @example
 58 var msg = "hi";
 59 valid(msg); //returns true
 60 valid(msg, "String" ); //returns true
 61 valid(msg, "Number"); //returns false
 62 valid(msg, "String",false ); //returns false
 63 valid(msg, "String", "hello" ); //returns "hello"
 64 valid(msg123); //returns false
 65 **/
 66 var valid = function ( arg, type, toReturn ) {
 67 	if ( type ) {
 68 		if ( typeof arg === type ) {
 69 			if ( toReturn )
 70 				return toReturn;
 71 			return true;
 72 		}
 73 		return false
 74 	}
 75 	if ( typeof arg !== "undefined" && arg!== "null" ) return true;
 76 	return false;
 77 }
 78 /**
 79 Clones an object
 80 @param {object} obj The source object
 81 @returns {object} The cloned object
 82 **/
 83 var clone = function( obj ){
 84     if(obj == null || typeof(obj) != 'object')
 85         return obj;
 86     var temp = new obj.constructor(); 
 87     for(var key in obj)
 88         temp[ key ] = clone( obj[ key ] );
 89     return temp;
 90 }
 91 
 92 /**
 93 Karma
 94 @name Karma
 95 @class Represents a Karma (master) object.
 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 						that.i18n.root[ that.i18n.shortcut ] =  i18nWrapper(
241 							{ 
242 								domain 	: lang, 
243 								file 	: { 
244 										  type: that.supportedLangFileTypes[i].type, 
245 										  uri: this.url, data: data 
246 										} 
247 							}
248 						);
249 						localiseContent( lang );
250 						tryNext = false;
251 					},
252 					error: function ( XHR, textStatus, errorThrown ) {
253 						//the file doesn't exist or it wasn't possible to load it
254 						tryNext = true;
255 					}
256 				});
257 				return tryNext;
258 			}
259 		});
260 		return loaded;
261 	}
262 	//PRIVATE STUFF end
263 	// default options 
264 	var defaultOptions ={
265 		container:   "#karma-main",
266 		language:   { 
267 						lang: 			undefined,
268 						alternatives: 	['en-US', 'en'],
269 						countryCode: 	undefined,
270 						langCode:		undefined,
271 					},
272 		i18n:		{
273 						root: 			self, // self is global
274 						shortcut: 		"_"
275 					}
276 	};
277 	//
278 	this.library = { "images": [], "sounds": [], "videos":[], "shapes":[] };
279 	
280 	//initializes the defaultOptions argument
281 	//1 argument: string.  assume it's the container
282 	if ( typeof options === "string" ) {
283 		options = { container: options };
284 		options.language = getLanguage() ;
285 	} else if (typeof options === "object" ){
286 		if ( typeof options.lang === "string" ) {
287 			//if language is string, assume  it's the language.lang
288 			options.language = { lang: options.lang };
289 		}
290 	}
291 	$.extend( true, defaultOptions, options );
292 	//
293 	//copy defaultOptions to this, we use this.xyz instead this.defaultOptions.xyz 
294 	for (var i in defaultOptions ) {
295 		this[ i ] = defaultOptions[i];
296 	}
297 	
298 	//initializes i18n
299 	//add the localized language to the language.alternatives
300 	if ( typeof this.language.countryCode !== "undefined" ) {
301 		this.language.alternatives.unshift( 
302 			this.language.langCode, 
303 			this.language.countryCode 
304 		);
305 	}
306 	if ( typeof this.language.lang !== "undefined" ) {
307 		this.language.alternatives.unshift( this.language.lang );
308 	}
309 	//try to load the localized lang file (po or json or ...)
310 	this.language.fileLoaded = loadAlternatives( );
311 	//initializes the container
312 	if ( typeof this.container === "string" ) {
313 		this.container = $( this.container )[ 0 ];
314 		if ( !valid(this.container) ) delete this.container;
315 	}
316 	
317 	gk = {
318 		"paths": this.paths
319 	}
320 	this.surfaces = {};
321 }
322 
323 /**
324 @memberOf Karma 
325 @namespace Geometry functions.
326 **/
327 Karma.prototype.geometry = {
328 	/**
329 	Converts a value from degrees to radians.
330 	@param {Number} angle The angle in degrees 
331 	@returns {Number} The The angle in radians 
332 	**/
333 	radians : function( angle ){
334 		return ( angle / 180 ) * Math.PI;
335 	},
336 	/**
337 	Gets the square of the Euclidian (ordinary) distance between 2 points.
338 	@param {Number} Point Point No. 0 
339 	@param {Number} Point Point No. 1
340 	@returns {Number} The square of the Euclidian distance 
341 	**/
342 	distance2 : function ( p0, p1 ) {
343 		return   (p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p1.y) * (p1.y - p1.y); 
344 	},
345 	/**
346 	Gets the Euclidian (ordinary) distance between 2 points.<br>
347 	<b>Warning:</b> It's slower than distance2 function
348 	@param {Number} Point Point No. 0 
349 	@param {Number} Point Point No. 1
350 	@returns {Number} The Euclidian distance 
351 	**/
352 	distance : function ( p0, p1 ) {
353 		return   Math.sqrt( Karma.prototype.geometry.distance2( p0, p1 ) ); 
354 	}
355 }
356 /**
357 @memberOf Karma
358 @namespace Graphics functions.
359 **/
360 Karma.prototype.graphics = {
361 	/**
362 	Creates a new rectangle. It's a shortcut for calling 'new KRectangle(..)'.
363 	**/
364 	rectangle: function ( args ) { return new KRectangle( args ); },
365 	/**
366 	Creates a new circle. It's a shortcut for calling 'new KCircle(..)'.
367 	**/
368 	circle: function ( args ) { return new KCircle( args ); }
369 }
370 /**
371 @memberOf Karma 
372 @namespace Math functions.
373 **/
374 Karma.prototype.math = {
375 	/**
376 	Generates a random bumber between lower bound and upper bound inclusive.
377 	@param {Number} lower The lower bound
378 	@param {Number} upper The upper bound
379 	@returns {Number} The generated number
380 	**/
381 	rand : function ( lower, upper ){
382 		return Math.round ( Math.random() * (upper - lower) + lower );
383 	}
384 }
385 //FIXME
386 //everything inside karma.graphics is exported to karma.prototype
387 $.extend( Karma.prototype, Karma.prototype.graphics);
388 //
389 /**
390 @param {Object} [toLoad] The Object that has the arrays for preloading.
391 @param {Array} [toLoad.images] The images 
392 @param {Array} [toLoad.sounds] The sounds 
393 @param {Array} [toLoad.videos] The videos 
394 @memberOf Karma 
395 @returns {Object} this
396 **/
397 Karma.prototype.init = function( toLoad ) {
398 	this.pendingToLoad = toLoad;
399 	return this; //chaining :)
400 }
401 
402 /**
403 Main function. Any Karma function call should be inside the callback function.
404 The callback function will be executed when the preloading finishes.
405 @param {Function} cb The callback funtion
406 @memberOf Karma 
407 @see Karma#init
408 **/
409 Karma.prototype.main = function ( cb ) {
410 	if ( valid( this.pendingToLoad ) ) {
411 		//loader
412 		var loaderDiv = $("body").append('<div id=\"karma-loader\">Karma is \
413 		loading ...<div id=\"karma-loader\" class=\"status\"></div></div>');
414 		var statusDiv = $("#karma-loader .status");
415 		
416 		var statusUpdate = function ( current, error, total) {
417 			statusDiv.html(current + "/" + total + (error > 0 ? " [ "+error+" ]":''));
418 		}
419 		
420 		var that = this;
421 		var categories = ["images", "sounds", "videos" ];
422 		var counters = { "loaded":0, "error": 0 };
423 		var totalItems = 0;
424 		//creates the surfaces
425 		if ( valid( this.pendingToLoad[ "surfaces" ] ) ) {
426 			$.each (this.pendingToLoad[ "surfaces" ], function( key, config ){
427 				Karma.prototype.surface.call( that, config );
428 			});
429 		}
430 		statusUpdate( 0, 0, totalItems);
431 		//get the total items
432 		for ( var i=0; i < categories.length; i++ ) {
433 			if ( valid ( this.pendingToLoad[ categories[ i ] ] ) ) {
434 				totalItems += this.pendingToLoad[ categories[ i ] ].length;
435 			}
436 		}
437 		
438 		/**
439 		callback to check if all the items were loaded or got an error when 
440 		loading
441 		**/
442 		var errors=[];
443 		var checkAllLoaded = function ( ev ) {
444 			if ( ev.type === "load") counters.loaded += 1;
445 			else {
446 				errors.push( ev.target.src );
447 				counters.error += 1; 
448 			}
449 			statusUpdate( counters.loaded, counters.error, totalItems);
450 			if ( counters.loaded + counters.error === totalItems ) {
451 				if ( counters.error > 0 ){
452 					throw ( "Media files not found: " + errors );
453 				}
454 				$("#karma-loader:hiden:first").fadeOut("slow",function(){ 
455 					$(this).remove();});
456 				if ( cb ) cb();
457 			}
458 		}
459 		
460 		for ( var i=0; i < categories.length; i++ ) {
461 			var category = categories[ i ];
462 		    if ( valid ( this.pendingToLoad[ category ] ) ) {
463 				//load all the category elements
464 				var type = category.substr( 0, category.length-1 )
465 				$.each (this.pendingToLoad[ category ], function( key, config ){
466 					var name = config.name;
467 					delete config.name;
468 					//register the elements into the library
469 					that.library[ category ][ name ] =  Karma.prototype[ type ]( 
470 						config
471 					);
472 					that.library[ category ][ name ].media.addEventListener(
473 						"load",checkAllLoaded,false
474 					);
475 					that.library[ category ][ name ].media.addEventListener(
476 						"error",checkAllLoaded,false
477 					);
478 				});
479 			}
480 		}
481 	}else {
482 		if ( cb ) cb();
483 	}
484 }
485 /**
486 A shortcut for calling 'KImage( )'
487 @see KImage
488 @memberOf Karma 
489 @returns {Object} new instance of KImage object
490 **/
491 Karma.prototype.image = function ( args ) { return new KImage( args ) };
492 /**
493 A shortcut for calling 'KSound( )'
494 @see KSound
495 @memberOf Karma 
496 @returns {Object} new instance of KSound object
497 **/
498 Karma.prototype.sound = function ( args ) { return new KSound( args ) };
499 /**
500 A shortcut for calling 'KVideo( )'
501 @see KVideo
502 @memberOf Karma 
503 @returns {Object} new instance of KVideo object
504 **/
505 Karma.prototype.video = function ( args ) { alert("Not implemented yet"); };
506 /**
507 A shortcut for calling 'KGroup( )'
508 @see KGroup
509 @memberOf Karma 
510 @returns {Object} new instance of KGroup object
511 **/
512 Karma.prototype.group = function ( args ) { return new KGroup( args ) };
513 /**
514 A shortcut for calling 'KButton( )'
515 @see KButton
516 @memberOf Karma 
517 @returns {Object} new instance of KButton object
518 **/
519 Karma.prototype.button = function ( args ) { return new KButton( args ) };
520 /**
521 A shortcut for calling 'KSurface(.. )'.  
522 @see KSurface
523 @memberOf Karma 
524 @returns {Object} new instance of KSurface object 
525 **/
526 Karma.prototype.surface = function ( options ) {
527 	if ( !valid(options, "object") ){
528 		var options = { name: "ksurface-"+ ( this.surfaces.length + 1 ) };
529 	}
530 	options.mainContainer = this.container;
531 	options.paths = this.paths;
532 	this.surfaces[ options.name ] = new KSurface( options ); 
533 	return this.surface[ options.name ];
534 }
535 
536 /**
537 Mouse
538 **/
539 var mouse = {};
540 /**
541 Gets the 'x' and 'y' mouse coordinates relatives to the canvas  
542 @returns {Object} An Object with 'x' and 'y' attributes
543 **/
544 mouse.getRelativeCanvasPosition = function ( ev ) {
545 	if ( !ev ) return;
546 	var xy ={x:0, y:0};
547 	xy.x = ev.layerX;
548     xy.y = ev.layerY;
549 	return xy;
550 }
551 
552 //Events stuff
553 var master ={}
554 master.buttons =[];
555 var handleEvents = function( ev ) {
556 	var xy = mouse.getRelativeCanvasPosition( ev  );
557 	for (var i in master.buttons) {
558 		if (master.buttons[i].isPointInPath( xy.x, xy.y) ){
559 			master.buttons[i].onClick( ev );
560 		}
561 	}
562 	/*switch(ev.type){
563 		case "click": break;
564 	}*/
565 	/*var s="";
566 	for (var i in ev) {
567 		s+=i+"="+ev[i]+"\n";
568 	}
569 	alert(s);*/
570 };
571 
572 /**
573 Master class creator. It will merge all the properties and methods of the 
574 recived arguments (objects) into one new class that wil be returned.
575 @returns {Object} The new class 
576 **/
577 var Class = function ( ) {
578 	var log="";
579 	var parents = [];
580 	for ( var i = 0; i < arguments.length; i++ ) {
581 		if ( arguments[i].prototype && arguments[i].init ) {
582 			parents.push( arguments[i].init );
583 		}
584 	}
585 	var o = function ( ) {
586 		//we inject all the init functions 
587 		/*for ( var i = 0; i < this.__parents.length; i++ ) {
588 			this.__parents[ i ].apply ( this, arguments );
589 		}*/
590 		//call the real  class init
591 		if ( this.init )
592 			this.init.apply( this, arguments );
593 	};
594 
595 	o.prototype ={};
596 	var a;
597 	for ( var i =0; i < arguments.length; i++) {
598 		a = arguments[i];
599 		log += "**" + typeof a+"\n";
600 		//if ( a === "function") {
601 		if (a.prototype) {
602 			for ( var j in a.prototype ) {
603 				//log += j+" = "+a.prototype[j]+"\n";
604 				o[ j ] = o.prototype[ j ] = a.prototype [ j ];
605 			}
606 		}
607 		else {
608 		//if ( typeof a === "object") {
609 			for (var j in a) {
610 				//log += j+" = "+a[j]+"\n";
611 				o[ j ] = o.prototype[ j ] = a [ j ];
612 			}
613 		}
614 		
615 	}
616 	o.prototype.__parents = parents;
617 	//alert( log );
618 	return  o; //(function ( ) { return new o( arguments );});
619 };
620 
621 /**
622 Creates a new surface. A surface is a 'canvas' element with additional methods
623 that makes easier its manipulation. <br>
624 There are 2 ways to create a new KSurface:
625 <ol>
626 	<li><b>Using an existing canvas element:</b>You must provide at least 
627 		the 'canvas' parameter. The 'name' is optional (if it's not provided the
628 		'canvas' parameter will be used).
629 	</li>
630 	<li><b>Creating a new canvas element:</b> A new 'canvas' element will be
631 		created and it will be appended to the specific 'container'. 
632 		You must provide at least the 'name' and 'container' parameters.
633 	</li>
634 </ol>
635 @class KSurface class
636 @param {object} options Constructor options.
637 @param {string} [options.name]  The desired name for the surface. The value must
638 	be unique among others KSurfaces-name objects.
639 @param {string} [options.canvas]  The name of the element. Commonly the 
640 	canvas-id value.
641 @param {string | object} [options.container] The the name of the container 
642 	element. Commonly a div-id value.
643 @param {number} [width=100] The width of the canvas.
644 @param {number} [height=100] The height of the canvas.
645 @param {number} [fps=24] The frames per second for any refresh operation.
646 @param {boolean} [visible=true] 'true' if the content is visible (will be drawn).
647 @memberOf_ Karma 
648 **/
649 var KSurface = Class(
650 	{
651 		init: function( options ){
652 			//fix the container
653 			if ( valid( options.container, "string" ) && !valid( options.canvas)
654 			) {
655 				var name=options.container;
656 				options.container = $( options.container )[ 0 ];
657 				if ( !valid (options.container) ){
658 					// the container must be created inside the mainContainer
659 					if ( !valid( options.mainContainer ) ){
660 						throw ("You need to create the Karma master container");
661 					}
662 					var div = document.createElement("div");
663 					div.id = name;
664 					options.container=options.mainContainer.appendChild( div );
665 				} 
666 			}else {
667 				if ( !valid( options.mainContainer ) ){
668 					throw ("You need to create the Karma master container");
669 				}
670 				options.container = options.mainContainer;
671 			}
672 			
673 			var defaultOptions = {
674 				//mainContainer: '',//must be overwritten by Karma.container
675 				name: '',//must be overwritten by the Karma.surface OR user
676 				container: '', //must be overwritten by Karma.container OR user
677 				
678 				width: 100,
679 				height: 100,
680 				fps: 24,
681 				visible: true
682 			}
683 			$.extend( this, defaultOptions, options);
684 			
685 			if ( !this.canvas ) {
686 				this.canvas = document.createElement("canvas");
687 				this.canvas.width  = this.width; 
688 				this.canvas.height = this.height;
689 				this.canvas.id = this.name;
690 				this.container.appendChild( this.canvas );
691 			}else {
692 			    this.canvas = document.getElementById( options.canvas );
693 			    if ( !this.canvas ){
694 					throw new Error ("The canvas id doesn't exist");
695 			    }
696 			    this.width = this.canvas.width;
697 			    this.height = this.canvas.height;
698 			    if (!this.name){
699 					this.name = this.canvas.id;
700 			    }
701 			}
702 			if ( this.canvas.getContext ) {
703 				this.ctx = this.canvas.getContext("2d");
704 			}else {
705 				throw new Error ("Your browser doesn't support canvas, \
706 				try the newest Firefox, Safari or Google Chrome");
707 			}
708 			//ctx methods chaining stuff
709 			var toChain = [
710 			"globalAlpha", "globalCompositeOperation", "lineWidth", "lineCap", 
711 			"lineJoin", "miterLimit", "font", "textAlign", "textBaseline", "save", 
712 			"restore", "scale", "rotate", "translate", "transform", "setTransform", 
713 			"clearRect", "fillRect", "strokeRect", "beginPath", "closePath", 
714 			"moveTo", "lineTo", "quadraticCurveTo", "bezierCurveTo", "arcTo", 
715 			"arc", "rect", "fill", "stroke", "clip", "fillText", "strokeText", 
716 			"measureText", "isPointInPath", "strokeStyle", "fillStyle", 
717 			"createLinearGradient", "createRadialGradient", "createPattern", 
718 			"shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", 
719 			//"mozTextStyle", "mozDrawText", "mozMeasureText", "mozPathText", 
720 			"mozTextAlongPath", "drawImage", "getImageData", "putImageData", 
721 			"createImageData", "drawWindow"
722 			];
723 			var that=this;
724 			var chainMaker = function ( name ){
725 				that[ name ] = function ( ){
726 					var type = typeof that.ctx[name];
727 					if ( type === "function") {
728 						that.ctx[ name ].apply( that.ctx, arguments );
729 					}else if ( type === "string" ){
730 						that.ctx[ name ] = arguments[0];
731 					}else {
732 						throw ("wtf?!: impossible to chain " + name + "!");
733 					}
734 					return that;
735 				}
736 			}
737 			for (var i=0; i<toChain.length; i++){
738 				chainMaker(  toChain[ i ] );
739 			}
740 
741 			
742 			//events
743 			this.canvas.addEventListener("contextmenu", function(ev){
744 				//
745 				},false
746 			);
747 			this.canvas.addEventListener("click", 
748 				handleEvents,
749 				false
750 			);
751 			
752 		},
753 		/**
754 		Adds an event listener to the surface
755 		@param {string} type Event type
756 		@param {function} cb Function call back
757 		@param {boolean} [bubble=false] If the event must be captured on
758 			bubbling phase
759 		**/
760 		addEventListener : function ( type, cb, bubble ) {
761 			this.canvas.addEventListener( type, cb, bubble || false );
762 		},
763 		/**
764 		Removes an event listener attached to the surface
765 		@param {string} type Event type
766 		@param {function} cb Function call back
767 		@param {boolean} [bubble=false] If the event must be captured on
768 			bubbling phase
769 		**/
770 		removeEventListener : function ( type, cb, bubble ) {
771 			this.canvas.removeEventListener( type, cb, bubble || false );
772 		},
773 		/**
774 		Clears a rectangular area within the canvas
775 		@param {Number} [x=0] Start position of x
776 		@param {Number} [y=0] Start position of y
777 		@param {Number} [width=canvas width] Square width
778 		@param {Number} [height=canvas height] Square height
779 		**/
780 		clear : function ( x, y, width, height ) {
781 			this.ctx.clearRect(
782 				x || 0,
783 				y || 0, 
784 				width  || this.width, 
785 				height || this.height
786 			);
787 		    return this;
788 		},
789 		draw: function (  ) {
790 			
791 		}
792 	}
793 );
794 
795 /**
796 Karma basic Object 
797 @class The basic Karma object
798 @param {Object} [options] Options 
799 @param {String} [options.localized = true] The object will be localized
800 @memberOf_ Karma 
801 **/
802 var KObject = Class(
803 	{
804 		init: function ( options ) {
805 			if ( valid(options.localized, "boolean" ) ) {
806 				this.localized = options.localized;
807 			}else {
808 				this.localized = true;
809 			}
810 		}
811 	}
812 );
813 /**
814 Graphics basic Object
815 @class General methods for any Graphic object
816 @param {object} [options] Options 
817 @param {number} [options.x = 0] The 'x' position of the object
818 @param {number} [options.y = 0] The 'y' position of the object
819 @param {number} [options.z = 0] The 'z' index of the object
820 @param {number} [options.width = 0] The 'width' of the object
821 @param {number} [options.height = 0] The 'height' of the object
822 @param {boolean} [options.visible = true] Defines if the object will be visible 
823 	when drawing
824 @augments KObject
825 @memberOf_ Karma 
826 **/
827 var KGraphic = Class(
828 	KObject,
829 	{
830 		init: function ( options ) {
831 			if ( valid( options.localized ) ) 
832 				KObject.init.call(this, options.localized );
833 			var defaultOptions = {
834 				x : 0,
835 				y : 0,
836 				z : 0,
837 				width: 0,
838 				height: 0,
839 				visible : true
840 			}
841 			$.extend( this, defaultOptions, options);
842 		},
843 		/**
844 		@memberOf KGraphic
845 		Determines if the 'x' and 'y' coodinates are inside the object.
846 		@returns {boolean} 'true' if the coordinates are inside or on the border
847 			of the object, otherwise 'false'
848 		**/
849 		isPointInPath : function( x, y ) {
850 			return (this.x <= x &&  (this.x + this.width) >= x && 
851 					this.y <= y &&  (this.y+this.width)>=y); 
852 		},
853 		addEventListener : function (type, cb, bubble) {
854 			//FIXME
855 		}
856 	}
857 );
858 /**
859 An object that collects multiple KGraphic objects. Supports multiple objects.
860 @class An object that collects multiple KGraphic objects 
861 @augments KGraphic
862 @memberOf_ Karma 
863 **/
864 var KGroup = Class(
865 	KGraphic,
866 	{
867 		init: function ( options ) {
868 			this.childNodes = [];
869 			this.sorted = true;
870 		},
871 		/**
872 		@memberOf KGroup
873 		Adds each argument passed to the funtion to chilNodes.
874 		@param {Array:KGraphic} arguments The elements to add to childNodes		
875 		@see KGroup#draw
876 		**/
877 		appendChild : function (  ) {
878 			if ( arguments.length > 0 ) {
879 				for ( var i = 0; i< arguments.length; i++) {
880 					this.childNodes.push ( arguments[ i ] );
881 				}
882 				this.sorted = false;
883 				
884 			}
885 		},
886 		removeChild: function () {
887 			//FIXME
888 		},
889 		/**
890 		@memberOf_ KGroup
891 		Draws all the elements in childNodes. The elements are drawn according
892 		to its 'z' (z-index) value.
893 		@see KGroup#appendChild
894 		**/
895 		draw : function() {
896 			if ( this.visible && this.childNodes.length > 0 ) {
897 				if ( !this.sorted ) {
898 					this.childNodes.sort ( function ( g1, g2 ) {
899 						return g1.z - g2.z;
900 					});
901 					this.sorted = true;
902 				}
903 				for (var i in this.childNodes) {
904 					this.childNodes[ i ].draw();
905 				}
906 			}
907 		},
908 		isPointInPath : function() {
909 			//TODO 
910 		}
911 		
912 	}
913 );
914 
915 /**
916 Graphics basic Media object.
917 @class General methods for any Graphic object
918 @param {String} file  The name of the file that must be loaded
919 @param {String} type 'image', 'sound' or 'video'
920 @param {Object} [options] Options that will be passed to the media element
921 	constructor  
922 @augments KObject
923 @memberOf_ Karma 
924 **/
925 var KMedia = Class(
926 	KObject,
927 	{
928 		init: function (file, type, options ) {
929 			if ( !file || !type ) {
930 				throw new Error ("file and type needed");
931 			}
932 			if ( valid ( options ) ) 
933 				KObject.init.call (this, options);
934 				
935 			this.file = file;
936 			this.type = type;
937 			
938 			this.status = undefined;
939 			this.path = undefined;
940 			this.media = undefined;
941 			switch ( this.type ) {
942 				case "image": this.media = new Image(); break;
943 				case "sound": this.media = new Audio(); break;
944 				default: throw new Error ("Media type not supported"); 
945 			}
946 			this.path = gk.paths[ this.type + "s" ][ 
947 				this.localized ? "localized": "generic" 
948 			];
949 			this.media.src = this.src = this.path + this.file;
950 
951 			var that = this;
952 			this.media.addEventListener("load", 
953 			function (e) { that.status = "loaded";}, false);
954 			this.media.addEventListener("error", 
955 			function (e) { that.status = "error";}, false);
956 			this.media.addEventListener("abort", 
957 			function (e) { that.status = "aborted";}, false);
958 		}
959 	}
960 );
961 
962 /**
963 Image object
964 @class General methods for any Image object
965 @param {Object} options Constructor arguments.
966 @param {Object} options.file The image file that will be loaded.
967 @augments KGraphic
968 @augments KMedia
969 @memberOf_ Karma 
970 **/
971 var KImage = Class(
972 	KGraphic,
973 	KMedia,
974 	{
975 		init: function ( options ) {
976 			if ( valid ( options, "string" ) ) {
977 				options = { file:options };
978 			}
979 			if ( valid( options ) ) {
980 				KGraphic.init.call(this, options);
981 				KMedia.init.call(this, options.file, "image", options );
982 			}
983 			var defaultOptions = {
984 				//w : undefined,
985 				//h : undefined,
986 			};
987 			$.extend( this, defaultOptions, options);
988 		},
989 		draw : function( ctx, x, y ) {
990 			if ( this.visible && this.isReady() ) {
991 				this.x = x || this.x;
992 				this.y = y || this.y;
993 				ctx.drawImage( this.media, this.x , this.y );
994 			}
995 		},
996 		/**
997 		Checks if the image has been loaded and fully decoded.
998 		@returns {boolean} 'true' or 'false' 
999 		**/
1000 		isReady : function () {
1001 			if ( !this.media.complete ) return false;
1002 			if ( !this.media.naturalWidth || this.media.naturalWidth === 0) 
1003 				return false;
1004 			return true;
1005 		}
1006 	}
1007 );
1008 
1009 /**
1010 Sound object
1011 @class General methods for any Sound object
1012 @param {Object} options Constructor arguments.
1013 @param {Object} options.file The image file that will be loaded.
1014 @augments KMedia
1015 @memberOf_ Karma 
1016 **/
1017 var KSound = Class(
1018 	/**@lends_ KMedia*/
1019 	KMedia,
1020 	{
1021 		init: function( options ) {
1022 			if ( valid ( options, "string" ) ) {
1023 				options = { file: options };
1024 			}
1025 			if ( valid( options ) ) {
1026 				KMedia.init.call(this, options.file, "sound", options );
1027 				//next line is important!
1028 				this.media.load();
1029 			}
1030 		},
1031 		/**
1032 		Checks if the image has been loaded and fully decoded.
1033 		@returns {boolean} 'true' or 'false' 
1034 		**/
1035 		isReady: function () {
1036 			return this.readyState === 4;
1037 		},
1038 		play: function (){
1039 			//hack to fix the audio "stuttering" problem
1040 			//more info: https://bugs.launchpad.net/karma/+bug/426108
1041 			this.media.currentTime = 0.1;
1042 			this.media.play();
1043 		}
1044 	}
1045 );
1046 
1047 /**
1048 Shape object
1049 @class General methods for any Shape object
1050 @param {object} options Constructor arguments.
1051 @param {boolean} [options.fill=true] 'true' if the Shape will be filled when 
1052 	drawing.
1053 @param {boolean} [options.stroke=true] 'true' if the stroke will be drawn.
1054 @param {color|string} [options.fillStyle="#000"] The fill style of the shape.
1055 @param {color|string} [options.strokeStyle="#000"] The stroke style of the shape.
1056 @augments KMedia
1057 @memberOf_ Karma 
1058 **/
1059 var KShape = Class(
1060 	/**@lends_ KGraphic*/
1061 	KGraphic,
1062 	{
1063 		init : function ( options ) {
1064 			if ( valid( options ) ) {
1065 				KGraphic.init.call(this, options );
1066 			}
1067 			var defaultOptions = {
1068 				fill:	true,
1069 				stroke: true,
1070 				fillStyle: '#000',
1071 				strokeStyle: '#000',
1072 				openPath : false
1073 			}
1074 			$.extend( this, defaultOptions, options);
1075 		},
1076 		draw : function ( ctx ) {
1077 			if ( this.visible ) {
1078 				ctx.fillStyle = this.fillStyle
1079 				ctx.strokeStyle= this.strokeStyle
1080 					if ( this.fill )
1081 						ctx.fill();
1082 					if ( this.stroke )
1083 						ctx.stroke();
1084 					if ( !this.openPath )
1085 						ctx.closePath();
1086 				ctx.restore();
1087 			}
1088 		}
1089 	}
1090 );
1091 /**
1092 Rectangle object
1093 @class General methods for a rectangle object
1094 @param {object} options Constructor arguments.
1095 @param {number} options.x The 'x' position.
1096 @param {number} options.y The 'y' position.
1097 @param {number} options.w The width of the rectangle. 
1098 @param {number} options.h The height of the rectangle.
1099 @augments KShape
1100 @memberOf_ Karma 
1101 **/
1102 var KRectangle = Class(
1103 	KShape,
1104 	{
1105 		init : function ( options ) {
1106 			//ADD multiple constructors support
1107 			//x,y,w,h
1108 			//w,y,w,h,options
1109 			if ( valid( options ) ) {
1110 				KShape.init.call(this, options );
1111 			}
1112 		},
1113 		draw : function ( ctx ) {
1114 			if ( this.visible ) {
1115 				ctx.save();
1116 				ctx.beginPath();
1117 				ctx.rect( this.x, this.y, this.width, this.height);
1118 				KShape.draw.call( this, ctx );
1119 			}
1120 		},
1121 	    clear : function ( ) {
1122 			if ( this.visible ) {
1123 				
1124 			}
1125 	    }	
1126 	}
1127 	
1128 );
1129 
1130 /**@class_ */
1131 var KButton = Class(
1132 	/**@lends_ KGraphic*/
1133 	KGraphic,
1134 	{
1135 		
1136 		init : function ( options ) {
1137 			//ADD multiple constructors support
1138 			//x,y,w,h
1139 			//w,y,w,h,options
1140 			if ( valid( options ) ) {
1141 				KGraphic.init.call(this, options );
1142 			}
1143 			this.name = options.name;
1144 			master.buttons.push(this);
1145 		},
1146 		draw : function ( ) {},
1147 		onClick : function() { } //callback
1148 	}
1149 );
1150 //
1151 /**
1152 Karma function. It's a shotcut for calling 'new Karma(..)'
1153 @param [options] Options passed to the Karma constructor
1154 @returns {Object} a new Karma object
1155 @see Karma
1156 **/
1157 $.karma = function (options) {
1158 	var k =new Karma( options );
1159 	return k;
1160 }
1161 })(jQuery);