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