Moved ricoLocale_xx files to their parent directory (ricoClient/js). Moved files...
[infodrom/rico3] / minsrc / ricoUI.js
1 /*
2  *  (c) 2005-2009 Richard Cowin (http://openrico.org)
3  *  (c) 2005-2009 Matt Brown (http://dowdybrown.com)
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6  *  file except in compliance with the License. You may obtain a copy of the License at
7  *
8  *         http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software distributed under the
11  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12  *  either express or implied. See the License for the specific language governing permissions
13  *  and limitations under the License.
14  */
15
16 Rico.applyShadow = function(elem,shadowFlag) {
17   if (typeof shadowFlag=='undefined') shadowFlag=true;
18   if (shadowFlag) Rico.addClass(elem,'ricoShadow');
19   return elem;
20 };
21
22 // ensure popups/windows get closed in the right order when the user hits escape key
23 Rico._OpenPopupList = [];
24 Rico._RemoveOpenPopup = function(popup) {
25   if (popup.openIndex >= 0 && popup.openIndex < Rico._OpenPopupList.length) Rico._OpenPopupList.splice(popup.openIndex,1);
26   popup.openIndex = -1;
27 };
28 Rico._AddOpenPopup = function(popup) {
29   popup.openIndex = Rico._OpenPopupList.push(popup) - 1;
30 };
31 Rico._checkEscKey = function(e) {
32   if (Rico.eventKey(e) != 27) return true;
33   while (Rico._OpenPopupList.length > 0) {
34     var popup = Rico._OpenPopupList.pop();
35     if (popup && popup.visible()) {
36       popup.openIndex = -1;
37       Rico.eventStop(e);
38       popup.closeFunc();
39       return false;
40     }
41   }
42   return true;
43 };
44 Rico.eventBind(document,"keyup", Rico.eventHandle(Rico,'_checkEscKey'));
45
46
47 Rico.Popup = function(containerDiv,options) {
48   this.initialize(containerDiv,options);
49 };
50
51 Rico.Popup.prototype = {
52 /**
53  * @class Class to manage pop-up div windows.
54  * @constructs
55  * @param options object may contain any of the following:<dl>
56  *   <dt>hideOnClick </dt><dd> hide popup when mouse button is clicked? default=true</dd>
57  *   <dt>ignoreClicks</dt><dd> if true, mouse clicks within the popup are not allowed to bubble up to parent elements</dd>
58  *   <dt>position    </dt><dd> defaults to absolute, use "auto" to auto-detect</dd>
59  *   <dt>shadow      </dt><dd> display shadow with popup? default=true</dd>
60  *   <dt>zIndex      </dt><dd> which layer? default=1</dd>
61  *   <dt>canDrag     </dt><dd> boolean value (or function that returns a boolean) indicating if it is ok to drag/reposition popup, default=false</dd>
62  *   <dt>onClose     </dt><dd> function to call when the popup is closed</dd>
63  *</dl>
64  * @param containerDiv if supplied, then setDiv() is called at the end of initialization
65  */
66   initialize: function(containerDiv,options) {
67     this.options = {
68       hideOnClick   : false,
69       ignoreClicks  : false,
70       position      : 'absolute',
71       shadow        : true,
72       zIndex        : 2,
73       canDrag       : false,
74       dragElement   : false,
75       closeFunc     : false
76     };
77     this.openIndex=-1;
78     if (containerDiv) this.setDiv(containerDiv,options);
79   },
80
81   createContainer: function(options) {
82     this.setDiv(document.createElement('div'), options);
83     if (options && options.parent) {
84       options.parent.appendChild(this.container);
85     } else {
86       document.getElementsByTagName("body")[0].appendChild(this.container);
87     }
88   },
89
90 /**
91  * Apply popup behavior to a div that already exists in the DOM
92  * @param containerDiv div element (or element id) in the DOM. If null, then the div is created automatically.
93  */
94   setDiv: function(containerDiv,options) {
95     Rico.extend(this.options, options || {});
96     this.container=Rico.$(containerDiv);
97     if (this.options.position == 'auto') {
98       this.position=Rico.getStyle(this.container,'position').toLowerCase();
99     } else {
100       this.position=this.container.style.position=this.options.position;
101     }
102     if (this.position != 'absolute') {
103       this.content=this.container;
104       return;
105     }
106     if (this.options.zIndex >= 0) this.container.style.zIndex=this.options.zIndex;
107     if (this.options.closeFunc) {
108       this.closeFunc=this.options.closeFunc;
109     } else {
110       var self=this;
111       this.closeFunc=function() { self.closePopup(); };
112     }
113     //this.container.style.overflow='hidden';
114     this.container.style.top='0px';
115     this.container.style.left='0px';
116     this.container.style.display='none';
117
118     this.content=document.createElement('div');
119     while (this.container.firstChild) {
120       this.content.appendChild(this.container.firstChild);
121     }
122     this.container.appendChild(this.content);
123     this.content.className='RicoPopupContent';
124     this.content.style.position='relative';
125
126     if (Rico.isIE && Rico.ieVersion < 7) {
127       // create iframe shim
128       this.ifr = document.createElement('iframe');
129       this.ifr.style.position="absolute";
130       this.ifr.style.top     = '0px';
131       this.ifr.style.left    = '0px';
132       this.ifr.style.width   = '2000px';
133       this.ifr.style.height  = '2000px';
134       this.ifr.style.zIndex  = -1;
135       this.ifr.frameBorder   = 0;
136       this.ifr.src="javascript:false;";
137       this.content.appendChild(this.ifr);
138     }
139     Rico.applyShadow(this.container,this.options.shadow);
140
141     if (this.options.hideOnClick)
142       Rico.eventBind(document,"click", Rico.eventHandle(this,'_docClick'));
143     this.dragEnabled=false;
144     this.mousedownHandler = Rico.eventHandle(this,'_startDrag');
145     this.dragHandler = Rico.eventHandle(this,'_drag');
146     this.dropHandler = Rico.eventHandle(this,'_endDrag');
147     if (this.options.canDrag) this.enableDragging();
148     if (this.options.ignoreClicks || this.options.canDrag) this.ignoreClicks();
149   },
150
151   clearContent: function() {
152     this.content.innerHTML="";
153   },
154
155   setContent: function(content) {
156     this.content.innerHTML=content;
157   },
158
159   enableDragging: function() {
160     if (!this.dragEnabled && this.options.dragElement) {
161       Rico.eventBind(this.options.dragElement, "mousedown", this.mousedownHandler);
162       this.dragEnabled=true;
163     }
164     return this.dragEnabled;
165   },
166
167   disableDragging: function() {
168     if (!this.dragEnabled) return;
169     Rico.eventUnbind(this.options.dragElement, "mousedown", this.mousedownHandler);
170     this.dragEnabled=false;
171   },
172
173   setZ: function(zIndex) {
174     this.container.style.zIndex=zIndex;
175   },
176
177 /** @private */
178   ignoreClicks: function() {
179     Rico.eventBind(this.container,"click", Rico.eventHandle(this,'_ignoreClick'));
180   },
181
182   _ignoreClick: function(e) {
183     if (e.stopPropagation)
184       e.stopPropagation();
185     else
186       e.cancelBubble = true;
187     return true;
188   },
189
190   _docClick: function(e) {
191     this.closeFunc();
192     return true;
193   },
194
195 /**
196  * Move popup to specified position
197  */
198   move: function(left,top) {
199     if (typeof left=='number') this.container.style.left=left+'px';
200     if (typeof top=='number') this.container.style.top=top+'px';
201   },
202
203   _startDrag : function(event){
204     var elem=Rico.eventElement(event);
205     this.container.style.cursor='move';
206     this.lastMouse = Rico.eventClient(event);
207     Rico.eventBind(document, "mousemove", this.dragHandler);
208     Rico.eventBind(document, "mouseup", this.dropHandler);
209     Rico.eventStop(event);
210   },
211
212   _drag : function(event){
213     var newMouse = Rico.eventClient(event);
214     var newLeft = parseInt(this.container.style.left,10) + newMouse.x - this.lastMouse.x;
215     var newTop = parseInt(this.container.style.top,10) + newMouse.y - this.lastMouse.y;
216     this.move(newLeft, newTop);
217     this.lastMouse = newMouse;
218     Rico.eventStop(event);
219   },
220
221   _endDrag : function(){
222     this.container.style.cursor='';
223     Rico.eventUnbind(document, "mousemove", this.dragHandler);
224     Rico.eventUnbind(document, "mouseup", this.dropHandler);
225   },
226
227 /**
228  * Display popup at specified position
229  */
230   openPopup: function(left,top) {
231     this.container.style.display=this.position=='absolute' ? "block" : Rico.isIE && Rico.ieVersion<8 ? "inline" : "inline-block";
232     if (typeof left=='number') this.container.style.left=left+'px';
233     if (typeof top=='number') this.container.style.top=top+'px';
234     if (this.container.id) Rico.log('openPopup '+this.container.id+' at '+left+','+top);
235     Rico._AddOpenPopup(this);
236   },
237
238   centerPopup: function() {
239     this.openPopup();
240     var msgWidth=this.container.offsetWidth;
241     var msgHeight=this.container.offsetHeight;
242     var divwi=this.container.parentNode.offsetWidth;
243     var divht=this.container.parentNode.offsetHeight;
244     this.move(parseInt(Math.max((divwi-msgWidth)/2,0),10), parseInt(Math.max((divht-msgHeight)/2,0),10));
245   },
246
247   visible: function() {
248     return Rico.visible(this.container);
249   },
250
251 /**
252  * Hide popup
253  */
254   closePopup: function() {
255     Rico._RemoveOpenPopup(this);
256     if (!Rico.visible(this.container)) return;
257     if (this.container.id) Rico.log('closePopup '+this.container.id);
258     if (this.dragEnabled) this._endDrag();
259     this.container.style.display="none";
260     if (this.options.onClose) this.options.onClose();
261   }
262
263 };
264
265 Rico.closeButton = function(handle) {
266   var a = document.createElement('a');
267   a.className='RicoCloseAnchor';
268   if (Rico.theme.closeAnchor) Rico.addClass(a,Rico.theme.closeAnchor);
269   var span = a.appendChild(document.createElement('span'));
270   span.title=Rico.getPhraseById('close');
271   new Rico.HoverSet([a]);
272   Rico.addClass(span,Rico.theme.close || 'rico-icon RicoClose');
273   Rico.eventBind(a,"click", handle);
274   return a;
275 };
276
277 Rico.floatButton = function(buttonName, handle, title) {
278   var a=document.createElement("a");
279   a.className='RicoButtonAnchor'
280   Rico.addClass(a,Rico.theme.buttonAnchor || 'RicoButtonAnchorNative');
281   var span=a.appendChild(document.createElement("span"));
282   if (title) span.title=title;
283   span.className=Rico.theme[buttonName.toLowerCase()] || 'rico-icon Rico'+buttonName;
284   Rico.eventBind(a,"click", handle, false);
285   new Rico.HoverSet([a]);
286   return a
287 }
288
289 Rico.clearButton = function(handle) {
290   var span=document.createElement("span");
291   span.title=Rico.getPhraseById('clear');
292   span.className='ricoClear';
293   Rico.addClass(span, Rico.theme.clear || 'rico-icon ricoClearNative');
294   Rico.eventBind(span,"click", handle);
295   return span;
296 }
297
298 Rico.Window = function(title, options, contentParam) {
299   this.initialize(title, options, contentParam);
300 };
301
302 Rico.Window.prototype = {
303
304 /**
305  * Create popup div with a title bar.
306  */
307   initialize: function(title, options, contentParam) {
308     options=options || {overflow:'auto'};
309     Rico.extend(this, new Rico.Popup());
310
311     this.titleDiv = document.createElement('div');
312     this.options.canDrag=true;
313     this.options.dragElement=this.titleDiv;
314     this.createContainer(options);
315     this.content.appendChild(this.titleDiv);
316     contentParam=Rico.$(contentParam);
317     this.contentDiv=contentParam || document.createElement('div');
318     this.content.appendChild(this.contentDiv);
319
320     // create title area
321     this.titleDiv.className='ricoTitle';
322     if (Rico.theme.dialogTitle) Rico.addClass(this.titleDiv,Rico.theme.dialogTitle);
323     this.titleDiv.style.position='relative';
324     this.titleContent = document.createElement('span');
325     this.titleContent.className='ricoTitleSpan';
326     this.titleDiv.appendChild(this.titleContent);
327     this.titleDiv.appendChild(Rico.closeButton(Rico.eventHandle(this,'closePopup')));
328     if (!title && contentParam) {
329       title=contentParam.title;
330       contentParam.title='';
331     }
332     this.setTitle(title || '&nbsp;');
333
334     // create content area
335     this.contentDiv.className='ricoContent';
336     if (Rico.theme.dialogContent) Rico.addClass(this.contentDiv,Rico.theme.dialogContent);
337     this.contentDiv.style.position='relative';
338     if (options.height) this.contentDiv.style.height=options.height;
339     if (options.width) this.contentDiv.style.width=options.width;
340     if (options.overflow) this.contentDiv.style.overflow=options.overflow;
341     Rico.addClass(this.content,'ricoWindow');
342     if (Rico.theme.dialog) Rico.addClass(this.content,Rico.theme.dialog);
343     if (Rico.isIE) {
344       // fix float'ed content in IE
345       this.titleDiv.style.zoom=1;
346       this.contentDiv.style.zoom=1;
347     }
348
349     this.content=this.contentDiv;
350   },
351
352   setTitle: function(title) {
353     this.titleContent.innerHTML=title;
354   }
355
356 }
357
358
359 Rico.Menu = function(options) {
360   this.initialize(options);
361 }
362
363 Rico.Menu.prototype = {
364 /**
365  * @class Implements popup menus and submenus
366  * @extends Rico.Popup
367  * @constructs
368  */
369   initialize: function(options) {
370     Rico.extend(this, new Rico.Popup());
371     Rico.extend(this.options, {
372       width        : "15em",
373       arrowColor   : "b",   // for submenus: b=black, w=white
374       showDisabled : false,
375       hideOnClick  : true
376     });
377     if (typeof options=='string')
378       this.options.width=options;
379     else
380       Rico.extend(this.options, options || {});
381     this.hideFunc=null;
382     this.highlightElem=null;
383   },
384
385   createDiv: function(parentNode) {
386     if (this.container) return;
387     var self=this;
388     var options={ closeFunc: function() { self.cancelmenu(); } };
389     if (parentNode) options.parent=parentNode;
390     this.createContainer(options);
391     this.content.className = Rico.isWebKit ? 'ricoMenuSafari' : 'ricoMenu';
392     this.content.style.width=this.options.width;
393     this.direction=Rico.getStyle(this.container,'direction') || 'ltr';
394     this.direction=this.direction.toLowerCase();  // ltr or rtl
395     this.hidemenu();
396     this.itemCount=0;
397   },
398
399   showmenu: function(e,hideFunc){
400     Rico.eventStop(e);
401     this.hideFunc=hideFunc;
402     if (this.content.childNodes.length==0) {
403       this.cancelmenu();
404       return false;
405     }
406     var mousePos = Rico.eventClient(e);
407     this.openmenu(mousePos.x,mousePos.y,0,0);
408   },
409
410   openmenu: function(x,y,clickItemWi,clickItemHt,noOffset) {
411     var newLeft=x + (noOffset ? 0 : Rico.docScrollLeft());
412     this.container.style.visibility="hidden";
413     this.container.style.display="block";
414     var w=this.container.offsetWidth;
415     var cw=this.content.offsetWidth;
416     //window.status='openmenu: newLeft='+newLeft+' width='+w+' clickItemWi='+clickItemWi+' windowWi='+Rico.windowWidth();
417     if (this.direction == 'rtl') {
418       if (newLeft > w+clickItemWi) newLeft-=cw+clickItemWi;
419     } else {
420       if (x+w > Rico.windowWidth()) newLeft-=cw+clickItemWi-2;
421     }
422     var scrTop=Rico.docScrollTop();
423     var newTop=y + (noOffset ? 0 : scrTop);
424     if (y+this.container.offsetHeight-scrTop > Rico.windowHeight())
425       newTop=Math.max(newTop-this.content.offsetHeight+clickItemHt,0);
426     this.openPopup(newLeft,newTop);
427     this.container.style.visibility ="visible";
428     return false;
429   },
430
431   clearMenu: function() {
432     this.clearContent();
433     this.defaultAction=null;
434     this.itemCount=0;
435   },
436
437   addMenuHeading: function(hdg) {
438     var el=document.createElement('div');
439     el.innerHTML=hdg;
440     el.className='ricoMenuHeading';
441     this.content.appendChild(el);
442   },
443
444   addMenuBreak: function() {
445     var brk=document.createElement('div');
446     brk.className="ricoMenuBreak";
447     this.content.appendChild(brk);
448   },
449
450   addSubMenuItem: function(menutext, submenu, translate) {
451     var dir=this.direction=='rtl' ? 'left' : 'right';
452     var a=this.addMenuItem(menutext,null,true,null,translate);
453     a.className='ricoSubMenu';
454     var arrowdiv = a.appendChild(document.createElement('div'));
455     arrowdiv.className='rico-icon rico-'+dir+'-'+this.options.arrowColor;
456     Rico.setStyle(arrowdiv,{position:'absolute',top:'2px'});
457     arrowdiv.style[dir]='0px';
458     a.RicoSubmenu=submenu;
459     Rico.eventBind(a,"mouseover", Rico.eventHandle(this,'showSubMenu'));
460     Rico.eventBind(a,"mouseout", Rico.eventHandle(this,'subMenuOut'));
461   },
462
463   showSubMenu: function(e) {
464     if (this.openSubMenu) this.hideSubMenu();
465     var a=Rico.eventElement(e);
466     if (!a.RicoSubmenu) a=a.parentNode; // event can happen on arrow div
467     if (!a.RicoSubmenu) return;
468     this.openSubMenu=a.RicoSubmenu;
469     this.openMenuAnchor=a;
470     if (Rico.hasClass(a,'ricoSubMenu')) {
471       Rico.removeClass(a,'ricoSubMenu');
472       Rico.addClass(a,'ricoSubMenuOpen');
473     }
474     a.RicoSubmenu.openmenu(parseInt(this.container.style.left)+a.offsetWidth, parseInt(this.container.style.top)+a.offsetTop, a.offsetWidth-2, a.offsetHeight+2,true);
475   },
476
477   subMenuOut: function(e) {
478     if (!this.openSubMenu) return;
479     Rico.eventStop(e);
480     var elem=Rico.eventElement(e);
481     var reltg = Rico.eventRelatedTarget(e) || e.toElement;
482     try {
483       while (reltg != null && reltg != this.openSubMenu.div)
484         reltg=reltg.parentNode;
485     } catch(err) {}
486     if (reltg == this.openSubMenu.div) return;
487     this.hideSubMenu();
488   },
489
490   hideSubMenu: function() {
491     if (this.openMenuAnchor) {
492       Rico.removeClass(this.openMenuAnchor,'ricoSubMenuOpen');
493       Rico.addClass(this.openMenuAnchor,'ricoSubMenu');
494       this.openMenuAnchor=null;
495     }
496     if (this.openSubMenu) {
497       this.openSubMenu.hidemenu();
498       this.openSubMenu=null;
499     }
500   },
501
502   addMenuItemId: function(phraseId,action,enabled,title,target) {
503     if ( arguments.length < 3 ) enabled=true;
504     this.addMenuItem(Rico.getPhraseById(phraseId),action,enabled,title,target);
505   },
506
507 // if action is a string, then it is assumed to be a URL and the target parm can be used indicate which window gets the content
508 // action can also be a function
509 // action can also be a Rico.eventHandle, but set target='event' in this case
510   addMenuItem: function(menutext,action,enabled,title,target) {
511     if (arguments.length >= 3 && !enabled && !this.options.showDisabled) return null;
512     this.itemCount++;
513     var a = document.createElement(typeof action=='string' ? 'a' : 'div');
514     if ( arguments.length < 3 || enabled ) {
515       if (typeof action=='string') {
516         a.href = action;
517         if (target) a.target = target;
518       } else if (target=='event') {
519         Rico.eventBind(a,"click", action);
520       } else {
521         a.onclick=action;
522       }
523       a.className = 'enabled';
524       if (this.defaultAction==null) this.defaultAction=action;
525     } else {
526       a.disabled = true;
527       a.className = 'disabled';
528     }
529     a.innerHTML = menutext;
530     if (typeof title=='string')
531       a.title = title;
532     a=this.content.appendChild(a);
533     Rico.eventBind(a,"mouseover", Rico.eventHandle(this,'mouseOver'));
534     Rico.eventBind(a,"mouseout", Rico.eventHandle(this,'mouseOut'));
535     return a;
536   },
537
538   mouseOver: function(e) {
539     if (this.highlightElem && this.highlightElem.className=='enabled-hover') {
540       // required for Safari
541       this.highlightElem.className='enabled';
542       this.highlightElem=null;
543     }
544     var elem=Rico.eventElement(e);
545     if (this.openMenuAnchor && this.openMenuAnchor!=elem)
546       this.hideSubMenu();
547     if (elem.className=='enabled') {
548       elem.className='enabled-hover';
549       this.highlightElem=elem;
550     }
551   },
552
553   mouseOut: function(e) {
554     var elem=Rico.eventElement(e);
555     if (elem.className=='enabled-hover') elem.className='enabled';
556     if (this.highlightElem==elem) this.highlightElem=null;
557   },
558
559   cancelmenu: function() {
560     if (!this.visible()) return;
561     if (this.hideFunc) this.hideFunc();
562     this.hideFunc=null;
563     this.hidemenu();
564   },
565
566   hidemenu: function() {
567     if (this.openSubMenu) this.openSubMenu.hidemenu();
568     this.closePopup();
569   }
570
571 }
572
573
574 Rico.SelectionSet = function(selectionSet, options) {
575   this.initialize(selectionSet, options);
576 }
577
578 Rico.SelectionSet.prototype = {
579 /**
580  * @class
581  * @constructs
582  * @param selectionSet collection of DOM elements (or a CSS selection string)
583  * @param options object may contain any of the following:<dl>
584  *   <dt>selectedClass</dt><dd>class name to add when element is selected, default is "selected"</dd>
585  *   <dt>selectNode   </dt><dd>optional function that returns the element to be selected</dd>
586  *   <dt>onSelect     </dt><dd>optional function that gets called when element is selected</dd>
587  *   <dt>onFirstSelect</dt><dd>optional function that gets called the first time element is selected</dd>
588  *   <dt>noDefault    </dt><dd>when true, no element in the set is initially selected, default is false</dd>
589  *   <dt>selectedIndex</dt><dd>index of the element that should be initially selected, default is 0</dd>
590  *   <dt>cookieName   </dt><dd>optional name of cookie to use to remember selected element. If specified, and the cookie exists, then the cookie value overrides selectedIndex.</dd>
591  *   <dt>cookieDays   </dt><dd>specifies how long cookie should persist (in days). If unspecified, then the cookie persists for the current session.</dd>
592  *   <dt>cookiePath   </dt><dd>optional cookie path</dd>
593  *   <dt>cookieDomain </dt><dd>optional cookie domain</dd>
594  *</dl>
595  */
596   initialize: function(selectionSet, options){
597     Rico.log('SelectionSet#initialize');
598     this.options = options || {};
599     if (typeof selectionSet == 'string')
600       selectionSet = Rico.select(selectionSet);
601     this.previouslySelected = [];
602     this.selectionSet = [];
603     this.selectedClassName = this.options.selectedClass || Rico.theme.selected || "selected";
604     this.selectNode = this.options.selectNode || function(e){return e;};
605     this.onSelect = this.options.onSelect;
606     this.onFirstSelect = this.options.onFirstSelect;
607     var self=this;
608     this.clickHandler = function(idx) { self.selectIndex(idx); };
609     this.selectedIndex=-1;
610     for (var i=0; i<selectionSet.length; i++)
611       this.add(selectionSet[i]);
612     if (!this.options.noDefault) {
613       var cookieIndex=this.options.cookieName ? this.getCookie() : 0;
614       this.selectIndex(cookieIndex || this.options.selectedIndex || 0);
615     }
616   },
617   getCookie: function() {
618     var cookie = Rico.getCookie(this.options.cookieName);
619     if (!cookie) return 0;
620     var index = parseInt(cookie);
621     return index < this.selectionSet.length ? index : 0;
622   },
623   reset: function(){
624     this.previouslySelected = [];
625     this._notifySelected(this.selectedIndex);
626   },
627   clearSelected: function() {
628     if (this.selected)
629       Rico.removeClass(this.selectNode(this.selected), this.selectedClassName);
630   },
631   getIndex: function(element) {
632     for (var i=0; i<this.selectionSet.length; i++) {
633       if (element == this.selectionSet[i]) return i;
634     }
635     return -1;
636   },
637   select: function(element){
638     if (this.selected == element) return;
639     var i=this.getIndex(element);
640     if (i >= 0) this.selectIndex(i);
641   },
642   _notifySelected: function(index){
643     if (index < 0) return;
644     var element = this.selectionSet[index];
645     if (this.options.cookieName)
646       Rico.setCookie(this.options.cookieName, index, this.options.cookieDays, this.options.cookiePath, this.options.cookieDomain);
647     if (this.onFirstSelect && !this.previouslySelected[index]){
648       this.onFirstSelect(element, index);
649       this.previouslySelected[index] = true;
650     }
651     if (this.onSelect)
652       try{
653         this.onSelect(index);
654       } catch (e) {};
655   },
656   selectIndex: function(index){
657     if (this.selectedIndex == index || index >= this.selectionSet.length) return;
658     this.clearSelected();
659     this._notifySelected(index);
660     this.selectedIndex = index;
661     this.selected=this.selectionSet[index].element;
662     Rico.addClass(this.selectNode(this.selected), this.selectedClassName);
663   },
664   nextSelectIndex: function(){
665     return (this.selectedIndex + 1) % this.selectionSet.length;
666   },
667   nextSelectItem: function(){
668     return this.selectionSet[this.nextSelectIndex()];
669   },
670   selectNext: function(){
671     this.selectIndex(this.nextSelectIndex());
672   },
673   add: function(item){
674     var index=this.selectionSet.length;
675     this.selectionSet[index] = new Rico._SelectionItem(item,index,this.clickHandler);
676   },
677   remove: function(item){
678     if (item==this.selected) this.clearSelected();
679     var i=this.getIndex(item);
680     if (i < 0) return;
681     this.selectionSet[i].remove();
682     this.selectionSet.splice(i,1);
683   },
684   removeAll: function(){
685     this.clearSelected();
686     while (this.selectionSet.length > 0) {
687       this.selectionSet.pop().remove();
688     }
689   }
690 };
691
692
693 Rico._SelectionItem=function(element,index,callback) {
694   this.add(element,index,callback);
695 };
696
697 Rico._SelectionItem.prototype = {
698   add: function(element,index,callback) {
699     this.element=element;
700     this.index=index;
701     this.callback=callback;
702     this.handle=Rico.eventHandle(this,'click');
703     Rico.eventBind(element, "click", this.handle);
704   },
705
706   click: function(ev) {
707     this.callback(this.index);
708   },
709
710   remove: function() {
711     Rico.eventUnbind(this.element, "click", this.handle);
712   }
713 };
714
715
716 Rico.HoverSet = function(hoverSet, options) {
717   this.initialize(hoverSet, options);
718 };
719
720 Rico.HoverSet.prototype = {
721 /**
722  * @class
723  * @constructs
724  * @param hoverSet collection of DOM elements
725  * @param options object may contain any of the following:<dl>
726  *   <dt>hoverClass</dt><dd> class name to add when mouse is over element, default is "hover"</dd>
727  *   <dt>hoverNodes</dt><dd> optional function to select/filter which nodes are in the set</dd>
728  *</dl>
729  */
730   initialize: function(hoverSet, options){
731     Rico.log('HoverSet#initialize');
732     options = options || {};
733     this.hoverClass = options.hoverClass || Rico.theme.hover || "hover";
734     this.hoverFunc = options.hoverNodes || function(e){return [e];};
735     this.hoverSet=[];
736     if (!hoverSet) return;
737     for (var i=0; i<hoverSet.length; i++)
738       this.add(hoverSet[i]);
739   },
740   add: function(item) {
741     this.hoverSet.push(new Rico._HoverItem(item,this.hoverFunc,this.hoverClass));
742   },
743   removeAll: function(){
744     while (this.hoverSet.length > 0) {
745       this.hoverSet.pop().remove();
746     }
747   }
748 };
749
750
751 Rico._HoverItem=function(element,selectFunc,hoverClass) {
752   this.add(element,selectFunc,hoverClass);
753 };
754
755 Rico._HoverItem.prototype = {
756   add: function(element,selectFunc,hoverClass) {
757     this.element=element;
758     this.selectFunc=selectFunc;
759     this.hoverClass=hoverClass;
760     this.movehandle=Rico.eventHandle(this,'move');
761     this.outhandle=Rico.eventHandle(this,'mouseout');
762     Rico.eventBind(element, "mousemove", this.movehandle);
763     Rico.eventBind(element, "mouseout", this.outhandle);
764   },
765
766   move: function(ev) {
767     var elems=this.selectFunc(this.element);
768     for (var i=0; i<elems.length; i++)
769       Rico.addClass(elems[i],this.hoverClass);
770   },
771
772   mouseout: function(ev) {
773     var elems=this.selectFunc(this.element);
774     for (var i=0; i<elems.length; i++)
775       Rico.removeClass(elems[i],this.hoverClass);
776   },
777
778   remove: function() {
779     Rico.eventUnbind(element, "mousemove", this.movehandle);
780     Rico.eventUnbind(element, "mouseout", this.outhandle);
781   }
782 };
783
784
785 /** @namespace */
786 Rico.Effect = {};
787 Rico.Effect.easeIn = function(step){
788   return Math.sqrt(step);
789 };
790 Rico.Effect.easeOut = function(step){
791   return step*step;
792 };
793
794
795 /** @class core methods for transition effects */
796 Rico.ContentTransitionBase = function() {};
797 Rico.ContentTransitionBase.prototype = {
798   initBase: function(titles, contents, options) {
799     Rico.log('ContentTransitionBase#initBase');
800     if (typeof titles == 'string')
801       titles = Rico.select(titles);
802     if (typeof contents == 'string')
803       contents = Rico.select(contents);
804
805     this.options = options || {};
806     this.titles = titles;
807     this.contents = contents;
808     this.hoverSet = new Rico.HoverSet(titles, options);
809     for (var i=0; i<contents.length; i++) {
810       if (contents[i]) Rico.hide(contents[i]);
811     }
812     var self=this;
813     this.selectionSet = new Rico.SelectionSet(titles, Rico.extend(options, { onSelect: function(idx) { self._finishSelect(idx); } }));
814   },
815   reset: function(){
816     this.selectionSet.reset();
817   },
818   select: function(index) {
819     this.selectionSet.selectIndex(index);
820   },
821   _finishSelect: function(index) {
822     Rico.log('ContentTransitionBase#_finishSelect');
823     var panel = this.contents[index];
824     if (!panel) {
825       alert('Internal error: no panel @index='+index);
826       return;
827     }
828     if ( this.selected == panel) return;
829     if (this.transition){
830       if (this.selected){
831         this.transition(panel);
832       } else {
833         panel.style.display='block';
834       }
835     } else {
836       if (this.selected) Rico.hide(this.selected);
837       panel.style.display='block';
838     }
839     this.selected = panel;
840   },
841   addBase: function(title, content){
842     this.titles.push(title);
843     this.contents.push(content);
844     this.hoverSet.add(title);
845     this.selectionSet.add(title);
846     Rico.hide(content);
847     //this.selectionSet.select(title);
848   },
849   removeBase: function(title){},
850   removeAll: function(){
851     this.hoverSet.removeAll();
852     this.selectionSet.removeAll();
853   }
854 };
855
856
857 /**
858  * @class Implements accordion effect
859  * @see Rico.ContentTransitionBase#initialize for construction parameters
860  * @extends Rico.ContentTransitionBase
861  */
862 Rico.Accordion = function(element, options) {
863   this.initialize(element, options);
864 };
865
866 Rico.Accordion.prototype = Rico.extend(new Rico.ContentTransitionBase(),
867 /** @lends Rico.Accordion# */
868 {
869   initialize: function(element, options) {
870     element=Rico.$(element);
871     element.style.overflow='hidden';
872     element.className=options.accClass || Rico.theme.accordion || "Rico_accordion";
873     if (typeof options.panelWidth=='number') options.panelWidth+="px";
874     if (options.panelWidth) element.style.width = options.panelWidth;
875     var panels=Rico.getDirectChildrenByTag(element,'div');
876     var items,titles=[], contents=[];
877     for (var i=0; i<panels.length; i++) {
878       items=Rico.getDirectChildrenByTag(panels[i],'div');
879       if (items.length>=2) {
880         items[0].className=options.titleClass || Rico.theme.accTitle || "Rico_accTitle";
881         items[1].className=options.contentClass || Rico.theme.accContent || "Rico_accContent";
882         titles.push(items[0]);
883         contents.push(items[1]);
884         var a=Rico.wrapChildren(items[0],'','','a');
885         a.href="javascript:void(0)";
886       }
887     }
888     Rico.log('creating Rico.Accordion for '+element.id+' with '+titles.length+' panels');
889     this.initBase(titles, contents, options);
890     this.selected.style.height = this.options.panelHeight + "px";
891     this.totSteps=(typeof options.duration =='number' ? options.duration : 200)/25;
892   },
893   transition: function(p){
894     if (!this.options.noAnimate) {
895       this.closing=this.selected;
896       this.opening=p;
897       this.curStep=0;
898       var self=this;
899       this.timer=setInterval(function() { self.step(); },25);
900     } else {
901       p.style.height = this.options.panelHeight + "px";
902       if (this.selected) Rico.hide(this.selected);
903       p.style.display='block';
904     }
905   },
906   step: function() {
907     this.curStep++;
908     var oheight=Math.round(this.curStep/this.totSteps*this.options.panelHeight);
909     this.opening.style.height=oheight+'px';
910     this.closing.style.height=(this.options.panelHeight - oheight)+'px';
911     if (this.curStep==1) {
912       this.opening.style.paddingTop=this.opening.style.paddingBottom='0px';
913       this.opening.style.display='block';
914     }
915     if (this.curStep==this.totSteps) {
916       clearInterval(this.timer);
917       this.opening.style.paddingTop=this.opening.style.paddingBottom='';
918       Rico.hide(this.closing);
919     }
920   },
921   setPanelHeight: function(h) {
922     this.options.panelHeight = h;
923     this.selected.style.height = this.options.panelHeight + "px";
924   }
925 });
926
927
928 /**
929  * @class Implements tabbed panel effect
930  * @see Rico.ContentTransitionBase#initialize for construction parameters
931  * @extends Rico.ContentTransitionBase
932  */
933 Rico.TabbedPanel = function(element, options) {
934   this.initialize(element, options);
935 };
936
937 Rico.TabbedPanel.prototype = Rico.extend(new Rico.ContentTransitionBase(),
938 {
939   initialize: function(element, options) {
940     element=Rico.$(element);
941     options=options || {};
942     if (typeof options.panelWidth=='number') options.panelWidth+="px";
943     if (typeof options.panelHeight=='number') options.panelHeight+="px";
944     element.className=options.tabClass || Rico.theme.tabPanel || "Rico_tabPanel";
945     if (options.panelWidth) element.style.width = options.panelWidth;
946     var items = [];
947     var allKids = element.childNodes;
948     for( var i = 0 ; i < allKids.length ; i++ ) {
949       if (allKids[i] && allKids[i].tagName && allKids[i].tagName.match(/^div|ul$/i))
950         items.push(allKids[i]);
951     }
952     if (items.length < 2) return;
953     var childTag=items[0].tagName.toLowerCase()=='ul' ? 'li' : 'div';
954     items[0].className=options.navContainerClass || Rico.theme.tabNavContainer || "Rico_tabNavContainer";
955     items[0].style.listStyle='none';
956     items[1].className=options.contentContainerClass || Rico.theme.tabContentContainer || "Rico_tabContentContainer";
957     var titles=Rico.getDirectChildrenByTag(items[0], childTag);
958     var contents=Rico.getDirectChildrenByTag(items[1],'div');
959     if (!options.corners) options.corners='top';
960     for (var i=0; i<titles.length; i++) {
961       titles[i].className=options.titleClass || Rico.theme.tabTitle || "Rico_tabTitle";
962       var a=Rico.wrapChildren(titles[i],'','','a');
963       a.href="javascript:void(0)";
964       contents[i].className=options.contentClass || Rico.theme.tabContent || "Rico_tabContent";
965       if (options.panelHeight) contents[i].style.overflow='auto';
966       if (options.corners!='none') {
967         if (options.panelHdrWidth) titles[i].style.width=options.panelHdrWidth;
968         Rico.Corner.round(titles[i], Rico.theme.tabCornerOptions || options);
969       }
970     }
971     options.selectedClass=Rico.theme.tabSelected || 'selected';
972     this.initBase(titles, contents, options);
973     if (this.selected) this.transition(this.selected);
974   },
975   transition: function(p){
976     Rico.log('TabbedPanel#transition '+typeof(p));
977     if (this.selected) Rico.hide(this.selected);
978     Rico.show(p);
979     if (this.options.panelHeight) p.style.height = this.options.panelHeight;
980   }
981 });
982
983
984 /**
985  * @namespace
986  */
987 Rico.Corner = {
988
989    round: function(e, options) {
990       e = Rico.$(e);
991       this.options = {
992          corners : "all",
993          bgColor : "fromParent",
994          compact : false,
995          nativeCorners: false  // only use native corners?
996       };
997       Rico.extend(this.options, options || {});
998       if (typeof(Rico.getStyle(e,'border-radius'))=='string')
999         this._roundCornersStdCss(e);
1000       else if (typeof(Rico.getStyle(e,'-webkit-border-radius'))=='string')
1001         this._roundCornersWebKit(e);
1002       else if (typeof(Rico.getStyle(e,'-moz-border-radius'))=='string')
1003         this._roundCornersMoz(e);
1004       else if (!this.options.nativeCorners)
1005         this._roundCornersImpl(e);
1006    },
1007
1008     _roundCornersStdCss: function(e) {
1009       var radius=this.options.compact ? '4px' : '8px';
1010       if (this._hasString(this.options.corners, "all"))
1011         Rico.setStyle(e, {borderRadius:radius});
1012       else {
1013         if (this._hasString(this.options.corners, "top", "tl")) Rico.setStyle(e, {borderTopLeftRadius:radius});
1014         if (this._hasString(this.options.corners, "top", "tr")) Rico.setStyle(e, {borderTopRightRadius:radius});
1015         if (this._hasString(this.options.corners, "bottom", "bl")) Rico.setStyle(e, {borderBottomLeftRadius:radius});
1016         if (this._hasString(this.options.corners, "bottom", "br")) Rico.setStyle(e, {borderBottomRightRadius:radius});
1017       }
1018    },
1019
1020    _roundCornersWebKit: function(e) {
1021       var radius=this.options.compact ? '4px' : '8px';
1022       if (this._hasString(this.options.corners, "all"))
1023         Rico.setStyle(e, {WebkitBorderRadius:radius});
1024       else {
1025         if (this._hasString(this.options.corners, "top", "tl")) Rico.setStyle(e, {WebkitBorderTopLeftRadius:radius});
1026         if (this._hasString(this.options.corners, "top", "tr")) Rico.setStyle(e, {WebkitBorderTopRightRadius:radius});
1027         if (this._hasString(this.options.corners, "bottom", "bl")) Rico.setStyle(e, {WebkitBorderBottomLeftRadius:radius});
1028         if (this._hasString(this.options.corners, "bottom", "br")) Rico.setStyle(e, {WebkitBorderBottomRightRadius:radius});
1029       }
1030    },
1031
1032    _roundCornersMoz: function(e) {
1033       var radius=this.options.compact ? '4px' : '8px';
1034       if (this._hasString(this.options.corners, "all"))
1035         Rico.setStyle(e, {MozBorderRadius:radius});
1036       else {
1037         if (this._hasString(this.options.corners, "top", "tl")) Rico.setStyle(e, {MozBorderRadiusTopleft:radius});
1038         if (this._hasString(this.options.corners, "top", "tr")) Rico.setStyle(e, {MozBorderRadiusTopright:radius});
1039         if (this._hasString(this.options.corners, "bottom", "bl")) Rico.setStyle(e, {MozBorderRadiusBottomleft:radius});
1040         if (this._hasString(this.options.corners, "bottom", "br")) Rico.setStyle(e, {MozBorderRadiusBottomright:radius});
1041       }
1042    },
1043
1044   _roundCornersImpl: function(e) {
1045       var bgColor = this.options.bgColor == "fromParent" ? this._background(e.parentNode) : this.options.bgColor;
1046       e.style.position='relative';
1047       //this.options.numSlices = this.options.compact ? 2 : 4;
1048       if (this._hasString(this.options.corners, "all", "top", "tl")) this._createCorner(e,'top','left',bgColor);
1049       if (this._hasString(this.options.corners, "all", "top", "tr")) this._createCorner(e,'top','right',bgColor);
1050       if (this._hasString(this.options.corners, "all", "bottom", "bl")) this._createCorner(e,'bottom','left',bgColor);
1051       if (this._hasString(this.options.corners, "all", "bottom", "br")) this._createCorner(e,'bottom','right',bgColor);
1052    },
1053
1054   _createCorner: function(elem,tb,lr,bgColor) {
1055     //alert('Corner: '+tb+' '+lr+' bgColor='+typeof(bgColor));
1056     var corner = document.createElement("div");
1057     corner.className='ricoCorner';
1058     Rico.setStyle(corner,{width:'6px', height:'5px'});
1059     var borderStyle = Rico.getStyle(elem,'border-'+tb+'-style');
1060     var borderColor = borderStyle=='none' ? bgColor : Rico.getStyle(elem,'border-'+tb+'-color');
1061     //alert('Corner: '+tb+' '+borderStyle+borderColor+' '+);
1062     var pos=borderStyle=='none' ? '0px' : '-1px';
1063     corner.style[tb]=pos;
1064     corner.style[lr]=Rico.isIE && Rico.ieVersion<7 && lr=='right' && borderStyle!='none' ? '-2px' : '-1px';
1065     //corner.style[lr]='-1px';
1066     elem.appendChild(corner);
1067     var marginSizes = [ 0, 2, 3, 4, 4 ];
1068     if (tb=='bottom') marginSizes.reverse();
1069     var borderVal= borderStyle=='none' ? '0px none' : '1px solid '+borderColor;
1070     var d= lr=='left' ? 'Right' : 'Left';
1071     for (var i=0; i<marginSizes.length; i++) {
1072       var slice = document.createElement("div");
1073       Rico.setStyle(slice,{backgroundColor:bgColor,height:'1px'});
1074       slice.style['margin'+d]=marginSizes[i]+'px';
1075       slice.style['border'+d]=borderVal;
1076       corner.appendChild(slice);
1077     }
1078   },
1079
1080   _background: function(elem) {
1081      try {
1082        var actualColor = Rico.getStyle(elem, "backgroundColor");
1083
1084        // if color is tranparent, check parent
1085        // Safari returns "rgba(0, 0, 0, 0)", which means transparent
1086        if ( actualColor.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && elem.parentNode )
1087           return this._background(elem.parentNode);
1088
1089        return actualColor == null ? "#ffffff" : actualColor;
1090      } catch(err) {
1091        return "#ffffff";
1092      }
1093    },
1094
1095    _hasString: function(str) {
1096      for(var i=1 ; i<arguments.length ; i++) {
1097        if (str.indexOf(arguments[i]) >= 0) return true;
1098      }
1099      return false;
1100    }
1101
1102 };
1103
1104 Rico.toColorPart = function(c) {
1105   return Rico.zFill(c, 2, 16);
1106 };
1107
1108
1109 Rico.Color = function(red, green, blue) {
1110   this.initialize(red, green, blue);
1111 };
1112
1113 Rico.Color.prototype = {
1114 /**
1115  * @class Methods to manipulate color values.
1116  * @constructs
1117  * @param red integer (0-255)
1118  * @param green integer (0-255)
1119  * @param blue integer (0-255)
1120  */
1121    initialize: function(red, green, blue) {
1122       this.rgb = { r: red, g : green, b : blue };
1123    },
1124
1125    setRed: function(r) {
1126       this.rgb.r = r;
1127    },
1128
1129    setGreen: function(g) {
1130       this.rgb.g = g;
1131    },
1132
1133    setBlue: function(b) {
1134       this.rgb.b = b;
1135    },
1136
1137    setHue: function(h) {
1138
1139       // get an HSB model, and set the new hue...
1140       var hsb = this.asHSB();
1141       hsb.h = h;
1142
1143       // convert back to RGB...
1144       this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
1145    },
1146
1147    setSaturation: function(s) {
1148       // get an HSB model, and set the new hue...
1149       var hsb = this.asHSB();
1150       hsb.s = s;
1151
1152       // convert back to RGB and set values...
1153       this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
1154    },
1155
1156    setBrightness: function(b) {
1157       // get an HSB model, and set the new hue...
1158       var hsb = this.asHSB();
1159       hsb.b = b;
1160
1161       // convert back to RGB and set values...
1162       this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
1163    },
1164
1165    darken: function(percent) {
1166       var hsb  = this.asHSB();
1167       this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
1168    },
1169
1170    brighten: function(percent) {
1171       var hsb  = this.asHSB();
1172       this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
1173    },
1174
1175    blend: function(other) {
1176       this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
1177       this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
1178       this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
1179    },
1180
1181    isBright: function() {
1182       var hsb = this.asHSB();
1183       return this.asHSB().b > 0.5;
1184    },
1185
1186    isDark: function() {
1187       return ! this.isBright();
1188    },
1189
1190    asRGB: function() {
1191       return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
1192    },
1193
1194    asHex: function() {
1195       return "#" + Rico.toColorPart(this.rgb.r) + Rico.toColorPart(this.rgb.g) + Rico.toColorPart(this.rgb.b);
1196    },
1197
1198    asHSB: function() {
1199       return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
1200    },
1201
1202    toString: function() {
1203       return this.asHex();
1204    }
1205
1206 };
1207
1208 /**
1209  * Factory method for creating a color from an RGB string
1210  * @param hexCode a 3 or 6 digit hex string, optionally preceded by a # symbol
1211  * @returns a Rico.Color object
1212  */
1213 Rico.Color.createFromHex = function(hexCode) {
1214   if(hexCode.length==4) {
1215     var shortHexCode = hexCode;
1216     hexCode = '#';
1217     for(var i=1;i<4;i++)
1218       hexCode += (shortHexCode.charAt(i) + shortHexCode.charAt(i));
1219   }
1220   if ( hexCode.indexOf('#') == 0 )
1221     hexCode = hexCode.substring(1);
1222   if (!hexCode.match(/^[0-9A-Fa-f]{6}$/)) return null;
1223   var red   = hexCode.substring(0,2);
1224   var green = hexCode.substring(2,4);
1225   var blue  = hexCode.substring(4,6);
1226   return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
1227 };
1228
1229 /**
1230  * Retrieves the background color of an HTML element
1231  * @param elem the DOM element whose background color should be retreived
1232  * @returns a Rico.Color object
1233  */
1234 Rico.Color.createColorFromBackground = function(elem) {
1235
1236    if (!elem.style) return new Rico.Color(255,255,255);
1237    var actualColor = Rico.getStyle(elem, "background-color");
1238
1239    // if color is tranparent, check parent
1240    // Safari returns "rgba(0, 0, 0, 0)", which means transparent
1241    if ( actualColor.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && elem.parentNode )
1242       return Rico.Color.createColorFromBackground(elem.parentNode);
1243
1244    if (actualColor == null) return new Rico.Color(255,255,255);
1245
1246    if ( actualColor.indexOf("rgb(") == 0 ) {
1247       var colors = actualColor.substring(4, actualColor.length - 1 );
1248       var colorArray = colors.split(",");
1249       return new Rico.Color( parseInt( colorArray[0],10 ),
1250                              parseInt( colorArray[1],10 ),
1251                              parseInt( colorArray[2],10 )  );
1252
1253    }
1254    else if ( actualColor.indexOf("#") == 0 ) {
1255       return Rico.Color.createFromHex(actualColor);
1256    }
1257    else
1258       return new Rico.Color(255,255,255);
1259 };
1260
1261 /**
1262  * Converts hue/saturation/brightness to RGB
1263  * @returns a 3-element object: r=red, g=green, b=blue.
1264  */
1265 Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
1266
1267   var red   = 0;
1268   var green = 0;
1269   var blue  = 0;
1270
1271   if (saturation == 0) {
1272      red = parseInt(brightness * 255.0 + 0.5,10);
1273      green = red;
1274      blue = red;
1275   }
1276   else {
1277       var h = (hue - Math.floor(hue)) * 6.0;
1278       var f = h - Math.floor(h);
1279       var p = brightness * (1.0 - saturation);
1280       var q = brightness * (1.0 - saturation * f);
1281       var t = brightness * (1.0 - (saturation * (1.0 - f)));
1282
1283       switch (parseInt(h,10)) {
1284          case 0:
1285             red   = (brightness * 255.0 + 0.5);
1286             green = (t * 255.0 + 0.5);
1287             blue  = (p * 255.0 + 0.5);
1288             break;
1289          case 1:
1290             red   = (q * 255.0 + 0.5);
1291             green = (brightness * 255.0 + 0.5);
1292             blue  = (p * 255.0 + 0.5);
1293             break;
1294          case 2:
1295             red   = (p * 255.0 + 0.5);
1296             green = (brightness * 255.0 + 0.5);
1297             blue  = (t * 255.0 + 0.5);
1298             break;
1299          case 3:
1300             red   = (p * 255.0 + 0.5);
1301             green = (q * 255.0 + 0.5);
1302             blue  = (brightness * 255.0 + 0.5);
1303             break;
1304          case 4:
1305             red   = (t * 255.0 + 0.5);
1306             green = (p * 255.0 + 0.5);
1307             blue  = (brightness * 255.0 + 0.5);
1308             break;
1309           case 5:
1310             red   = (brightness * 255.0 + 0.5);
1311             green = (p * 255.0 + 0.5);
1312             blue  = (q * 255.0 + 0.5);
1313             break;
1314       }
1315   }
1316
1317    return { r : parseInt(red,10), g : parseInt(green,10) , b : parseInt(blue,10) };
1318 };
1319
1320 /**
1321  * Converts RGB value to hue/saturation/brightness
1322  * @param r integer (0-255)
1323  * @param g integer (0-255)
1324  * @param b integer (0-255)
1325  * @returns a 3-element object: h=hue, s=saturation, b=brightness.
1326  * (unlike some HSB documentation which states hue should be a value 0-360, this routine returns hue values from 0 to 1.0)
1327  */
1328 Rico.Color.RGBtoHSB = function(r, g, b) {
1329
1330    var hue;
1331    var saturation;
1332    var brightness;
1333
1334    var cmax = (r > g) ? r : g;
1335    if (b > cmax)
1336       cmax = b;
1337
1338    var cmin = (r < g) ? r : g;
1339    if (b < cmin)
1340       cmin = b;
1341
1342    brightness = cmax / 255.0;
1343    if (cmax != 0)
1344       saturation = (cmax - cmin)/cmax;
1345    else
1346       saturation = 0;
1347
1348    if (saturation == 0)
1349       hue = 0;
1350    else {
1351       var redc   = (cmax - r)/(cmax - cmin);
1352       var greenc = (cmax - g)/(cmax - cmin);
1353       var bluec  = (cmax - b)/(cmax - cmin);
1354
1355       if (r == cmax)
1356          hue = bluec - greenc;
1357       else if (g == cmax)
1358          hue = 2.0 + redc - bluec;
1359       else
1360          hue = 4.0 + greenc - redc;
1361
1362       hue = hue / 6.0;
1363       if (hue < 0)
1364          hue = hue + 1.0;
1365    }
1366
1367    return { h : hue, s : saturation, b : brightness };
1368 };
1369
1370 /**
1371  * Returns a new XML document object
1372  */
1373 Rico.createXmlDocument = function() {
1374   if (document.implementation && document.implementation.createDocument) {
1375     var doc = document.implementation.createDocument("", "", null);
1376     // some older versions of Moz did not support the readyState property
1377     // and the onreadystate event so we patch it!
1378     if (doc.readyState == null) {
1379       doc.readyState = 1;
1380       doc.addEventListener("load", function () {
1381         doc.readyState = 4;
1382         if (typeof doc.onreadystatechange == "function") {
1383           doc.onreadystatechange();
1384         }
1385       }, false);
1386     }
1387     return doc;
1388   }
1389
1390   if (window.ActiveXObject)
1391       return Rico.tryFunctions(
1392         function() { return new ActiveXObject('MSXML2.DomDocument');   },
1393         function() { return new ActiveXObject('Microsoft.DomDocument');},
1394         function() { return new ActiveXObject('MSXML.DomDocument');    },
1395         function() { return new ActiveXObject('MSXML3.DomDocument');   }
1396       ) || false;
1397   return null;
1398 }