BaSs_HaXoR

XBLSE Javascript source

Oct 17th, 2014
549
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. QWEBIRC_BUILD="711ba53432dc9c9b488ca60b3eff7a525dc2e45b";
  2. //MooTools, <http://mootools.net>, My Object Oriented (JavaScript) Tools. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net>, MIT Style License.
  3.  
  4. var MooTools={version:"1.2.5",build:"008d8f0f2fcc2044e54fdd3635341aaab274e757"};var Native=function(l){l=l||{};var a=l.name;var j=l.legacy;var b=l.protect;var c=l.implement;var i=l.generics;var g=l.initialize;var h=l.afterImplement||function(){};var d=g||j;i=i!==false;d.constructor=Native;d.$family={name:"native"};if(j&&g){d.prototype=j.prototype;}d.prototype.constructor=d;if(a){var f=a.toLowerCase();d.prototype.$family={name:f};Native.typize(d,f);}var k=function(o,m,p,n){if(!b||n||!o.prototype[m]){o.prototype[m]=p;}if(i){Native.genericize(o,m,b);}h.call(o,m,p);return o;};d.alias=function(o,m,q){if(typeof o=="string"){var p=this.prototype[o];if((o=p)){return k(this,m,o,q);}}for(var n in o){this.alias(n,o[n],m);}return this;};d.implement=function(n,m,q){if(typeof n=="string"){return k(this,n,m,q);}for(var o in n){k(this,o,n[o],m);}return this;};if(c){d.implement(c);}return d;};Native.genericize=function(b,c,a){if((!a||!b[c])&&typeof b.prototype[c]=="function"){b[c]=function(){var d=Array.prototype.slice.call(arguments);return b.prototype[c].apply(d.shift(),d);};}};Native.implement=function(d,c){for(var b=0,a=d.length;b<a;b++){d[b].implement(c);}};Native.typize=function(a,b){if(!a.type){a.type=function(c){return($type(c)===b);};}};(function(){var a={Array:Array,Date:Date,Function:Function,Number:Number,RegExp:RegExp,String:String};for(var j in a){new Native({name:j,initialize:a[j],protect:true});}var d={"boolean":Boolean,"native":Native,object:Object};for(var c in d){Native.typize(d[c],c);}var h={Array:["concat","indexOf","join","lastIndexOf","pop","push","reverse","shift","slice","sort","splice","toString","unshift","valueOf"],String:["charAt","charCodeAt","concat","indexOf","lastIndexOf","match","replace","search","slice","split","substr","substring","toLowerCase","toUpperCase","valueOf"]};for(var f in h){for(var b=h[f].length;b--;){Native.genericize(a[f],h[f][b],true);}}})();var Hash=new Native({name:"Hash",initialize:function(a){if($type(a)=="hash"){a=$unlink(a.getClean());}for(var b in a){this[b]=a[b];}return this;}});Hash.implement({forEach:function(b,c){for(var a in this){if(this.hasOwnProperty(a)){b.call(c,this[a],a,this);}}},getClean:function(){var b={};for(var a in this){if(this.hasOwnProperty(a)){b[a]=this[a];}}return b;},getLength:function(){var b=0;for(var a in this){if(this.hasOwnProperty(a)){b++;}}return b;}});Hash.alias("forEach","each");Array.implement({forEach:function(c,d){for(var b=0,a=this.length;b<a;b++){c.call(d,this[b],b,this);}}});Array.alias("forEach","each");function $A(b){if(b.item){var a=b.length,c=new Array(a);while(a--){c[a]=b[a];}return c;}return Array.prototype.slice.call(b);}function $arguments(a){return function(){return arguments[a];};}function $chk(a){return !!(a||a===0);}function $clear(a){clearTimeout(a);clearInterval(a);return null;}function $defined(a){return(a!=undefined);}function $each(c,b,d){var a=$type(c);((a=="arguments"||a=="collection"||a=="array")?Array:Hash).each(c,b,d);}function $empty(){}function $extend(c,a){for(var b in (a||{})){c[b]=a[b];}return c;}function $H(a){return new Hash(a);}function $lambda(a){return($type(a)=="function")?a:function(){return a;};}function $merge(){var a=Array.slice(arguments);a.unshift({});return $mixin.apply(null,a);}function $mixin(f){for(var d=1,a=arguments.length;d<a;d++){var b=arguments[d];if($type(b)!="object"){continue;}for(var c in b){var h=b[c],g=f[c];f[c]=(g&&$type(h)=="object"&&$type(g)=="object")?$mixin(g,h):$unlink(h);}}return f;}function $pick(){for(var b=0,a=arguments.length;b<a;b++){if(arguments[b]!=undefined){return arguments[b];}}return null;}function $random(b,a){return Math.floor(Math.random()*(a-b+1)+b);}function $splat(b){var a=$type(b);return(a)?((a!="array"&&a!="arguments")?[b]:b):[];}var $time=Date.now||function(){return +new Date;};function $try(){for(var b=0,a=arguments.length;b<a;b++){try{return arguments[b]();}catch(c){}}return null;}function $type(a){if(a==undefined){return false;}if(a.$family){return(a.$family.name=="number"&&!isFinite(a))?false:a.$family.name;}if(a.nodeName){switch(a.nodeType){case 1:return"element";case 3:return(/\S/).test(a.nodeValue)?"textnode":"whitespace";}}else{if(typeof a.length=="number"){if(a.callee){return"arguments";}else{if(a.item){return"collection";}}}}return typeof a;}function $unlink(c){var b;switch($type(c)){case"object":b={};for(var f in c){b[f]=$unlink(c[f]);}break;case"hash":b=new Hash(c);break;case"array":b=[];for(var d=0,a=c.length;d<a;d++){b[d]=$unlink(c[d]);}break;default:return c;}return b;}Array.implement({every:function(c,d){for(var b=0,a=this.length;b<a;b++){if(!c.call(d,this[b],b,this)){return false;}}return true;},filter:function(d,f){var c=[];for(var b=0,a=this.length;b<a;b++){if(d.call(f,this[b],b,this)){c.push(this[b]);}}return c;},clean:function(){return this.filter($defined);},indexOf:function(c,d){var a=this.length;for(var b=(d<0)?Math.max(0,a+d):d||0;b<a;b++){if(this[b]===c){return b;}}return -1;},map:function(d,f){var c=[];for(var b=0,a=this.length;b<a;b++){c[b]=d.call(f,this[b],b,this);}return c;},some:function(c,d){for(var b=0,a=this.length;b<a;b++){if(c.call(d,this[b],b,this)){return true;}}return false;},associate:function(c){var d={},b=Math.min(this.length,c.length);for(var a=0;a<b;a++){d[c[a]]=this[a];}return d;},link:function(c){var a={};for(var f=0,b=this.length;f<b;f++){for(var d in c){if(c[d](this[f])){a[d]=this[f];delete c[d];break;}}}return a;},contains:function(a,b){return this.indexOf(a,b)!=-1;},extend:function(c){for(var b=0,a=c.length;b<a;b++){this.push(c[b]);}return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[$random(0,this.length-1)]:null;},include:function(a){if(!this.contains(a)){this.push(a);}return this;},combine:function(c){for(var b=0,a=c.length;b<a;b++){this.include(c[b]);}return this;},erase:function(b){for(var a=this.length;a--;a){if(this[a]===b){this.splice(a,1);}}return this;},empty:function(){this.length=0;return this;},flatten:function(){var d=[];for(var b=0,a=this.length;b<a;b++){var c=$type(this[b]);if(!c){continue;}d=d.concat((c=="array"||c=="collection"||c=="arguments")?Array.flatten(this[b]):this[b]);}return d;},hexToRgb:function(b){if(this.length!=3){return null;}var a=this.map(function(c){if(c.length==1){c+=c;}return c.toInt(16);});return(b)?a:"rgb("+a+")";},rgbToHex:function(d){if(this.length<3){return null;}if(this.length==4&&this[3]==0&&!d){return"transparent";}var b=[];for(var a=0;a<3;a++){var c=(this[a]-0).toString(16);b.push((c.length==1)?"0"+c:c);}return(d)?b:"#"+b.join("");}});String.implement({test:function(a,b){return((typeof a=="string")?new RegExp(a,b):a).test(this);},contains:function(a,b){return(b)?(b+this+b).indexOf(b+a+b)>-1:this.indexOf(a)>-1;},trim:function(){return this.replace(/^\s+|\s+$/g,"");},clean:function(){return this.replace(/\s+/g," ").trim();},camelCase:function(){return this.replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();});},hyphenate:function(){return this.replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());});},capitalize:function(){return this.replace(/\b[a-z]/g,function(a){return a.toUpperCase();});},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");},toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);},hexToRgb:function(b){var a=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=this.match(/\d{1,3}/g);return(a)?a.rgbToHex(b):null;},stripScripts:function(b){var a="";var c=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(){a+=arguments[1]+"\n";return"";});if(b===true){$exec(a);}else{if($type(b)=="function"){b(a,c);}}return c;},substitute:function(a,b){return this.replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);}return(a[c]!=undefined)?a[c]:"";});}});try{delete Function.prototype.bind;}catch(e){}Function.implement({extend:function(a){for(var b in a){this[b]=a[b];}return this;},create:function(b){var a=this;b=b||{};return function(d){var c=b.arguments;c=(c!=undefined)?$splat(c):Array.slice(arguments,(b.event)?1:0);if(b.event){c=[d||window.event].extend(c);}var f=function(){return a.apply(b.bind||null,c);};if(b.delay){return setTimeout(f,b.delay);}if(b.periodical){return setInterval(f,b.periodical);}if(b.attempt){return $try(f);}return f();};},run:function(a,b){return this.apply(b,$splat(a));},pass:function(a,b){return this.create({bind:b,arguments:a});},bind:function(b,a){return this.create({bind:b,arguments:a});},bindWithEvent:function(b,a){return this.create({bind:b,arguments:a,event:true});},attempt:function(a,b){return this.create({bind:b,arguments:a,attempt:true})();},delay:function(b,c,a){return this.create({bind:c,arguments:a,delay:b})();},periodical:function(c,b,a){return this.create({bind:b,arguments:a,periodical:c})();}});Number.implement({limit:function(b,a){return Math.min(a,Math.max(b,this));},round:function(a){a=Math.pow(10,a||0);return Math.round(this*a)/a;},times:function(b,c){for(var a=0;a<this;a++){b.call(c,a,this);}},toFloat:function(){return parseFloat(this);},toInt:function(a){return parseInt(this,a||10);}});Number.alias("times","each");(function(b){var a={};b.each(function(c){if(!Number[c]){a[c]=function(){return Math[c].apply(null,[this].concat($A(arguments)));};}});Number.implement(a);})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(b){for(var a in this){if(this.hasOwnProperty(a)&&this[a]===b){return a;}}return null;},hasValue:function(a){return(Hash.keyOf(this,a)!==null);},extend:function(a){Hash.each(a||{},function(c,b){Hash.set(this,b,c);},this);return this;},combine:function(a){Hash.each(a||{},function(c,b){Hash.include(this,b,c);},this);return this;},erase:function(a){if(this.hasOwnProperty(a)){delete this[a];}return this;},get:function(a){return(this.hasOwnProperty(a))?this[a]:null;},set:function(a,b){if(!this[a]||this.hasOwnProperty(a)){this[a]=b;}return this;},empty:function(){Hash.each(this,function(b,a){delete this[a];},this);return this;},include:function(a,b){if(this[a]==undefined){this[a]=b;}return this;},map:function(b,c){var a=new Hash;Hash.each(this,function(f,d){a.set(d,b.call(c,f,d,this));},this);return a;},filter:function(b,c){var a=new Hash;Hash.each(this,function(f,d){if(b.call(c,f,d,this)){a.set(d,f);}},this);return a;},every:function(b,c){for(var a in this){if(this.hasOwnProperty(a)&&!b.call(c,this[a],a)){return false;}}return true;},some:function(b,c){for(var a in this){if(this.hasOwnProperty(a)&&b.call(c,this[a],a)){return true;}}return false;},getKeys:function(){var a=[];Hash.each(this,function(c,b){a.push(b);});return a;},getValues:function(){var a=[];Hash.each(this,function(b){a.push(b);});return a;},toQueryString:function(a){var b=[];Hash.each(this,function(g,f){if(a){f=a+"["+f+"]";}var d;switch($type(g)){case"object":d=Hash.toQueryString(g,f);break;case"array":var c={};g.each(function(j,h){c[h]=j;});d=Hash.toQueryString(c,f);break;default:d=f+"="+encodeURIComponent(g);}if(g!=undefined){b.push(d);}});return b.join("&");}});Hash.alias({keyOf:"indexOf",hasValue:"contains"});function Class(b){if(b instanceof Function){b={initialize:b};}var a=function(){Object.reset(this);if(a._prototyping){return this;}this._current=$empty;var c=(this.initialize)?this.initialize.apply(this,arguments):this;delete this._current;delete this.caller;return c;}.extend(this);a.implement(b);a.constructor=Class;a.prototype.constructor=a;return a;}Function.prototype.protect=function(){this._protected=true;return this;};Object.reset=function(a,c){if(c==null){for(var f in a){Object.reset(a,f);}return a;}delete a[c];switch($type(a[c])){case"object":var d=function(){};d.prototype=a[c];var b=new d;a[c]=Object.reset(b);break;case"array":a[c]=$unlink(a[c]);break;}return a;};new Native({name:"Class",initialize:Class}).extend({instantiate:function(b){b._prototyping=true;var a=new b;delete b._prototyping;return a;},wrap:function(a,b,c){if(c._origin){c=c._origin;}return function(){if(c._protected&&this._current==null){throw new Error('The method "'+b+'" cannot be called.');}var f=this.caller,g=this._current;this.caller=g;this._current=arguments.callee;var d=c.apply(this,arguments);this._current=g;this.caller=f;return d;}.extend({_owner:a,_origin:c,_name:b});}});Class.implement({implement:function(a,d){if($type(a)=="object"){for(var f in a){this.implement(f,a[f]);}return this;}var g=Class.Mutators[a];if(g){d=g.call(this,d);if(d==null){return this;}}var c=this.prototype;switch($type(d)){case"function":if(d._hidden){return this;}c[a]=Class.wrap(this,a,d);break;case"object":var b=c[a];if($type(b)=="object"){$mixin(b,d);}else{c[a]=$unlink(d);}break;case"array":c[a]=$unlink(d);break;default:c[a]=d;}return this;}});Class.Mutators={Extends:function(a){this.parent=a;this.prototype=Class.instantiate(a);this.implement("parent",function(){var b=this.caller._name,c=this.caller._owner.parent.prototype[b];if(!c){throw new Error('The method "'+b+'" has no parent.');}return c.apply(this,arguments);}.protect());},Implements:function(a){$splat(a).each(function(b){if(b instanceof Function){b=Class.instantiate(b);}this.implement(b);},this);}};var Chain=new Class({$chain:[],chain:function(){this.$chain.extend(Array.flatten(arguments));return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();return this;}});var Events=new Class({$events:{},addEvent:function(c,b,a){c=Events.removeOn(c);if(b!=$empty){this.$events[c]=this.$events[c]||[];this.$events[c].include(b);if(a){b.internal=true;}}return this;},addEvents:function(a){for(var b in a){this.addEvent(b,a[b]);}return this;},fireEvent:function(c,b,a){c=Events.removeOn(c);if(!this.$events||!this.$events[c]){return this;}this.$events[c].each(function(d){d.create({bind:this,delay:a,"arguments":b})();},this);return this;},removeEvent:function(b,a){b=Events.removeOn(b);if(!this.$events[b]){return this;}if(!a.internal){this.$events[b].erase(a);}return this;},removeEvents:function(c){var d;if($type(c)=="object"){for(d in c){this.removeEvent(d,c[d]);}return this;}if(c){c=Events.removeOn(c);}for(d in this.$events){if(c&&c!=d){continue;}var b=this.$events[d];for(var a=b.length;a--;a){this.removeEvent(d,b[a]);}}return this;}});Events.removeOn=function(a){return a.replace(/^on([A-Z])/,function(b,c){return c.toLowerCase();});};var Options=new Class({setOptions:function(){this.options=$merge.run([this.options].extend(arguments));if(!this.addEvent){return this;}for(var a in this.options){if($type(this.options[a])!="function"||!(/^on[A-Z]/).test(a)){continue;}this.addEvent(a,this.options[a]);delete this.options[a];}return this;}});var Browser=$merge({Engine:{name:"unknown",version:0},Platform:{name:(window.orientation!=undefined)?"ipod":(navigator.platform.match(/mac|win|linux/i)||["other"])[0].toLowerCase()},Features:{xpath:!!(document.evaluate),air:!!(window.runtime),query:!!(document.querySelector)},Plugins:{},Engines:{presto:function(){return(!window.opera)?false:((arguments.callee.caller)?960:((document.getElementsByClassName)?950:925));},trident:function(){return(!window.ActiveXObject)?false:((window.XMLHttpRequest)?((document.querySelectorAll)?6:5):4);},webkit:function(){return(navigator.taintEnabled)?false:((Browser.Features.xpath)?((Browser.Features.query)?525:420):419);},gecko:function(){return(!document.getBoxObjectFor&&window.mozInnerScreenX==null)?false:((document.getElementsByClassName)?19:18);}}},Browser||{});Browser.Platform[Browser.Platform.name]=true;Browser.detect=function(){for(var b in this.Engines){var a=this.Engines[b]();if(a){this.Engine={name:b,version:a};this.Engine[b]=this.Engine[b+a]=true;break;}}return{name:b,version:a};};Browser.detect();Browser.Request=function(){return $try(function(){return new XMLHttpRequest();},function(){return new ActiveXObject("MSXML2.XMLHTTP");},function(){return new ActiveXObject("Microsoft.XMLHTTP");});};Browser.Features.xhr=!!(Browser.Request());Browser.Plugins.Flash=(function(){var a=($try(function(){return navigator.plugins["Shockwave Flash"].description;},function(){return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version");})||"0 r0").match(/\d+/g);return{version:parseInt(a[0]||0+"."+a[1],10)||0,build:parseInt(a[2],10)||0};})();function $exec(b){if(!b){return b;}if(window.execScript){window.execScript(b);}else{var a=document.createElement("script");a.setAttribute("type","text/javascript");a[(Browser.Engine.webkit&&Browser.Engine.version<420)?"innerText":"text"]=b;document.head.appendChild(a);document.head.removeChild(a);}return b;}Native.UID=1;var $uid=(Browser.Engine.trident)?function(a){return(a.uid||(a.uid=[Native.UID++]))[0];}:function(a){return a.uid||(a.uid=Native.UID++);};var Window=new Native({name:"Window",legacy:(Browser.Engine.trident)?null:window.Window,initialize:function(a){$uid(a);if(!a.Element){a.Element=$empty;if(Browser.Engine.webkit){a.document.createElement("iframe");}a.Element.prototype=(Browser.Engine.webkit)?window["[[DOMElement.prototype]]"]:{};}a.document.window=a;return $extend(a,Window.Prototype);},afterImplement:function(b,a){window[b]=Window.Prototype[b]=a;}});Window.Prototype={$family:{name:"window"}};new Window(window);var Document=new Native({name:"Document",legacy:(Browser.Engine.trident)?null:window.Document,initialize:function(a){$uid(a);a.head=a.getElementsByTagName("head")[0];a.html=a.getElementsByTagName("html")[0];if(Browser.Engine.trident&&Browser.Engine.version<=4){$try(function(){a.execCommand("BackgroundImageCache",false,true);});}if(Browser.Engine.trident){a.window.attachEvent("onunload",function(){a.window.detachEvent("onunload",arguments.callee);a.head=a.html=a.window=null;});}return $extend(a,Document.Prototype);},afterImplement:function(b,a){document[b]=Document.Prototype[b]=a;}});Document.Prototype={$family:{name:"document"}};new Document(document);var Element=new Native({name:"Element",legacy:window.Element,initialize:function(a,b){var c=Element.Constructors.get(a);if(c){return c(b);}if(typeof a=="string"){return document.newElement(a,b);}return document.id(a).set(b);},afterImplement:function(a,b){Element.Prototype[a]=b;if(Array[a]){return;}Elements.implement(a,function(){var c=[],h=true;for(var f=0,d=this.length;f<d;f++){var g=this[f][a].apply(this[f],arguments);c.push(g);if(h){h=($type(g)=="element");}}return(h)?new Elements(c):c;});}});Element.Prototype={$family:{name:"element"}};Element.Constructors=new Hash;var IFrame=new Native({name:"IFrame",generics:false,initialize:function(){var g=Array.link(arguments,{properties:Object.type,iframe:$defined});var d=g.properties||{};var c=document.id(g.iframe);var f=d.onload||$empty;delete d.onload;d.id=d.name=$pick(d.id,d.name,c?(c.id||c.name):"IFrame_"+$time());c=new Element(c||"iframe",d);var b=function(){var h=$try(function(){return c.contentWindow.location.host;});if(!h||h==window.location.host){var i=new Window(c.contentWindow);new Document(c.contentWindow.document);$extend(i.Element.prototype,Element.Prototype);}f.call(c.contentWindow,c.contentWindow.document);};var a=$try(function(){return c.contentWindow;});((a&&a.document.body)||window.frames[d.id])?b():c.addListener("load",b);return c;}});var Elements=new Native({initialize:function(g,b){b=$extend({ddup:true,cash:true},b);g=g||[];if(b.ddup||b.cash){var h={},f=[];for(var c=0,a=g.length;c<a;c++){var d=document.id(g[c],!b.cash);if(b.ddup){if(h[d.uid]){continue;}h[d.uid]=true;}if(d){f.push(d);}}g=f;}return(b.cash)?$extend(g,this):g;}});Elements.implement({filter:function(a,b){if(!a){return this;}return new Elements(Array.filter(this,(typeof a=="string")?function(c){return c.match(a);}:a,b));}});(function(){var d;try{var a=document.createElement("<input name=x>");d=(a.name=="x");}catch(b){}var c=function(f){return(""+f).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(f,g){if(g&&g.checked!=null){g.defaultChecked=g.checked;}if(d&&g){f="<"+f;if(g.name){f+=' name="'+c(g.name)+'"';}if(g.type){f+=' type="'+c(g.type)+'"';}f+=">";delete g.name;delete g.type;}return this.id(this.createElement(f)).set(g);},newTextNode:function(f){return this.createTextNode(f);},getDocument:function(){return this;},getWindow:function(){return this.window;},id:(function(){var f={string:function(i,h,g){i=g.getElementById(i);return(i)?f.element(i,h):null;},element:function(g,j){$uid(g);if(!j&&!g.$family&&!(/^object|embed$/i).test(g.tagName)){var h=Element.Prototype;for(var i in h){g[i]=h[i];}}return g;},object:function(h,i,g){if(h.toElement){return f.element(h.toElement(g),i);}return null;}};f.textnode=f.whitespace=f.window=f.document=$arguments(0);return function(h,j,i){if(h&&h.$family&&h.uid){return h;}var g=$type(h);return(f[g])?f[g](h,j,i||document):null;};})()});})();if(window.$==null){Window.implement({$:function(a,b){return document.id(a,b,this.document);}});}Window.implement({$$:function(a){if(arguments.length==1&&typeof a=="string"){return this.document.getElements(a);}var g=[];var c=Array.flatten(arguments);for(var d=0,b=c.length;d<b;d++){var f=c[d];switch($type(f)){case"element":g.push(f);break;case"string":g.extend(this.document.getElements(f,true));}}return new Elements(g);},getDocument:function(){return this.document;},getWindow:function(){return this;}});Native.implement([Element,Document],{getElement:function(a,b){return document.id(this.getElements(a,true)[0]||null,b);},getElements:function(a,d){a=a.split(",");var c=[];var b=(a.length>1);a.each(function(f){var g=this.getElementsByTagName(f.trim());(b)?c.extend(g):c=g;},this);return new Elements(c,{ddup:b,cash:!d});}});(function(){var i={},g={};var j={input:"checked",option:"selected",textarea:(Browser.Engine.webkit&&Browser.Engine.version<420)?"innerHTML":"value"};var c=function(m){return(g[m]||(g[m]={}));};var h=function(o,m){if(!o){return;}var n=o.uid;if(m!==true){m=false;}if(Browser.Engine.trident){if(o.clearAttributes){var r=m&&o.cloneNode(false);o.clearAttributes();if(r){o.mergeAttributes(r);}}else{if(o.removeEvents){o.removeEvents();}}if((/object/i).test(o.tagName)){for(var q in o){if(typeof o[q]=="function"){o[q]=$empty;}}Element.dispose(o);}}if(!n){return;}i[n]=g[n]=null;};var d=function(){Hash.each(i,h);if(Browser.Engine.trident){$A(document.getElementsByTagName("object")).each(h);}if(window.CollectGarbage){CollectGarbage();}i=g=null;};var k=function(o,m,t,n,q,s){var p=o[t||m];var r=[];while(p){if(p.nodeType==1&&(!n||Element.match(p,n))){if(!q){return document.id(p,s);}r.push(p);}p=p[m];}return(q)?new Elements(r,{ddup:false,cash:!s}):null;};var f={html:"innerHTML","class":"className","for":"htmlFor",defaultValue:"defaultValue",text:(Browser.Engine.trident||(Browser.Engine.webkit&&Browser.Engine.version<420))?"innerText":"textContent"};var b=["compact","nowrap","ismap","declare","noshade","checked","disabled","readonly","multiple","selected","noresize","defer"];var l=["value","type","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","maxLength","readOnly","rowSpan","tabIndex","useMap"];b=b.associate(b);Hash.extend(f,b);Hash.extend(f,l.associate(l.map(String.toLowerCase)));var a={before:function(n,m){if(m.parentNode){m.parentNode.insertBefore(n,m);}},after:function(n,m){if(!m.parentNode){return;}var o=m.nextSibling;(o)?m.parentNode.insertBefore(n,o):m.parentNode.appendChild(n);},bottom:function(n,m){m.appendChild(n);},top:function(n,m){var o=m.firstChild;(o)?m.insertBefore(n,o):m.appendChild(n);}};a.inside=a.bottom;Hash.each(a,function(m,n){n=n.capitalize();Element.implement("inject"+n,function(o){m(this,document.id(o,true));return this;});Element.implement("grab"+n,function(o){m(document.id(o,true),this);return this;});});Element.implement({set:function(q,n){switch($type(q)){case"object":for(var o in q){this.set(o,q[o]);}break;case"string":var m=Element.Properties.get(q);(m&&m.set)?m.set.apply(this,Array.slice(arguments,1)):this.setProperty(q,n);}return this;},get:function(n){var m=Element.Properties.get(n);return(m&&m.get)?m.get.apply(this,Array.slice(arguments,1)):this.getProperty(n);},erase:function(n){var m=Element.Properties.get(n);(m&&m.erase)?m.erase.apply(this):this.removeProperty(n);return this;},setProperty:function(n,o){var m=f[n];if(o==undefined){return this.removeProperty(n);}if(m&&b[n]){o=!!o;}(m)?this[m]=o:this.setAttribute(n,""+o);return this;},setProperties:function(m){for(var n in m){this.setProperty(n,m[n]);}return this;},getProperty:function(n){var m=f[n];var o=(m)?this[m]:this.getAttribute(n,2);return(b[n])?!!o:(m)?o:o||null;},getProperties:function(){var m=$A(arguments);return m.map(this.getProperty,this).associate(m);},removeProperty:function(n){var m=f[n];(m)?this[m]=(m&&b[n])?false:"":this.removeAttribute(n);return this;},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},hasClass:function(m){return this.className.contains(m," ");},addClass:function(m){if(!this.hasClass(m)){this.className=(this.className+" "+m).clean();}return this;},removeClass:function(m){this.className=this.className.replace(new RegExp("(^|\\s)"+m+"(?:\\s|$)"),"$1");return this;},toggleClass:function(m){return this.hasClass(m)?this.removeClass(m):this.addClass(m);},adopt:function(){Array.flatten(arguments).each(function(m){m=document.id(m,true);if(m){this.appendChild(m);}},this);return this;},appendText:function(n,m){return this.grab(this.getDocument().newTextNode(n),m);},grab:function(n,m){a[m||"bottom"](document.id(n,true),this);return this;},inject:function(n,m){a[m||"bottom"](this,document.id(n,true));return this;},replaces:function(m){m=document.id(m,true);m.parentNode.replaceChild(this,m);return this;},wraps:function(n,m){n=document.id(n,true);return this.replaces(n).grab(n,m);},getPrevious:function(m,n){return k(this,"previousSibling",null,m,false,n);},getAllPrevious:function(m,n){return k(this,"previousSibling",null,m,true,n);},getNext:function(m,n){return k(this,"nextSibling",null,m,false,n);},getAllNext:function(m,n){return k(this,"nextSibling",null,m,true,n);},getFirst:function(m,n){return k(this,"nextSibling","firstChild",m,false,n);},getLast:function(m,n){return k(this,"previousSibling","lastChild",m,false,n);},getParent:function(m,n){return k(this,"parentNode",null,m,false,n);},getParents:function(m,n){return k(this,"parentNode",null,m,true,n);},getSiblings:function(m,n){return this.getParent().getChildren(m,n).erase(this);},getChildren:function(m,n){return k(this,"nextSibling","firstChild",m,true,n);},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(p,o){var n=this.ownerDocument.getElementById(p);if(!n){return null;}for(var m=n.parentNode;m!=this;m=m.parentNode){if(!m){return null;}}return document.id(n,o);},getSelected:function(){return new Elements($A(this.options).filter(function(m){return m.selected;}));},getComputedStyle:function(n){if(this.currentStyle){return this.currentStyle[n.camelCase()];}var m=this.getDocument().defaultView.getComputedStyle(this,null);return(m)?m.getPropertyValue([n.hyphenate()]):null;},toQueryString:function(){var m=[];this.getElements("input, select, textarea",true).each(function(n){if(!n.name||n.disabled||n.type=="submit"||n.type=="reset"||n.type=="file"){return;}var o=(n.tagName.toLowerCase()=="select")?Element.getSelected(n).map(function(p){return p.value;}):((n.type=="radio"||n.type=="checkbox")&&!n.checked)?null:n.value;$splat(o).each(function(p){if(typeof p!="undefined"){m.push(n.name+"="+encodeURIComponent(p));}});});return m.join("&");},clone:function(p,m){p=p!==false;var s=this.cloneNode(p);var o=function(w,v){if(!m){w.removeAttribute("id");}if(Browser.Engine.trident){w.clearAttributes();w.mergeAttributes(v);w.removeAttribute("uid");if(w.options){var x=w.options,t=v.options;for(var u=x.length;u--;){x[u].selected=t[u].selected;}}}var y=j[v.tagName.toLowerCase()];if(y&&v[y]){w[y]=v[y];}};if(p){var q=s.getElementsByTagName("*"),r=this.getElementsByTagName("*");for(var n=q.length;n--;){o(q[n],r[n]);}}o(s,this);return document.id(s);},destroy:function(){Element.empty(this);Element.dispose(this);h(this,true);return null;},empty:function(){$A(this.childNodes).each(function(m){Element.destroy(m);});return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},hasChild:function(m){m=document.id(m,true);if(!m){return false;}if(Browser.Engine.webkit&&Browser.Engine.version<420){return $A(this.getElementsByTagName(m.tagName)).contains(m);}return(this.contains)?(this!=m&&this.contains(m)):!!(this.compareDocumentPosition(m)&16);},match:function(m){return(!m||(m==this)||(Element.get(this,"tag")==m));}});Native.implement([Element,Window,Document],{addListener:function(p,o){if(p=="unload"){var m=o,n=this;o=function(){n.removeListener("unload",o);m();};}else{i[this.uid]=this;}if(this.addEventListener){this.addEventListener(p,o,false);}else{this.attachEvent("on"+p,o);}return this;},removeListener:function(n,m){if(this.removeEventListener){this.removeEventListener(n,m,false);}else{this.detachEvent("on"+n,m);}return this;},retrieve:function(n,m){var p=c(this.uid),o=p[n];if(m!=undefined&&o==undefined){o=p[n]=m;}return $pick(o);},store:function(n,m){var o=c(this.uid);o[n]=m;return this;},eliminate:function(m){var n=c(this.uid);delete n[m];return this;}});window.addListener("unload",d);})();Element.Properties=new Hash;Element.Properties.style={set:function(a){this.style.cssText=a;},get:function(){return this.style.cssText;},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();}};Element.Properties.html=(function(){var c=document.createElement("div");var a={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};a.thead=a.tfoot=a.tbody;var b={set:function(){var f=Array.flatten(arguments).join("");var g=Browser.Engine.trident&&a[this.get("tag")];if(g){var h=c;h.innerHTML=g[1]+f+g[2];for(var d=g[0];d--;){h=h.firstChild;}this.empty().adopt(h.childNodes);}else{this.innerHTML=f;}}};b.erase=b.set;return b;})();if(Browser.Engine.webkit&&Browser.Engine.version<420){Element.Properties.text={get:function(){if(this.innerText){return this.innerText;}var a=this.ownerDocument.newElement("div",{html:this.innerHTML}).inject(this.ownerDocument.body);var b=a.innerText;a.destroy();return b;}};}(function(){Element.implement({scrollTo:function(i,j){if(b(this)){this.getWindow().scrollTo(i,j);}else{this.scrollLeft=i;this.scrollTop=j;}return this;},getSize:function(){if(b(this)){return this.getWindow().getSize();}return{x:this.offsetWidth,y:this.offsetHeight};},getScrollSize:function(){if(b(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};},getScroll:function(){if(b(this)){return this.getWindow().getScroll();}return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var j=this,i={x:0,y:0};while(j&&!b(j)){i.x+=j.scrollLeft;i.y+=j.scrollTop;j=j.parentNode;}return i;},getOffsetParent:function(){var i=this;if(b(i)){return null;}if(!Browser.Engine.trident){return i.offsetParent;}while((i=i.parentNode)&&!b(i)){if(d(i,"position")!="static"){return i;}}return null;},getOffsets:function(){if(this.getBoundingClientRect){var k=this.getBoundingClientRect(),n=document.id(this.getDocument().documentElement),q=n.getScroll(),l=this.getScrolls(),j=this.getScroll(),i=(d(this,"position")=="fixed");return{x:k.left.toInt()+l.x-j.x+((i)?0:q.x)-n.clientLeft,y:k.top.toInt()+l.y-j.y+((i)?0:q.y)-n.clientTop};}var m=this,o={x:0,y:0};if(b(this)){return o;}while(m&&!b(m)){o.x+=m.offsetLeft;o.y+=m.offsetTop;if(Browser.Engine.gecko){if(!g(m)){o.x+=c(m);o.y+=h(m);}var p=m.parentNode;if(p&&d(p,"overflow")!="visible"){o.x+=c(p);o.y+=h(p);}}else{if(m!=this&&Browser.Engine.webkit){o.x+=c(m);o.y+=h(m);}}m=m.offsetParent;}if(Browser.Engine.gecko&&!g(this)){o.x-=c(this);o.y-=h(this);}return o;},getPosition:function(l){if(b(this)){return{x:0,y:0};}var m=this.getOffsets(),j=this.getScrolls();var i={x:m.x-j.x,y:m.y-j.y};var k=(l&&(l=document.id(l)))?l.getPosition():{x:0,y:0};return{x:i.x-k.x,y:i.y-k.y};},getCoordinates:function(k){if(b(this)){return this.getWindow().getCoordinates();}var i=this.getPosition(k),j=this.getSize();var l={left:i.x,top:i.y,width:j.x,height:j.y};l.right=l.left+l.width;l.bottom=l.top+l.height;return l;},computePosition:function(i){return{left:i.x-f(this,"margin-left"),top:i.y-f(this,"margin-top")};},setPosition:function(i){return this.setStyles(this.computePosition(i));}});Native.implement([Document,Window],{getSize:function(){if(Browser.Engine.presto||Browser.Engine.webkit){var j=this.getWindow();return{x:j.innerWidth,y:j.innerHeight};}var i=a(this);return{x:i.clientWidth,y:i.clientHeight};},getScroll:function(){var j=this.getWindow(),i=a(this);return{x:j.pageXOffset||i.scrollLeft,y:j.pageYOffset||i.scrollTop};},getScrollSize:function(){var j=a(this),i=this.getSize();return{x:Math.max(j.scrollWidth,i.x),y:Math.max(j.scrollHeight,i.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var i=this.getSize();return{top:0,left:0,bottom:i.y,right:i.x,height:i.y,width:i.x};}});var d=Element.getComputedStyle;function f(i,j){return d(i,j).toInt()||0;}function g(i){return d(i,"-moz-box-sizing")=="border-box";}function h(i){return f(i,"border-top-width");}function c(i){return f(i,"border-left-width");}function b(i){return(/^(?:body|html)$/i).test(i.tagName);}function a(i){var j=i.getDocument();return(!j.compatMode||j.compatMode=="CSS1Compat")?j.html:j.body;}})();Element.alias("setPosition","position");Native.implement([Window,Document,Element],{getHeight:function(){return this.getSize().y;},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;},getLeft:function(){return this.getPosition().x;}});var Event=new Native({name:"Event",initialize:function(a,g){g=g||window;var l=g.document;a=a||g.event;if(a.$extended){return a;}this.$extended=true;var k=a.type;var h=a.target||a.srcElement;while(h&&h.nodeType==3){h=h.parentNode;}if(k.test(/key/)){var b=a.which||a.keyCode;var n=Event.Keys.keyOf(b);if(k=="keydown"){var d=b-111;if(d>0&&d<13){n="f"+d;}}n=n||String.fromCharCode(b).toLowerCase();}else{if(k.match(/(click|mouse|menu)/i)){l=(!l.compatMode||l.compatMode=="CSS1Compat")?l.html:l.body;var j={x:a.pageX||a.clientX+l.scrollLeft,y:a.pageY||a.clientY+l.scrollTop};var c={x:(a.pageX)?a.pageX-g.pageXOffset:a.clientX,y:(a.pageY)?a.pageY-g.pageYOffset:a.clientY};if(k.match(/DOMMouseScroll|mousewheel/)){var i=(a.wheelDelta)?a.wheelDelta/120:-(a.detail||0)/3;}var f=(a.which==3)||(a.button==2);var m=null;if(k.match(/over|out/)){switch(k){case"mouseover":m=a.relatedTarget||a.fromElement;break;case"mouseout":m=a.relatedTarget||a.toElement;}if(!(function(){while(m&&m.nodeType==3){m=m.parentNode;}return true;}).create({attempt:Browser.Engine.gecko})()){m=false;}}}}return $extend(this,{event:a,type:k,page:j,client:c,rightClick:f,wheel:i,relatedTarget:m,target:h,code:b,key:n,shift:a.shiftKey,control:a.ctrlKey,alt:a.altKey,meta:a.metaKey});}});Event.Keys=new Hash({enter:13,up:38,down:40,left:37,right:39,esc:27,space:32,backspace:8,tab:9,"delete":46});Event.implement({stop:function(){return this.stopPropagation().preventDefault();},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault();}else{this.event.returnValue=false;}return this;}});Element.Properties.events={set:function(a){this.addEvents(a);}};Native.implement([Element,Window,Document],{addEvent:function(f,h){var i=this.retrieve("events",{});i[f]=i[f]||{keys:[],values:[]};if(i[f].keys.contains(h)){return this;}i[f].keys.push(h);var g=f,a=Element.Events.get(f),c=h,j=this;if(a){if(a.onAdd){a.onAdd.call(this,h);}if(a.condition){c=function(k){if(a.condition.call(this,k)){return h.call(this,k);}return true;};}g=a.base||g;}var d=function(){return h.call(j);};var b=Element.NativeEvents[g];if(b){if(b==2){d=function(k){k=new Event(k,j.getWindow());if(c.call(j,k)===false){k.stop();}};}this.addListener(g,d);}i[f].values.push(d);return this;},removeEvent:function(c,b){var a=this.retrieve("events");if(!a||!a[c]){return this;}var g=a[c].keys.indexOf(b);if(g==-1){return this;}a[c].keys.splice(g,1);var f=a[c].values.splice(g,1)[0];var d=Element.Events.get(c);if(d){if(d.onRemove){d.onRemove.call(this,b);}c=d.base||c;}return(Element.NativeEvents[c])?this.removeListener(c,f):this;},addEvents:function(a){for(var b in a){this.addEvent(b,a[b]);}return this;},removeEvents:function(a){var c;if($type(a)=="object"){for(c in a){this.removeEvent(c,a[c]);}return this;}var b=this.retrieve("events");if(!b){return this;}if(!a){for(c in b){this.removeEvents(c);}this.eliminate("events");}else{if(b[a]){while(b[a].keys[0]){this.removeEvent(a,b[a].keys[0]);}b[a]=null;}}return this;},fireEvent:function(d,b,a){var c=this.retrieve("events");if(!c||!c[d]){return this;}c[d].keys.each(function(f){f.create({bind:this,delay:a,"arguments":b})();},this);return this;},cloneEvents:function(d,a){d=document.id(d);var c=d.retrieve("events");if(!c){return this;}if(!a){for(var b in c){this.cloneEvents(d,b);}}else{if(c[a]){c[a].keys.each(function(f){this.addEvent(a,f);},this);}}return this;}});try{if(typeof HTMLElement!="undefined"){HTMLElement.prototype.fireEvent=Element.prototype.fireEvent;}}catch(e){}Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,load:1,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1};(function(){var a=function(b){var c=b.relatedTarget;if(c==undefined){return true;}if(c===false){return false;}return($type(this)!="document"&&c!=this&&c.prefix!="xul"&&!this.hasChild(c));};Element.Events=new Hash({mouseenter:{base:"mouseover",condition:a},mouseleave:{base:"mouseout",condition:a},mousewheel:{base:(Browser.Engine.gecko)?"DOMMouseScroll":"mousewheel"}});})();Element.Properties.styles={set:function(a){this.setStyles(a);}};Element.Properties.opacity={set:function(a,b){if(!b){if(a==0){if(this.style.visibility!="hidden"){this.style.visibility="hidden";}}else{if(this.style.visibility!="visible"){this.style.visibility="visible";}}}if(!this.currentStyle||!this.currentStyle.hasLayout){this.style.zoom=1;}if(Browser.Engine.trident){this.style.filter=(a==1)?"":"alpha(opacity="+a*100+")";}this.style.opacity=a;this.store("opacity",a);},get:function(){return this.retrieve("opacity",1);}};Element.implement({setOpacity:function(a){return this.set("opacity",a,true);},getOpacity:function(){return this.get("opacity");},setStyle:function(b,a){switch(b){case"opacity":return this.set("opacity",parseFloat(a));case"float":b=(Browser.Engine.trident)?"styleFloat":"cssFloat";}b=b.camelCase();if($type(a)!="string"){var c=(Element.Styles.get(b)||"@").split(" ");a=$splat(a).map(function(f,d){if(!c[d]){return"";}return($type(f)=="number")?c[d].replace("@",Math.round(f)):f;}).join(" ");}else{if(a==String(Number(a))){a=Math.round(a);}}this.style[b]=a;return this;},getStyle:function(h){switch(h){case"opacity":return this.get("opacity");case"float":h=(Browser.Engine.trident)?"styleFloat":"cssFloat";}h=h.camelCase();var a=this.style[h];if(!$chk(a)){a=[];for(var g in Element.ShortStyles){if(h!=g){continue;}for(var f in Element.ShortStyles[g]){a.push(this.getStyle(f));}return a.join(" ");}a=this.getComputedStyle(h);}if(a){a=String(a);var c=a.match(/rgba?\([\d\s,]+\)/);if(c){a=a.replace(c[0],c[0].rgbToHex());}}if(Browser.Engine.presto||(Browser.Engine.trident&&!$chk(parseInt(a,10)))){if(h.test(/^(height|width)$/)){var b=(h=="width")?["left","right"]:["top","bottom"],d=0;b.each(function(i){d+=this.getStyle("border-"+i+"-width").toInt()+this.getStyle("padding-"+i).toInt();},this);return this["offset"+h.capitalize()]-d+"px";}if((Browser.Engine.presto)&&String(a).test("px")){return a;}if(h.test(/(border(.+)Width|margin|padding)/)){return"0px";}}return a;},setStyles:function(b){for(var a in b){this.setStyle(a,b[a]);}return this;},getStyles:function(){var a={};Array.flatten(arguments).each(function(b){a[b]=this.getStyle(b);},this);return a;}});Element.Styles=new Hash({left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"});Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(h){var g=Element.ShortStyles;var b=Element.Styles;["margin","padding"].each(function(i){var j=i+h;g[i][j]=b[j]="@px";});var f="border"+h;g.border[f]=b[f]="@px @ rgb(@, @, @)";var d=f+"Width",a=f+"Style",c=f+"Color";g[f]={};g.borderWidth[d]=g[f][d]=b[d]="@px";g.borderStyle[a]=g[f][a]=b[a]="@";g.borderColor[c]=g[f][c]=b[c]="rgb(@, @, @)";});var Fx=new Class({Implements:[Chain,Events,Options],options:{fps:50,unit:false,duration:500,link:"ignore"},initialize:function(a){this.subject=this.subject||this;this.setOptions(a);this.options.duration=Fx.Durations[this.options.duration]||this.options.duration.toInt();var b=this.options.wait;if(b===false){this.options.link="cancel";}},getTransition:function(){return function(a){return -(Math.cos(Math.PI*a)-1)/2;};},step:function(){var a=$time();if(a<this.time+this.options.duration){var b=this.transition((a-this.time)/this.options.duration);this.set(this.compute(this.from,this.to,b));}else{this.set(this.compute(this.from,this.to,1));this.complete();}},set:function(a){return a;},compute:function(c,b,a){return Fx.compute(c,b,a);},check:function(){if(!this.timer){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.bind(this,arguments));return false;}return false;},start:function(b,a){if(!this.check(b,a)){return this;}this.from=b;this.to=a;this.time=0;this.transition=this.getTransition();this.startTimer();this.onStart();return this;},complete:function(){if(this.stopTimer()){this.onComplete();}return this;},cancel:function(){if(this.stopTimer()){this.onCancel();}return this;},onStart:function(){this.fireEvent("start",this.subject);},onComplete:function(){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject);}},onCancel:function(){this.fireEvent("cancel",this.subject).clearChain();},pause:function(){this.stopTimer();return this;},resume:function(){this.startTimer();return this;},stopTimer:function(){if(!this.timer){return false;}this.time=$time()-this.time;this.timer=$clear(this.timer);return true;},startTimer:function(){if(this.timer){return false;}this.time=$time()-this.time;this.timer=this.step.periodical(Math.round(1000/this.options.fps),this);return true;}});Fx.compute=function(c,b,a){return(b-c)*a+c;};Fx.Durations={"short":250,normal:500,"long":1000};Fx.CSS=new Class({Extends:Fx,prepare:function(d,f,b){b=$splat(b);var c=b[1];if(!$chk(c)){b[1]=b[0];b[0]=d.getStyle(f);}var a=b.map(this.parse);return{from:a[0],to:a[1]};},parse:function(a){a=$lambda(a)();a=(typeof a=="string")?a.split(" "):$splat(a);return a.map(function(c){c=String(c);var b=false;Fx.CSS.Parsers.each(function(g,f){if(b){return;}var d=g.parse(c);if($chk(d)){b={value:d,parser:g};}});b=b||{value:c,parser:Fx.CSS.Parsers.String};return b;});},compute:function(d,c,b){var a=[];(Math.min(d.length,c.length)).times(function(f){a.push({value:d[f].parser.compute(d[f].value,c[f].value,b),parser:d[f].parser});});a.$family={name:"fx:css:value"};return a;},serve:function(c,b){if($type(c)!="fx:css:value"){c=this.parse(c);}var a=[];c.each(function(d){a=a.concat(d.parser.serve(d.value,b));});return a;},render:function(a,d,c,b){a.setStyle(d,this.serve(c,b));},search:function(a){if(Fx.CSS.Cache[a]){return Fx.CSS.Cache[a];}var b={};Array.each(document.styleSheets,function(f,d){var c=f.href;if(c&&c.contains("://")&&!c.contains(document.domain)){return;}var g=f.rules||f.cssRules;Array.each(g,function(k,h){if(!k.style){return;}var j=(k.selectorText)?k.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();}):null;if(!j||!j.test("^"+a+"$")){return;}Element.Styles.each(function(l,i){if(!k.style[i]||Element.ShortStyles[i]){return;}l=String(k.style[i]);b[i]=(l.test(/^rgb/))?l.rgbToHex():l;});});});return Fx.CSS.Cache[a]=b;}});Fx.CSS.Cache={};Fx.CSS.Parsers=new Hash({Color:{parse:function(a){if(a.match(/^#[0-9a-f]{3,6}$/i)){return a.hexToRgb(true);}return((a=a.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[a[1],a[2],a[3]]:false;},compute:function(c,b,a){return c.map(function(f,d){return Math.round(Fx.compute(c[d],b[d],a));});},serve:function(a){return a.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(b,a){return(a)?b+a:b;}},String:{parse:$lambda(false),compute:$arguments(1),serve:$arguments(0)}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);},set:function(a){if(typeof a=="string"){a=this.search(a);}for(var b in a){this.render(this.element,b,a[b],this.options.unit);}return this;},compute:function(f,d,c){var a={};for(var b in f){a[b]=this.parent(f[b],d[b],c);}return a;},start:function(b){if(!this.check(b)){return this;}if(typeof b=="string"){b=this.search(b);}var f={},d={};for(var c in b){var a=this.prepare(this.element,c,b[c]);f[c]=a.from;d[c]=a.to;}return this.parent(f,d);}});Element.Properties.morph={set:function(a){var b=this.retrieve("morph");if(b){b.cancel();}return this.eliminate("morph").store("morph:options",$extend({link:"cancel"},a));},get:function(a){if(a||!this.retrieve("morph")){if(a||!this.retrieve("morph:options")){this.set("morph",a);}this.store("morph",new Fx.Morph(this,this.retrieve("morph:options")));}return this.retrieve("morph");}};Element.implement({morph:function(a){this.get("morph").start(a);return this;}});Fx.implement({getTransition:function(){var a=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof a=="string"){var b=a.split(":");a=Fx.Transitions;a=a[b[0]]||a[b[0].capitalize()];if(b[1]){a=a["ease"+b[1].capitalize()+(b[2]?b[2].capitalize():"")];}}return a;}});Fx.Transition=function(b,a){a=$splat(a);return $extend(b,{easeIn:function(c){return b(c,a);},easeOut:function(c){return 1-b(1-c,a);},easeInOut:function(c){return(c<=0.5)?b(2*c,a)/2:(2-b(2*(1-c),a))/2;}});};Fx.Transitions=new Hash({linear:$arguments(0)});Fx.Transitions.extend=function(a){for(var b in a){Fx.Transitions[b]=new Fx.Transition(a[b]);}};Fx.Transitions.extend({Pow:function(b,a){return Math.pow(b,a[0]||6);},Expo:function(a){return Math.pow(2,8*(a-1));},Circ:function(a){return 1-Math.sin(Math.acos(a));},Sine:function(a){return 1-Math.sin((1-a)*Math.PI/2);},Back:function(b,a){a=a[0]||1.618;return Math.pow(b,2)*((a+1)*b-a);},Bounce:function(g){var f;for(var d=0,c=1;1;d+=c,c/=2){if(g>=(7-4*d)/11){f=c*c-Math.pow((11-6*d-11*g)/4,2);break;}}return f;},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,[a+2]);});});Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);},set:function(b,a){if(arguments.length==1){a=b;b=this.property||this.options.property;}this.render(this.element,b,a,this.options.unit);return this;},start:function(c,f,d){if(!this.check(c,f,d)){return this;}var b=Array.flatten(arguments);this.property=this.options.property||b.shift();var a=this.prepare(this.element,this.property,b);return this.parent(a.from,a.to);}});Element.Properties.tween={set:function(a){var b=this.retrieve("tween");if(b){b.cancel();}return this.eliminate("tween").store("tween:options",$extend({link:"cancel"},a));},get:function(a){if(a||!this.retrieve("tween")){if(a||!this.retrieve("tween:options")){this.set("tween",a);}this.store("tween",new Fx.Tween(this,this.retrieve("tween:options")));}return this.retrieve("tween");}};Element.implement({tween:function(a,c,b){this.get("tween").start(arguments);return this;},fade:function(c){var f=this.get("tween"),d="opacity",a;c=$pick(c,"toggle");switch(c){case"in":f.start(d,1);break;case"out":f.start(d,0);break;case"show":f.set(d,1);break;case"hide":f.set(d,0);break;case"toggle":var b=this.retrieve("fade:flag",this.get("opacity")==1);f.start(d,(b)?0:1);this.store("fade:flag",!b);a=true;break;default:f.start(d,arguments);}if(!a){this.eliminate("fade:flag");}return this;},highlight:function(c,a){if(!a){a=this.retrieve("highlight:original",this.getStyle("background-color"));a=(a=="transparent")?"#fff":a;}var b=this.get("tween");b.start("background-color",c||"#ffff88",a).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));b.callChain();}.bind(this));return this;}});var Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,noCache:false},initialize:function(a){this.xhr=new Browser.Request();this.setOptions(a);this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.headers=new Hash(this.options.headers);},onStateChange:function(){if(this.xhr.readyState!=4||!this.running){return;}this.running=false;this.status=0;$try(function(){this.status=this.xhr.status;}.bind(this));this.xhr.onreadystatechange=$empty;if(this.options.isSuccess.call(this,this.status)){this.response={text:this.xhr.responseText,xml:this.xhr.responseXML};this.success(this.response.text,this.response.xml);}else{this.response={text:null,xml:null};this.failure();}},isSuccess:function(){return((this.status>=200)&&(this.status<300));},processScripts:function(a){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return $exec(a);}return a.stripScripts(this.options.evalScripts);},success:function(b,a){this.onSuccess(this.processScripts(b),a);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain();},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},setHeader:function(a,b){this.headers.set(a,b);return this;},getHeader:function(a){return $try(function(){return this.xhr.getResponseHeader(a);}.bind(this));},check:function(){if(!this.running){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.bind(this,arguments));return false;}return false;},send:function(l){if(!this.check(l)){return this;}this.running=true;var j=$type(l);if(j=="string"||j=="element"){l={data:l};}var d=this.options;l=$extend({data:d.data,url:d.url,method:d.method},l);var h=l.data,b=String(l.url),a=l.method.toLowerCase();switch($type(h)){case"element":h=document.id(h).toQueryString();break;case"object":case"hash":h=Hash.toQueryString(h);}if(this.options.format){var k="format="+this.options.format;h=(h)?k+"&"+h:k;}if(this.options.emulation&&!["get","post"].contains(a)){var i="_method="+a;h=(h)?i+"&"+h:i;a="post";}if(this.options.urlEncoded&&a=="post"){var c=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers.set("Content-type","application/x-www-form-urlencoded"+c);}if(this.options.noCache){var g="noCache="+new Date().getTime();h=(h)?g+"&"+h:g;}var f=b.lastIndexOf("/");if(f>-1&&(f=b.indexOf("#"))>-1){b=b.substr(0,f);}if(h&&a=="get"){b=b+(b.contains("?")?"&":"?")+h;h=null;}this.xhr.open(a.toUpperCase(),b,this.options.async);this.xhr.onreadystatechange=this.onStateChange.bind(this);this.headers.each(function(n,m){try{this.xhr.setRequestHeader(m,n);}catch(o){this.fireEvent("exception",[m,n]);}},this);this.fireEvent("request");this.xhr.send(h);if(!this.options.async){this.onStateChange();}return this;},cancel:function(){if(!this.running){return this;}this.running=false;this.xhr.abort();this.xhr.onreadystatechange=$empty;this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});(function(){var a={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(b){a[b]=function(){var c=Array.link(arguments,{url:String.type,data:$defined});return this.send($extend(c,{method:b}));};});Request.implement(a);})();Element.Properties.send={set:function(a){var b=this.retrieve("send");if(b){b.cancel();}return this.eliminate("send").store("send:options",$extend({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")},a));},get:function(a){if(a||!this.retrieve("send")){if(a||!this.retrieve("send:options")){this.set("send",a);}this.store("send",new Request(this.retrieve("send:options")));}return this.retrieve("send");}};Element.implement({send:function(a){var b=this.get("send");b.send({data:this,url:a||b.options.url});return this;}});Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false},processHTML:function(c){var b=c.match(/<body[^>]*>([\s\S]*?)<\/body>/i);c=(b)?b[1]:c;var a=new Element("div");return $try(function(){var d="<root>"+c+"</root>",h;if(Browser.Engine.trident){h=new ActiveXObject("Microsoft.XMLDOM");h.async=false;h.loadXML(d);}else{h=new DOMParser().parseFromString(d,"text/xml");}d=h.getElementsByTagName("root")[0];if(!d){return null;}for(var g=0,f=d.childNodes.length;g<f;g++){var j=Element.clone(d.childNodes[g],true,true);if(j){a.grab(j);}}return a;})||a.set("html",c);},success:function(d){var c=this.options,b=this.response;b.html=d.stripScripts(function(f){b.javascript=f;});var a=this.processHTML(b.html);b.tree=a.childNodes;b.elements=a.getElements("*");if(c.filter){b.tree=b.elements.filter(c.filter);}if(c.update){document.id(c.update).empty().set("html",b.html);}else{if(c.append){document.id(c.append).adopt(a.getChildren());}}if(c.evalScripts){$exec(b.javascript);}this.onSuccess(b.tree,b.elements,b.html,b.javascript);}});Element.Properties.load={set:function(a){var b=this.retrieve("load");if(b){b.cancel();}return this.eliminate("load").store("load:options",$extend({data:this,link:"cancel",update:this,method:"get"},a));},get:function(a){if(a||!this.retrieve("load")){if(a||!this.retrieve("load:options")){this.set("load",a);}this.store("load",new Request.HTML(this.retrieve("load:options")));}return this.retrieve("load");}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Object.type,url:String.type}));return this;}});var JSON=new Hash(this.JSON&&{stringify:JSON.stringify,parse:JSON.parse}).extend({$specialChars:{"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},$replaceChars:function(a){return JSON.$specialChars[a]||"\\u00"+Math.floor(a.charCodeAt()/16).toString(16)+(a.charCodeAt()%16).toString(16);},encode:function(b){switch($type(b)){case"string":return'"'+b.replace(/[\x00-\x1f\\"]/g,JSON.$replaceChars)+'"';case"array":return"["+String(b.map(JSON.encode).clean())+"]";case"object":case"hash":var a=[];Hash.each(b,function(f,d){var c=JSON.encode(f);if(c){a.push(JSON.encode(d)+":"+c);}});return"{"+a+"}";case"number":case"boolean":return String(b);case false:return"null";}return null;},decode:function(string,secure){if($type(string)!="string"||!string.length){return null;}if(secure&&!(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g,"@").replace(/"[^"\\\n\r]*"/g,""))){return null;}return eval("("+string+")");}});Request.JSON=new Class({Extends:Request,options:{secure:true},initialize:function(a){this.parent(a);this.headers.extend({Accept:"application/json","X-Request":"JSON"});},success:function(a){this.response.json=JSON.decode(a,this.options.secure);this.onSuccess(this.response.json,a);}});var Cookie=new Class({Implements:Options,options:{path:false,domain:false,duration:false,secure:false,document:document},initialize:function(b,a){this.key=b;this.setOptions(a);},write:function(b){b=encodeURIComponent(b);if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path;}if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure";}this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,$merge(this.options,{duration:-1})).write("");return this;}});Cookie.write=function(b,c,a){return new Cookie(b,a).write(c);};Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();};Element.Events.domready={onAdd:function(a){if(Browser.loaded){a.call(this);}}};(function(){var b=function(){if(Browser.loaded){return;}Browser.loaded=true;window.fireEvent("domready");document.fireEvent("domready");};window.addEvent("load",b);if(Browser.Engine.trident){var a=document.createElement("div");(function(){($try(function(){a.doScroll();return document.id(a).inject(document.body).set("html","temp").dispose();}))?b():arguments.callee.delay(50);})();}else{if(Browser.Engine.webkit&&Browser.Engine.version<525){(function(){(["loaded","complete"].contains(document.readyState))?b():arguments.callee.delay(50);})();}else{document.addEvent("DOMContentLoaded",b);}}})();Native.implement([Document,Element],{getElements:function(j,h){j=j.split(",");var c,f={};for(var d=0,b=j.length;d<b;d++){var a=j[d],g=Selectors.Utils.search(this,a,f);if(d!=0&&g.item){g=$A(g);}c=(d==0)?g:(c.item)?$A(c).concat(g):c.concat(g);}return new Elements(c,{ddup:(j.length>1),cash:!h});}});Element.implement({match:function(b){if(!b||(b==this)){return true;}var d=Selectors.Utils.parseTagAndID(b);var a=d[0],f=d[1];if(!Selectors.Filters.byID(this,f)||!Selectors.Filters.byTag(this,a)){return false;}var c=Selectors.Utils.parseSelector(b);return(c)?Selectors.Utils.filter(this,c,{}):true;}});var Selectors={Cache:{nth:{},parsed:{}}};Selectors.RegExps={id:(/#([\w-]+)/),tag:(/^(\w+|\*)/),quick:(/^(\w+|\*)$/),splitter:(/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),combined:(/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)};Selectors.Utils={chk:function(b,c){if(!c){return true;}var a=$uid(b);if(!c[a]){return c[a]=true;}return false;},parseNthArgument:function(i){if(Selectors.Cache.nth[i]){return Selectors.Cache.nth[i];}var f=i.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);if(!f){return false;}var h=parseInt(f[1],10);var d=(h||h===0)?h:1;var g=f[2]||false;var c=parseInt(f[3],10)||0;if(d!=0){c--;while(c<1){c+=d;}while(c>=d){c-=d;}}else{d=c;g="index";}switch(g){case"n":f={a:d,b:c,special:"n"};break;case"odd":f={a:2,b:0,special:"n"};break;case"even":f={a:2,b:1,special:"n"};break;case"first":f={a:0,special:"index"};break;case"last":f={special:"last-child"};break;case"only":f={special:"only-child"};break;default:f={a:(d-1),special:"index"};}return Selectors.Cache.nth[i]=f;},parseSelector:function(f){if(Selectors.Cache.parsed[f]){return Selectors.Cache.parsed[f];}var d,i={classes:[],pseudos:[],attributes:[]};while((d=Selectors.RegExps.combined.exec(f))){var j=d[1],h=d[2],g=d[3],b=d[5],c=d[6],k=d[7];if(j){i.classes.push(j);}else{if(c){var a=Selectors.Pseudo.get(c);if(a){i.pseudos.push({parser:a,argument:k});}else{i.attributes.push({name:c,operator:"=",value:k});}}else{if(h){i.attributes.push({name:h,operator:g,value:b});}}}}if(!i.classes.length){delete i.classes;}if(!i.attributes.length){delete i.attributes;}if(!i.pseudos.length){delete i.pseudos;}if(!i.classes&&!i.attributes&&!i.pseudos){i=null;}return Selectors.Cache.parsed[f]=i;},parseTagAndID:function(b){var a=b.match(Selectors.RegExps.tag);var c=b.match(Selectors.RegExps.id);return[(a)?a[1]:"*",(c)?c[1]:false];},filter:function(g,c,f){var d;if(c.classes){for(d=c.classes.length;d--;d){var h=c.classes[d];if(!Selectors.Filters.byClass(g,h)){return false;}}}if(c.attributes){for(d=c.attributes.length;d--;d){var b=c.attributes[d];if(!Selectors.Filters.byAttribute(g,b.name,b.operator,b.value)){return false;}}}if(c.pseudos){for(d=c.pseudos.length;d--;d){var a=c.pseudos[d];if(!Selectors.Filters.byPseudo(g,a.parser,a.argument,f)){return false;}}}return true;},getByTagAndID:function(b,a,d){if(d){var c=(b.getElementById)?b.getElementById(d,true):Element.getElementById(b,d,true);return(c&&Selectors.Filters.byTag(c,a))?[c]:[];}else{return b.getElementsByTagName(a);}},search:function(p,o,u){var b=[];var c=o.trim().replace(Selectors.RegExps.splitter,function(k,j,i){b.push(j);return":)"+i;}).split(":)");var q,f,B;for(var A=0,w=c.length;A<w;A++){var z=c[A];if(A==0&&Selectors.RegExps.quick.test(z)){q=p.getElementsByTagName(z);continue;}var a=b[A-1];var r=Selectors.Utils.parseTagAndID(z);var C=r[0],s=r[1];if(A==0){q=Selectors.Utils.getByTagAndID(p,C,s);}else{var d={},h=[];for(var y=0,x=q.length;y<x;y++){h=Selectors.Getters[a](h,q[y],C,s,d);}q=h;}var g=Selectors.Utils.parseSelector(z);if(g){f=[];for(var v=0,t=q.length;v<t;v++){B=q[v];if(Selectors.Utils.filter(B,g,u)){f.push(B);}}q=f;}}return q;}};Selectors.Getters={" ":function(j,h,k,a,f){var d=Selectors.Utils.getByTagAndID(h,k,a);for(var c=0,b=d.length;c<b;c++){var g=d[c];if(Selectors.Utils.chk(g,f)){j.push(g);}}return j;},">":function(j,h,k,a,g){var c=Selectors.Utils.getByTagAndID(h,k,a);for(var f=0,d=c.length;f<d;f++){var b=c[f];if(b.parentNode==h&&Selectors.Utils.chk(b,g)){j.push(b);}}return j;},"+":function(c,b,a,f,d){while((b=b.nextSibling)){if(b.nodeType==1){if(Selectors.Utils.chk(b,d)&&Selectors.Filters.byTag(b,a)&&Selectors.Filters.byID(b,f)){c.push(b);}break;}}return c;},"~":function(c,b,a,f,d){while((b=b.nextSibling)){if(b.nodeType==1){if(!Selectors.Utils.chk(b,d)){break;}if(Selectors.Filters.byTag(b,a)&&Selectors.Filters.byID(b,f)){c.push(b);}}}return c;}};Selectors.Filters={byTag:function(b,a){return(a=="*"||(b.tagName&&b.tagName.toLowerCase()==a));},byID:function(a,b){return(!b||(a.id&&a.id==b));},byClass:function(b,a){return(b.className&&b.className.contains&&b.className.contains(a," "));},byPseudo:function(a,d,c,b){return d.call(a,c,b);},byAttribute:function(c,d,b,f){var a=Element.prototype.getProperty.call(c,d);if(!a){return(b=="!=");}if(!b||f==undefined){return true;}switch(b){case"=":return(a==f);case"*=":return(a.contains(f));case"^=":return(a.substr(0,f.length)==f);case"$=":return(a.substr(a.length-f.length)==f);case"!=":return(a!=f);case"~=":return a.contains(f," ");case"|=":return a.contains(f,"-");}return false;}};Selectors.Pseudo=new Hash({checked:function(){return this.checked;},empty:function(){return !(this.innerText||this.textContent||"").length;},not:function(a){return !Element.match(this,a);},contains:function(a){return(this.innerText||this.textContent||"").contains(a);},"first-child":function(){return Selectors.Pseudo.index.call(this,0);},"last-child":function(){var a=this;while((a=a.nextSibling)){if(a.nodeType==1){return false;}}return true;},"only-child":function(){var b=this;while((b=b.previousSibling)){if(b.nodeType==1){return false;}}var a=this;while((a=a.nextSibling)){if(a.nodeType==1){return false;}}return true;},"nth-child":function(h,f){h=(h==undefined)?"n":h;var c=Selectors.Utils.parseNthArgument(h);if(c.special!="n"){return Selectors.Pseudo[c.special].call(this,c.a,f);}var g=0;f.positions=f.positions||{};var d=$uid(this);if(!f.positions[d]){var b=this;while((b=b.previousSibling)){if(b.nodeType!=1){continue;}g++;var a=f.positions[$uid(b)];if(a!=undefined){g=a+g;break;}}f.positions[d]=g;}return(f.positions[d]%c.a==c.b);},index:function(a){var b=this,c=0;while((b=b.previousSibling)){if(b.nodeType==1&&++c>a){return false;}}return(c==a);},even:function(b,a){return Selectors.Pseudo["nth-child"].call(this,"2n+1",a);},odd:function(b,a){return Selectors.Pseudo["nth-child"].call(this,"2n",a);},selected:function(){return this.selected;},enabled:function(){return(this.disabled===false);}});var Swiff=new Class({Implements:[Options],options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"transparent",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object;},initialize:function(m,n){this.instance="Swiff_"+$time();this.setOptions(n);n=this.options;var b=this.id=n.id||this.instance;var a=document.id(n.container);Swiff.CallBacks[this.instance]={};var f=n.params,h=n.vars,g=n.callBacks;var i=$extend({height:n.height,width:n.width},n.properties);var l=this;for(var d in g){Swiff.CallBacks[this.instance][d]=(function(o){return function(){return o.apply(l.object,arguments);};})(g[d]);h[d]="Swiff.CallBacks."+this.instance+"."+d;}f.flashVars=Hash.toQueryString(h);if(Browser.Engine.trident){i.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";f.movie=m;}else{i.type="application/x-shockwave-flash";i.data=m;}var k='<object id="'+b+'"';for(var j in i){k+=" "+j+'="'+i[j]+'"';}k+=">";for(var c in f){if(f[c]){k+='<param name="'+c+'" value="'+f[c]+'" />';}}k+="</object>";this.object=((a)?a.empty():new Element("div")).set("html",k).firstChild;},replaces:function(a){a=document.id(a,true);a.parentNode.replaceChild(this.toElement(),a);return this;},inject:function(a){document.id(a,true).appendChild(this.toElement());return this;},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].extend(arguments));}});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction('<invoke name="'+fn+'" returntype="javascript">'+__flash__argumentsToXML(arguments,2)+"</invoke>");return eval(rs);};
  5. //MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2009 Aaron Newton <http://clientcide.com/>, Valerio Proietti <http://mad4milk.net> & the MooTools team <http://mootools.net/developers>, MIT Style License.
  6.  
  7. MooTools.More={version:"1.2.5.1",build:"254884f2b83651bf95260eed5c6cceb838e22d8e"};Class.Mutators.Binds=function(a){return a;};Class.Mutators.initialize=function(a){return function(){$splat(this.Binds).each(function(b){var c=this[b];
  8. if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);};};Element.implement({measure:function(e){var g=function(h){return !!(!h||h.offsetHeight||h.offsetWidth);
  9. };if(g(this)){return e.apply(this);}var d=this.getParent(),f=[],b=[];while(!g(d)&&d!=document.body){b.push(d.expose());d=d.getParent();}var c=this.expose();
  10. var a=e.apply(this);c();b.each(function(h){h();});return a;},expose:function(){if(this.getStyle("display")!="none"){return $empty;}var a=this.style.cssText;
  11. this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=a;}.bind(this);},getDimensions:function(a){a=$merge({computeSize:false},a);
  12. var f={};var d=function(g,e){return(e.computeSize)?g.getComputedSize(e):g.getSize();};var b=this.getParent("body");if(b&&this.getStyle("display")=="none"){f=this.measure(function(){return d(this,a);
  13. });}else{if(b){try{f=d(this,a);}catch(c){}}else{f={x:0,y:0};}}return $chk(f.x)?$extend(f,{width:f.x,height:f.y}):$extend(f,{x:f.width,y:f.height});},getComputedSize:function(a){if(a&&a.plains){a.planes=a.plains;
  14. }a=$merge({styles:["padding","border"],planes:{height:["top","bottom"],width:["left","right"]},mode:"both"},a);var c={width:0,height:0};switch(a.mode){case"vertical":delete c.width;
  15. delete a.planes.width;break;case"horizontal":delete c.height;delete a.planes.height;break;}var b=[];$each(a.planes,function(f,g){f.each(function(h){a.styles.each(function(i){b.push((i=="border")?i+"-"+h+"-width":i+"-"+h);
  16. });});});var e={};b.each(function(f){e[f]=this.getComputedStyle(f);},this);var d=[];$each(a.planes,function(f,g){var h=g.capitalize();c["total"+h]=c["computed"+h]=0;
  17. f.each(function(i){c["computed"+i.capitalize()]=0;b.each(function(k,j){if(k.test(i)){e[k]=e[k].toInt()||0;c["total"+h]=c["total"+h]+e[k];c["computed"+i.capitalize()]=c["computed"+i.capitalize()]+e[k];
  18. }if(k.test(i)&&g!=k&&(k.test("border")||k.test("padding"))&&!d.contains(k)){d.push(k);c["computed"+h]=c["computed"+h]-e[k];}});});});["Width","Height"].each(function(g){var f=g.toLowerCase();
  19. if(!$chk(c[f])){return;}c[f]=c[f]+this["offset"+g]+c["computed"+g];c["total"+g]=c[f]+c["total"+g];delete c["computed"+g];},this);return $extend(e,c);}});
  20. Fx.Elements=new Class({Extends:Fx.CSS,initialize:function(b,a){this.elements=this.subject=$$(b);this.parent(a);},compute:function(g,h,j){var c={};for(var d in g){var a=g[d],e=h[d],f=c[d]={};
  21. for(var b in a){f[b]=this.parent(a[b],e[b],j);}}return c;},set:function(b){for(var c in b){if(!this.elements[c]){continue;}var a=b[c];for(var d in a){this.render(this.elements[c],d,a[d],this.options.unit);
  22. }}return this;},start:function(c){if(!this.check(c)){return this;}var h={},j={};for(var d in c){if(!this.elements[d]){continue;}var f=c[d],a=h[d]={},g=j[d]={};
  23. for(var b in f){var e=this.prepare(this.elements[d],b,f[b]);a[b]=e.from;g[b]=e.to;}}return this.parent(h,j);}});Fx.Accordion=new Class({Extends:Fx.Elements,options:{fixedHeight:false,fixedWidth:false,display:0,show:false,height:true,width:false,opacity:true,alwaysHide:false,trigger:"click",initialDisplayFx:true,returnHeightToAuto:true},initialize:function(){var c=Array.link(arguments,{container:Element.type,options:Object.type,togglers:$defined,elements:$defined});
  24. this.parent(c.elements,c.options);this.togglers=$$(c.togglers);this.previous=-1;this.internalChain=new Chain();if(this.options.alwaysHide){this.options.wait=true;
  25. }if($chk(this.options.show)){this.options.display=false;this.previous=this.options.show;}if(this.options.start){this.options.display=false;this.options.show=false;
  26. }this.effects={};if(this.options.opacity){this.effects.opacity="fullOpacity";}if(this.options.width){this.effects.width=this.options.fixedWidth?"fullWidth":"offsetWidth";
  27. }if(this.options.height){this.effects.height=this.options.fixedHeight?"fullHeight":"scrollHeight";}for(var b=0,a=this.togglers.length;b<a;b++){this.addSection(this.togglers[b],this.elements[b]);
  28. }this.elements.each(function(e,d){if(this.options.show===d){this.fireEvent("active",[this.togglers[d],e]);}else{for(var f in this.effects){e.setStyle(f,0);
  29. }}},this);if($chk(this.options.display)||this.options.initialDisplayFx===false){this.display(this.options.display,this.options.initialDisplayFx);}if(this.options.fixedHeight!==false){this.options.returnHeightToAuto=false;
  30. }this.addEvent("complete",this.internalChain.callChain.bind(this.internalChain));},addSection:function(e,c){e=document.id(e);c=document.id(c);var f=this.togglers.contains(e);
  31. this.togglers.include(e);this.elements.include(c);var a=this.togglers.indexOf(e);var b=this.display.bind(this,a);e.store("accordion:display",b);e.addEvent(this.options.trigger,b);
  32. if(this.options.height){c.setStyles({"padding-top":0,"border-top":"none","padding-bottom":0,"border-bottom":"none"});}if(this.options.width){c.setStyles({"padding-left":0,"border-left":"none","padding-right":0,"border-right":"none"});
  33. }c.fullOpacity=1;if(this.options.fixedWidth){c.fullWidth=this.options.fixedWidth;}if(this.options.fixedHeight){c.fullHeight=this.options.fixedHeight;}c.setStyle("overflow","hidden");
  34. if(!f){for(var d in this.effects){c.setStyle(d,0);}}return this;},removeSection:function(e,b){var a=this.togglers.indexOf(e);var c=this.elements[a];var d=function(){this.togglers.erase(e);
  35. this.elements.erase(c);this.detach(e);}.bind(this);if(this.now==a||b!=undefined){this.display($pick(b,a-1>=0?a-1:0)).chain(d);}else{d();}return this;},detach:function(b){var a=function(c){c.removeEvent(this.options.trigger,c.retrieve("accordion:display"));
  36. }.bind(this);if(!b){this.togglers.each(a);}else{a(b);}return this;},display:function(a,b){if(!this.check(a,b)){return this;}b=$pick(b,true);a=($type(a)=="element")?this.elements.indexOf(a):a;
  37. if(a==this.previous&&!this.options.alwaysHide){return this;}if(this.options.returnHeightToAuto){var d=this.elements[this.previous];if(d&&!this.selfHidden){for(var c in this.effects){d.setStyle(c,d[this.effects[c]]);
  38. }}}if((this.timer&&this.options.wait)||(a===this.previous&&!this.options.alwaysHide)){return this;}this.previous=a;var e={};this.elements.each(function(h,g){e[g]={};
  39. var f;if(g!=a){f=true;}else{if(this.options.alwaysHide&&((h.offsetHeight>0&&this.options.height)||h.offsetWidth>0&&this.options.width)){f=true;this.selfHidden=true;
  40. }}this.fireEvent(f?"background":"active",[this.togglers[g],h]);for(var j in this.effects){e[g][j]=f?0:h[this.effects[j]];}},this);this.internalChain.clearChain();
  41. this.internalChain.chain(function(){if(this.options.returnHeightToAuto&&!this.selfHidden){var f=this.elements[a];if(f){f.setStyle("height","auto");}}}.bind(this));
  42. return b?this.start(e):this.set(e);}});var Accordion=new Class({Extends:Fx.Accordion,initialize:function(){this.parent.apply(this,arguments);var a=Array.link(arguments,{container:Element.type});
  43. this.container=a.container;},addSection:function(c,b,e){c=document.id(c);b=document.id(b);var d=this.togglers.contains(c);var a=this.togglers.length;if(a&&(!d||e)){e=$pick(e,a-1);
  44. c.inject(this.togglers[e],"before");b.inject(c,"after");}else{if(this.container&&!d){c.inject(this.container);b.inject(this.container);}}return this.parent.apply(this,arguments);
  45. }});Fx.Scroll=new Class({Extends:Fx,options:{offset:{x:0,y:0},wheelStops:true},initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);
  46. var d=this.cancel.bind(this,false);if($type(this.element)!="element"){this.element=document.id(this.element.getDocument().body);}var c=this.element;if(this.options.wheelStops){this.addEvent("start",function(){c.addEvent("mousewheel",d);
  47. },true);this.addEvent("complete",function(){c.removeEvent("mousewheel",d);},true);}},set:function(){var a=Array.flatten(arguments);if(Browser.Engine.gecko){a=[Math.round(a[0]),Math.round(a[1])];
  48. }this.element.scrollTo(a[0]+this.options.offset.x,a[1]+this.options.offset.y);},compute:function(c,b,a){return[0,1].map(function(d){return Fx.compute(c[d],b[d],a);
  49. });},start:function(c,g){if(!this.check(c,g)){return this;}var e=this.element.getScrollSize(),b=this.element.getScroll(),d={x:c,y:g};for(var f in d){var a=e[f];
  50. if($chk(d[f])){d[f]=($type(d[f])=="number")?d[f]:a;}else{d[f]=b[f];}d[f]+=this.options.offset[f];}return this.parent([b.x,b.y],[d.x,d.y]);},toTop:function(){return this.start(false,0);
  51. },toLeft:function(){return this.start(0,false);},toRight:function(){return this.start("right",false);},toBottom:function(){return this.start(false,"bottom");
  52. },toElement:function(b){var a=document.id(b).getPosition(this.element);return this.start(a.x,a.y);},scrollIntoView:function(c,e,d){e=e?$splat(e):["x","y"];
  53. var h={};c=document.id(c);var f=c.getPosition(this.element);var i=c.getSize();var g=this.element.getScroll();var a=this.element.getSize();var b={x:f.x+i.x,y:f.y+i.y};
  54. ["x","y"].each(function(j){if(e.contains(j)){if(b[j]>g[j]+a[j]){h[j]=b[j]-a[j];}if(f[j]<g[j]){h[j]=f[j];}}if(h[j]==null){h[j]=g[j];}if(d&&d[j]){h[j]=h[j]+d[j];
  55. }},this);if(h.x!=g.x||h.y!=g.y){this.start(h.x,h.y);}return this;},scrollToCenter:function(c,e,d){e=e?$splat(e):["x","y"];c=$(c);var h={},f=c.getPosition(this.element),i=c.getSize(),g=this.element.getScroll(),a=this.element.getSize(),b={x:f.x+i.x,y:f.y+i.y};
  56. ["x","y"].each(function(j){if(e.contains(j)){h[j]=f[j]-(a[j]-i[j])/2;}if(h[j]==null){h[j]=g[j];}if(d&&d[j]){h[j]=h[j]+d[j];}},this);if(h.x!=g.x||h.y!=g.y){this.start(h.x,h.y);
  57. }return this;}});Fx.Slide=new Class({Extends:Fx,options:{mode:"vertical",wrapper:false,hideOverflow:true,resetHeight:false},initialize:function(b,a){this.addEvent("complete",function(){this.open=(this.wrapper["offset"+this.layout.capitalize()]!=0);
  58. if(this.open&&this.options.resetHeight){this.wrapper.setStyle("height","");}if(this.open&&Browser.Engine.webkit419){this.element.dispose().inject(this.wrapper);
  59. }},true);this.element=this.subject=document.id(b);this.parent(a);var d=this.element.retrieve("wrapper");var c=this.element.getStyles("margin","position","overflow");
  60. if(this.options.hideOverflow){c=$extend(c,{overflow:"hidden"});}if(this.options.wrapper){d=document.id(this.options.wrapper).setStyles(c);}this.wrapper=d||new Element("div",{styles:c}).wraps(this.element);
  61. this.element.store("wrapper",this.wrapper).setStyle("margin",0);this.now=[];this.open=true;},vertical:function(){this.margin="margin-top";this.layout="height";
  62. this.offset=this.element.offsetHeight;},horizontal:function(){this.margin="margin-left";this.layout="width";this.offset=this.element.offsetWidth;},set:function(a){this.element.setStyle(this.margin,a[0]);
  63. this.wrapper.setStyle(this.layout,a[1]);return this;},compute:function(c,b,a){return[0,1].map(function(d){return Fx.compute(c[d],b[d],a);});},start:function(b,e){if(!this.check(b,e)){return this;
  64. }this[e||this.options.mode]();var d=this.element.getStyle(this.margin).toInt();var c=this.wrapper.getStyle(this.layout).toInt();var a=[[d,c],[0,this.offset]];
  65. var g=[[d,c],[-this.offset,0]];var f;switch(b){case"in":f=a;break;case"out":f=g;break;case"toggle":f=(c==0)?a:g;}return this.parent(f[0],f[1]);},slideIn:function(a){return this.start("in",a);
  66. },slideOut:function(a){return this.start("out",a);},hide:function(a){this[a||this.options.mode]();this.open=false;return this.set([-this.offset,0]);},show:function(a){this[a||this.options.mode]();
  67. this.open=true;return this.set([0,this.offset]);},toggle:function(a){return this.start("toggle",a);}});Element.Properties.slide={set:function(b){var a=this.retrieve("slide");
  68. if(a){a.cancel();}return this.eliminate("slide").store("slide:options",$extend({link:"cancel"},b));},get:function(a){if(a||!this.retrieve("slide")){if(a||!this.retrieve("slide:options")){this.set("slide",a);
  69. }this.store("slide",new Fx.Slide(this,this.retrieve("slide:options")));}return this.retrieve("slide");}};Element.implement({slide:function(d,e){d=d||"toggle";
  70. var b=this.get("slide"),a;switch(d){case"hide":b.hide(e);break;case"show":b.show(e);break;case"toggle":var c=this.retrieve("slide:flag",b.open);b[c?"slideOut":"slideIn"](e);
  71. this.store("slide:flag",!c);a=true;break;default:b.start(d,e);}if(!a){this.eliminate("slide:flag");}return this;}});var SmoothScroll=Fx.SmoothScroll=new Class({Extends:Fx.Scroll,initialize:function(b,c){c=c||document;
  72. this.doc=c.getDocument();var d=c.getWindow();this.parent(this.doc,b);this.links=$$(this.options.links||this.doc.links);var a=d.location.href.match(/^[^#]*/)[0]+"#";
  73. this.links.each(function(f){if(f.href.indexOf(a)!=0){return;}var e=f.href.substr(a.length);if(e){this.useLink(f,e);}},this);if(!Browser.Engine.webkit419){this.addEvent("complete",function(){d.location.hash=this.anchor;
  74. },true);}},useLink:function(c,a){var b;c.addEvent("click",function(d){if(b!==false&&!b){b=document.id(a)||this.doc.getElement("a[name="+a+"]");}if(b){d.preventDefault();
  75. this.anchor=a;this.toElement(b).chain(function(){this.fireEvent("scrolledTo",[c,b]);}.bind(this));c.blur();}}.bind(this));}});var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Object.type,element:$defined});
  76. this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=$type(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;
  77. this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection=(Browser.Engine.trident)?"selectstart":"mousedown";this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:$lambda(false)};
  78. this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);
  79. return this;},start:function(e){if(e.rightClick){return;}if(this.options.preventDefault){e.preventDefault();}if(this.options.stopPropagation){e.stopPropagation();
  80. }this.mouse.start=e.page;this.fireEvent("beforeStart",this.element);var a=this.options.limit;this.limit={x:[],y:[]};var d=this.element.getStyles("left","right","top","bottom");
  81. this._invert={x:this.options.modifiers.x=="left"&&d.left=="auto"&&!isNaN(d.right.toInt())&&(this.options.modifiers.x="right"),y:this.options.modifiers.y=="top"&&d.top=="auto"&&!isNaN(d.bottom.toInt())&&(this.options.modifiers.y="bottom")};
  82. var g,f;for(g in this.options.modifiers){if(!this.options.modifiers[g]){continue;}var c=this.element.getStyle(this.options.modifiers[g]);if(c&&!c.match(/px$/)){if(!f){f=this.element.getCoordinates(this.element.getOffsetParent());
  83. }c=f[this.options.modifiers[g]];}if(this.options.style){this.value.now[g]=(c||0).toInt();}else{this.value.now[g]=this.element[this.options.modifiers[g]];
  84. }if(this.options.invert){this.value.now[g]*=-1;}if(this._invert[g]){this.value.now[g]*=-1;}this.mouse.pos[g]=e.page[g]-this.value.now[g];if(a&&a[g]){for(var b=2;
  85. b--;b){if($chk(a[g][b])){this.limit[g][b]=$lambda(a[g][b])();}}}}if($type(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid};
  86. }this.document.addEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});this.document.addEvent(this.selection,this.bound.eventStop);},check:function(a){if(this.options.preventDefault){a.preventDefault();
  87. }var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});
  88. this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);}},drag:function(a){if(this.options.preventDefault){a.preventDefault();}this.mouse.now=a.page;
  89. for(var b in this.options.modifiers){if(!this.options.modifiers[b]){continue;}this.value.now[b]=this.mouse.now[b]-this.mouse.pos[b];if(this.options.invert){this.value.now[b]*=-1;
  90. }if(this._invert[b]){this.value.now[b]*=-1;}if(this.options.limit&&this.limit[b]){if($chk(this.limit[b][1])&&(this.value.now[b]>this.limit[b][1])){this.value.now[b]=this.limit[b][1];
  91. }else{if($chk(this.limit[b][0])&&(this.value.now[b]<this.limit[b][0])){this.value.now[b]=this.limit[b][0];}}}if(this.options.grid[b]){this.value.now[b]-=((this.value.now[b]-(this.limit[b][0]||0))%this.options.grid[b]);
  92. }if(this.options.style){this.element.setStyle(this.options.modifiers[b],this.value.now[b]+this.options.unit);}else{this.element[this.options.modifiers[b]]=this.value.now[b];
  93. }}this.fireEvent("drag",[this.element,a]);},cancel:function(a){this.document.removeEvent("mousemove",this.bound.check);this.document.removeEvent("mouseup",this.bound.cancel);
  94. if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(a){this.document.removeEvent(this.selection,this.bound.eventStop);
  95. this.document.removeEvent("mousemove",this.bound.drag);this.document.removeEvent("mouseup",this.bound.stop);if(a){this.fireEvent("complete",[this.element,a]);
  96. }}});Element.implement({makeResizable:function(a){var b=new Drag(this,$merge({modifiers:{x:"width",y:"height"}},a));this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);
  97. }.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
  98. b=this.element;this.droppables=$$(this.options.droppables);this.container=document.id(this.options.container);if(this.container&&$type(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
  99. }if(this.options.style){if(this.options.modifiers.x=="left"&&this.options.modifiers.y=="top"){var f,c=document.id(b.getOffsetParent());if(c){f=c.getStyles("border-top-width","border-left-width");
  100. }var d=b.getStyles("left","top");if(c&&(d.left=="auto"||d.top=="auto")){var e=b.getPosition(c);e.x=e.x-(f["border-left-width"]?f["border-left-width"].toInt():0);
  101. e.y=e.y-(f["border-top-width"]?f["border-top-width"].toInt():0);b.setPosition(e);}}if(b.getStyle("position")=="static"){b.setStyle("position","absolute");
  102. }}this.addEvent("start",this.checkDroppables,true);this.overed=null;},start:function(a){if(this.container){this.options.limit=this.calculateLimit();}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();
  103. });}this.parent(a);},calculateLimit:function(){var d=document.id(this.element.getOffsetParent())||document.body,h=this.container.getCoordinates(d),g={},c={},b={},j={},f={},l={};
  104. ["top","right","bottom","left"].each(function(p){g[p]=this.container.getStyle("border-"+p).toInt();b[p]=this.element.getStyle("border-"+p).toInt();c[p]=this.element.getStyle("margin-"+p).toInt();
  105. j[p]=this.container.getStyle("margin-"+p).toInt();l[p]=d.getStyle("padding-"+p).toInt();f[p]=d.getStyle("border-"+p).toInt();},this);var e=this.element.offsetWidth+c.left+c.right,o=this.element.offsetHeight+c.top+c.bottom,i=0,k=0,n=h.right-g.right-e,a=h.bottom-g.bottom-o;
  106. if(this.options.includeMargins){i+=c.left;k+=c.top;}else{n+=c.right;a+=c.bottom;}if(this.element.getStyle("position")=="relative"){var m=this.element.getCoordinates(d);
  107. m.left-=this.element.getStyle("left").toInt();m.top-=this.element.getStyle("top").toInt();i+=g.left-m.left;k+=g.top-m.top;n+=c.left-m.left;a+=c.top-m.top;
  108. if(this.container!=d){i+=j.left+l.left;k+=(Browser.Engine.trident4?0:j.top)+l.top;}}else{i-=c.left;k-=c.top;if(this.container==d){n-=g.left;a-=g.top;}else{i+=h.left+g.left-f.left;
  109. k+=h.top+g.top-f.top;n-=f.left;a-=f.top;}}return{x:[i,n],y:[k,a]};},checkAgainst:function(c,b){c=(this.positions)?this.positions[b]:c.getCoordinates();
  110. var a=this.mouse.now;return(a.x>c.left&&a.x<c.right&&a.y<c.bottom&&a.y>c.top);},checkDroppables:function(){var a=this.droppables.filter(this.checkAgainst,this).getLast();
  111. if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);
  112. if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);
  113. this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);this.store("dragger",b);return b;}});
  114. var Slider=new Class({Implements:[Events,Options],Binds:["clickedElement","draggedKnob","scrolledElement"],options:{onTick:function(a){if(this.options.snap){a=this.toPosition(this.step);
  115. }this.knob.setStyle(this.property,a);},initialStep:0,snap:false,offset:0,range:false,wheel:false,steps:100,mode:"horizontal"},initialize:function(f,a,e){this.setOptions(e);
  116. this.element=document.id(f);this.knob=document.id(a);this.previousChange=this.previousEnd=this.step=-1;var g,b={},d={x:false,y:false};switch(this.options.mode){case"vertical":this.axis="y";
  117. this.property="top";g="offsetHeight";break;case"horizontal":this.axis="x";this.property="left";g="offsetWidth";}this.full=this.element.measure(function(){this.half=this.knob[g]/2;
  118. return this.element[g]-this.knob[g]+(this.options.offset*2);}.bind(this));this.setRange(this.options.range);this.knob.setStyle("position","relative").setStyle(this.property,-this.options.offset);
  119. d[this.axis]=this.property;b[this.axis]=[-this.options.offset,this.full-this.options.offset];var c={snap:0,limit:b,modifiers:d,onDrag:this.draggedKnob,onStart:this.draggedKnob,onBeforeStart:(function(){this.isDragging=true;
  120. }).bind(this),onCancel:function(){this.isDragging=false;}.bind(this),onComplete:function(){this.isDragging=false;this.draggedKnob();this.end();}.bind(this)};
  121. if(this.options.snap){c.grid=Math.ceil(this.stepWidth);c.limit[this.axis][1]=this.full;}this.drag=new Drag(this.knob,c);this.attach();if(this.options.initialStep!=null){this.set(this.options.initialStep);
  122. }},attach:function(){this.element.addEvent("mousedown",this.clickedElement);if(this.options.wheel){this.element.addEvent("mousewheel",this.scrolledElement);
  123. }this.drag.attach();return this;},detach:function(){this.element.removeEvent("mousedown",this.clickedElement);this.element.removeEvent("mousewheel",this.scrolledElement);
  124. this.drag.detach();return this;},set:function(a){if(!((this.range>0)^(a<this.min))){a=this.min;}if(!((this.range>0)^(a>this.max))){a=this.max;}this.step=Math.round(a);
  125. this.checkStep();this.fireEvent("tick",this.toPosition(this.step));this.end();return this;},setRange:function(a,b){this.min=$pick(a[0],0);this.max=$pick(a[1],this.options.steps);
  126. this.range=this.max-this.min;this.steps=this.options.steps||this.full;this.stepSize=Math.abs(this.range)/this.steps;this.stepWidth=this.stepSize*this.full/Math.abs(this.range);
  127. this.set($pick(b,this.step).floor(this.min).max(this.max));return this;},clickedElement:function(c){if(this.isDragging||c.target==this.knob){return;}var b=this.range<0?-1:1;
  128. var a=c.page[this.axis]-this.element.getPosition()[this.axis]-this.half;a=a.limit(-this.options.offset,this.full-this.options.offset);this.step=Math.round(this.min+b*this.toStep(a));
  129. this.checkStep();this.fireEvent("tick",a);this.end();},scrolledElement:function(a){var b=(this.options.mode=="horizontal")?(a.wheel<0):(a.wheel>0);this.set(b?this.step-this.stepSize:this.step+this.stepSize);
  130. a.stop();},draggedKnob:function(){var b=this.range<0?-1:1;var a=this.drag.value.now[this.axis];a=a.limit(-this.options.offset,this.full-this.options.offset);
  131. this.step=Math.round(this.min+b*this.toStep(a));this.checkStep();},checkStep:function(){if(this.previousChange!=this.step){this.previousChange=this.step;
  132. this.fireEvent("change",this.step);}},end:function(){if(this.previousEnd!==this.step){this.previousEnd=this.step;this.fireEvent("complete",this.step+"");
  133. }},toStep:function(a){var b=(a+this.options.offset)*this.stepSize/this.full*this.steps;return this.options.steps?Math.round(b-=b%this.stepSize):b;},toPosition:function(a){return(this.full*Math.abs(this.min-a))/(this.steps*this.stepSize)-this.options.offset;
  134. }});var Sortables=new Class({Implements:[Events,Options],options:{snap:4,opacity:1,clone:false,revert:false,handle:false,constrain:false,preventDefault:false},initialize:function(a,b){this.setOptions(b);
  135. this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(a)||a));if(!this.options.clone){this.options.revert=false;}if(this.options.revert){this.effect=new Fx.Morph(null,$merge({duration:250,link:"cancel"},this.options.revert));
  136. }},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(a){this.elements.push(a);
  137. var b=a.retrieve("sortables:start",this.start.bindWithEvent(this,a));(this.options.handle?a.getElement(this.options.handle)||a:a).addEvent("mousedown",b);
  138. },this);return this;},addLists:function(){Array.flatten(arguments).each(function(a){this.lists.push(a);this.addItems(a.getChildren());},this);return this;
  139. },removeItems:function(){return $$(Array.flatten(arguments).map(function(a){this.elements.erase(a);var b=a.retrieve("sortables:start");(this.options.handle?a.getElement(this.options.handle)||a:a).removeEvent("mousedown",b);
  140. return a;},this));},removeLists:function(){return $$(Array.flatten(arguments).map(function(a){this.lists.erase(a);this.removeItems(a.getChildren());return a;
  141. },this));},getClone:function(b,a){if(!this.options.clone){return new Element(a.tagName).inject(document.body);}if($type(this.options.clone)=="function"){return this.options.clone.call(this,b,a,this.list);
  142. }var c=a.clone(true).setStyles({margin:"0px",position:"absolute",visibility:"hidden",width:a.getStyle("width")});if(c.get("html").test("radio")){c.getElements("input[type=radio]").each(function(d,e){d.set("name","clone_"+e);
  143. if(d.get("checked")){a.getElements("input[type=radio]")[e].set("checked",true);}});}return c.inject(this.list).setPosition(a.getPosition(a.getOffsetParent()));
  144. },getDroppables:function(){var a=this.list.getChildren();if(!this.options.constrain){a=this.lists.concat(a).erase(this.list);}return a.erase(this.clone).erase(this.element);
  145. },insert:function(c,b){var a="inside";if(this.lists.contains(b)){this.list=b;this.drag.droppables=this.getDroppables();}else{a=this.element.getAllPrevious().contains(b)?"before":"after";
  146. }this.element.inject(b,a);this.fireEvent("sort",[this.element,this.clone]);},start:function(b,a){if(!this.idle||b.rightClick||["button","input"].contains(document.id(b.target).get("tag"))){return;
  147. }this.idle=false;this.element=a;this.opacity=a.get("opacity");this.list=a.getParent();this.clone=this.getClone(b,a);this.drag=new Drag.Move(this.clone,{preventDefault:this.options.preventDefault,snap:this.options.snap,container:this.options.constrain&&this.element.getParent(),droppables:this.getDroppables(),onSnap:function(){b.stop();
  148. this.clone.setStyle("visibility","visible");this.element.set("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone]);}.bind(this),onEnter:this.insert.bind(this),onCancel:this.reset.bind(this),onComplete:this.end.bind(this)});
  149. this.clone.inject(this.element,"before");this.drag.start(b);},end:function(){this.drag.detach();this.element.set("opacity",this.opacity);if(this.effect){var a=this.element.getStyles("width","height");
  150. var b=this.clone.computePosition(this.element.getPosition(this.clone.getOffsetParent()));this.effect.element=this.clone;this.effect.start({top:b.top,left:b.left,width:a.width,height:a.height,opacity:0.25}).chain(this.reset.bind(this));
  151. }else{this.reset();}},reset:function(){this.idle=true;this.clone.destroy();this.fireEvent("complete",this.element);},serialize:function(){var c=Array.link(arguments,{modifier:Function.type,index:$defined});
  152. var b=this.lists.map(function(d){return d.getChildren().map(c.modifier||function(e){return e.get("id");},this);},this);var a=c.index;if(this.lists.length==1){a=0;
  153. }return $chk(a)&&a>=0&&a<this.lists.length?b[a]:b;}});var Asset={javascript:function(f,d){d=$extend({onload:$empty,document:document,check:$lambda(true)},d);
  154. if(d.onLoad){d.onload=d.onLoad;delete d.onLoad;}var b=new Element("script",{src:f,type:"text/javascript"});var e=d.onload.bind(b),a=d.check,g=d.document;
  155. delete d.onload;delete d.check;delete d.document;b.addEvents({load:e,readystatechange:function(){if(["loaded","complete"].contains(this.readyState)){e();
  156. }}}).set(d);if(Browser.Engine.webkit419){var c=(function(){if(!$try(a)){return;}$clear(c);e();}).periodical(50);}return b.inject(g.head);},css:function(b,a){a=a||{};
  157. var c=a.onload||a.onLoad;if(c){a.events=a.events||{};a.events.load=c;delete a.onload;delete a.onLoad;}return new Element("link",$merge({rel:"stylesheet",media:"screen",type:"text/css",href:b},a)).inject(document.head);
  158. },image:function(c,b){b=$merge({onload:$empty,onabort:$empty,onerror:$empty},b);var d=new Image();var a=document.id(d)||new Element("img");["load","abort","error"].each(function(e){var g="on"+e;
  159. var f=e.capitalize();if(b["on"+f]){b[g]=b["on"+f];delete b["on"+f];}var h=b[g];delete b[g];d[g]=function(){if(!d){return;}if(!a.parentNode){a.width=d.width;
  160. a.height=d.height;}d=d.onload=d.onabort=d.onerror=null;h.delay(1,a,a);a.fireEvent(e,a,1);};});d.src=a.src=c;if(d&&d.complete){d.onload.delay(1);}return a.set(b);
  161. },images:function(d,c){c=$merge({onComplete:$empty,onProgress:$empty,onError:$empty,properties:{}},c);d=$splat(d);var a=[];var b=0;return new Elements(d.map(function(f,e){return Asset.image(f,$extend(c.properties,{onload:function(){c.onProgress.call(this,b,e);
  162. b++;if(b==d.length){c.onComplete();}},onerror:function(){c.onError.call(this,b,e);b++;if(b==d.length){c.onComplete();}}}));}));}};var Color=new Native({initialize:function(b,c){if(arguments.length>=3){c="rgb";
  163. b=Array.slice(arguments,0,3);}else{if(typeof b=="string"){if(b.match(/rgb/)){b=b.rgbToHex().hexToRgb(true);}else{if(b.match(/hsb/)){b=b.hsbToRgb();}else{b=b.hexToRgb(true);
  164. }}}}c=c||"rgb";switch(c){case"hsb":var a=b;b=b.hsbToRgb();b.hsb=a;break;case"hex":b=b.hexToRgb(true);break;}b.rgb=b.slice(0,3);b.hsb=b.hsb||b.rgbToHsb();
  165. b.hex=b.rgbToHex();return $extend(b,this);}});Color.implement({mix:function(){var a=Array.slice(arguments);var c=($type(a.getLast())=="number")?a.pop():50;
  166. var b=this.slice();a.each(function(d){d=new Color(d);for(var e=0;e<3;e++){b[e]=Math.round((b[e]/100*(100-c))+(d[e]/100*c));}});return new Color(b,"rgb");
  167. },invert:function(){return new Color(this.map(function(a){return 255-a;}));},setHue:function(a){return new Color([a,this.hsb[1],this.hsb[2]],"hsb");},setSaturation:function(a){return new Color([this.hsb[0],a,this.hsb[2]],"hsb");
  168. },setBrightness:function(a){return new Color([this.hsb[0],this.hsb[1],a],"hsb");}});var $RGB=function(d,c,a){return new Color([d,c,a],"rgb");};var $HSB=function(d,c,a){return new Color([d,c,a],"hsb");
  169. };var $HEX=function(a){return new Color(a,"hex");};Array.implement({rgbToHsb:function(){var b=this[0],c=this[1],j=this[2],g=0;var i=Math.max(b,c,j),e=Math.min(b,c,j);
  170. var k=i-e;var h=i/255,f=(i!=0)?k/i:0;if(f!=0){var d=(i-b)/k;var a=(i-c)/k;var l=(i-j)/k;if(b==i){g=l-a;}else{if(c==i){g=2+d-l;}else{g=4+a-d;}}g/=6;if(g<0){g++;
  171. }}return[Math.round(g*360),Math.round(f*100),Math.round(h*100)];},hsbToRgb:function(){var c=Math.round(this[2]/100*255);if(this[1]==0){return[c,c,c];}else{var a=this[0]%360;
  172. var e=a%60;var g=Math.round((this[2]*(100-this[1]))/10000*255);var d=Math.round((this[2]*(6000-this[1]*e))/600000*255);var b=Math.round((this[2]*(6000-this[1]*(60-e)))/600000*255);
  173. switch(Math.floor(a/60)){case 0:return[c,b,g];case 1:return[d,c,g];case 2:return[g,c,b];case 3:return[g,d,c];case 4:return[b,g,c];case 5:return[c,g,d];
  174. }}return false;}});String.implement({rgbToHsb:function(){var a=this.match(/\d{1,3}/g);return(a)?a.rgbToHsb():null;},hsbToRgb:function(){var a=this.match(/\d{1,3}/g);
  175. return(a)?a.hsbToRgb():null;}});var Group=new Class({initialize:function(){this.instances=Array.flatten(arguments);this.events={};this.checker={};},addEvent:function(b,a){this.checker[b]=this.checker[b]||{};
  176. this.events[b]=this.events[b]||[];if(this.events[b].contains(a)){return false;}else{this.events[b].push(a);}this.instances.each(function(c,d){c.addEvent(b,this.check.bind(this,[b,c,d]));
  177. },this);return this;},check:function(c,a,b){this.checker[c][b]=true;var d=this.instances.every(function(f,e){return this.checker[c][e]||false;},this);if(!d){return;
  178. }this.checker[c]={};this.events[c].each(function(e){e.call(this,this.instances,a);},this);}});Hash.Cookie=new Class({Extends:Cookie,options:{autoSave:true},initialize:function(b,a){this.parent(b,a);
  179. this.load();},save:function(){var a=JSON.encode(this.hash);if(!a||a.length>4096){return false;}if(a=="{}"){this.dispose();}else{this.write(a);}return true;
  180. },load:function(){this.hash=new Hash(JSON.decode(this.read(),true));return this;}});Hash.each(Hash.prototype,function(b,a){if(typeof b=="function"){Hash.Cookie.implement(a,function(){var c=b.apply(this.hash,arguments);
  181. if(this.options.autoSave){this.save();}return c;});}});var Scroller=new Class({Implements:[Events,Options],options:{area:20,velocity:1,onChange:function(a,b){this.element.scrollTo(a,b);
  182. },fps:50},initialize:function(b,a){this.setOptions(a);this.element=document.id(b);this.docBody=document.id(this.element.getDocument().body);this.listener=($type(this.element)!="element")?this.docBody:this.element;
  183. this.timer=null;this.bound={attach:this.attach.bind(this),detach:this.detach.bind(this),getCoords:this.getCoords.bind(this)};},start:function(){this.listener.addEvents({mouseenter:this.bound.attach,mouseleave:this.bound.detach});
  184. },stop:function(){this.listener.removeEvents({mouseenter:this.bound.attach,mouseleave:this.bound.detach});this.detach();this.timer=$clear(this.timer);},attach:function(){this.listener.addEvent("mousemove",this.bound.getCoords);
  185. },detach:function(){this.listener.removeEvent("mousemove",this.bound.getCoords);this.timer=$clear(this.timer);},getCoords:function(a){this.page=(this.listener.get("tag")=="body")?a.client:a.page;
  186. if(!this.timer){this.timer=this.scroll.periodical(Math.round(1000/this.options.fps),this);}},scroll:function(){var c=this.element.getSize(),a=this.element.getScroll(),h=this.element!=this.docBody?this.element.getOffsets():{x:0,y:0},d=this.element.getScrollSize(),g={x:0,y:0},e=this.options.area.top||this.options.area,b=this.options.area.bottom||this.options.area;
  187. for(var f in this.page){if(this.page[f]<(e+h[f])&&a[f]!=0){g[f]=(this.page[f]-e-h[f])*this.options.velocity;}else{if(this.page[f]+b>(c[f]+h[f])&&a[f]+c[f]!=d[f]){g[f]=(this.page[f]-c[f]+b-h[f])*this.options.velocity;
  188. }}g[f]=g[f].round();}if(g.y||g.x){this.fireEvent("change",[a.x+g.x,a.y+g.y]);}}});(function(){var a=function(c,b){return(c)?($type(c)=="function"?c(b):b.get(c)):"";
  189. };this.Tips=new Class({Implements:[Events,Options],options:{onShow:function(){this.tip.setStyle("display","block");},onHide:function(){this.tip.setStyle("display","none");
  190. },title:"title",text:function(b){return b.get("rel")||b.get("href");},showDelay:100,hideDelay:100,className:"tip-wrap",offset:{x:16,y:16},windowPadding:{x:0,y:0},fixed:false},initialize:function(){var b=Array.link(arguments,{options:Object.type,elements:$defined});
  191. this.setOptions(b.options);if(b.elements){this.attach(b.elements);}this.container=new Element("div",{"class":"tip"});},toElement:function(){if(this.tip){return this.tip;
  192. }return this.tip=new Element("div",{"class":this.options.className,styles:{position:"absolute",top:0,left:0}}).adopt(new Element("div",{"class":"tip-top"}),this.container,new Element("div",{"class":"tip-bottom"}));
  193. },attach:function(b){$$(b).each(function(d){var f=a(this.options.title,d),e=a(this.options.text,d);d.erase("title").store("tip:native",f).retrieve("tip:title",f);
  194. d.retrieve("tip:text",e);this.fireEvent("attach",[d]);var c=["enter","leave"];if(!this.options.fixed){c.push("move");}c.each(function(h){var g=d.retrieve("tip:"+h);
  195. if(!g){g=this["element"+h.capitalize()].bindWithEvent(this,d);}d.store("tip:"+h,g).addEvent("mouse"+h,g);},this);},this);return this;},detach:function(b){$$(b).each(function(d){["enter","leave","move"].each(function(e){d.removeEvent("mouse"+e,d.retrieve("tip:"+e)).eliminate("tip:"+e);
  196. });this.fireEvent("detach",[d]);if(this.options.title=="title"){var c=d.retrieve("tip:native");if(c){d.set("title",c);}}},this);return this;},elementEnter:function(c,b){this.container.empty();
  197. ["title","text"].each(function(e){var d=b.retrieve("tip:"+e);if(d){this.fill(new Element("div",{"class":"tip-"+e}).inject(this.container),d);}},this);$clear(this.timer);
  198. this.timer=(function(){this.show(b);this.position((this.options.fixed)?{page:b.getPosition()}:c);}).delay(this.options.showDelay,this);},elementLeave:function(c,b){$clear(this.timer);
  199. this.timer=this.hide.delay(this.options.hideDelay,this,b);this.fireForParent(c,b);},fireForParent:function(c,b){b=b.getParent();if(!b||b==document.body){return;
  200. }if(b.retrieve("tip:enter")){b.fireEvent("mouseenter",c);}else{this.fireForParent(c,b);}},elementMove:function(c,b){this.position(c);},position:function(e){if(!this.tip){document.id(this);
  201. }var c=window.getSize(),b=window.getScroll(),f={x:this.tip.offsetWidth,y:this.tip.offsetHeight},d={x:"left",y:"top"},g={};for(var h in d){g[d[h]]=e.page[h]+this.options.offset[h];
  202. if((g[d[h]]+f[h]-b[h])>c[h]-this.options.windowPadding[h]){g[d[h]]=e.page[h]-this.options.offset[h]-f[h];}}this.tip.setStyles(g);},fill:function(b,c){if(typeof c=="string"){b.set("html",c);
  203. }else{b.adopt(c);}},show:function(b){if(!this.tip){document.id(this);}if(!this.tip.getParent()){this.tip.inject(document.body);}this.fireEvent("show",[this.tip,b]);
  204. },hide:function(b){if(!this.tip){document.id(this);}this.fireEvent("hide",[this.tip,b]);}});})();
  205. var qwebirc = {ui: {themes: {}, style: {}}, irc: {}, util: {crypto: {}}, config: {}, options: {}, auth: {}, sound: {}, connected: false};
  206. var conf = null;
  207. var ui = null;
  208.  
  209. if(typeof QWEBIRC_BUILD != "undefined") {
  210.   qwebirc.BUILD = QWEBIRC_BUILD;
  211.   qwebirc.FILE_SUFFIX = "-" + QWEBIRC_BUILD;
  212. } else {
  213.   qwebirc.BUILD = null;
  214.   qwebirc.FILE_SUFFIX = "";
  215. }
  216.  
  217. /* Contains configuration loading and parsing functions. */
  218.  
  219. /* Load configuration.
  220.  * Accepts a default configuration object, which is modified with settings
  221.  * from cookies and the query string, then returned. */
  222. qwebirc.config.load = function(config) {
  223.  
  224.     /* Stow away some unmodified values from default configuration.
  225.      * This allows them to be accessed as 'default' values for
  226.      * query strings later. */
  227.     config.frontend.initial_nick_default = config.frontend.initial_nick;
  228.     config.frontend.initial_chans_default = config.frontend.initial_chans;
  229.     config.frontend.prompt_default = config.frontend.prompt;
  230.     config.ui.fg_color_default = config.ui.fg_color;
  231.     config.ui.fg_sec_color_default = config.ui.fg_sec_color;
  232.     config.ui.bg_color_default = config.ui.bg_color;
  233.  
  234.     /* Load user settings from cookie. */
  235.     qwebirc.config.loadCookieSettings(config);
  236.  
  237.     /* Load query string parameters. */
  238.     var uri = String(document.location);
  239.     var args = qwebirc.util.parseURI(uri);
  240.  
  241.     /* Map backwards compatiblity query string aliases to the
  242.      * parameters they represent, unless they're already set. */
  243.     if($defined(args["nick"]) && !$defined(args["initial_nick"]))
  244.         args["initial_nick"] = args["nick"];
  245.     if($defined(args["channels"]) && !$defined(args["initial_chans"]))
  246.         args["initial_chans"] = args["channels"];
  247.  
  248.     /* If we had any arguments, default chan_list_on_start off. */
  249.     if (uri.splitMax("/", 4)[3].indexOf("?") != -1)
  250.         args["chan_list_on_start"] = "0";
  251.  
  252.     /* Load nick from query string. */
  253.     if($defined(args["initial_nick"])) {
  254.         var initial_nick = args["initial_nick"];
  255.         config.frontend.initial_nick = initial_nick;
  256.         config.frontend.chan_prompt = false;
  257.     }
  258.  
  259.     /* Load channels from query string. */
  260.     if($defined(args["url"])) {
  261.         var urlchans = qwebirc.config.parseIRCURL(args["url"]);
  262.         if (urlchans) {
  263.             config.frontend.initial_chans = urlchans;
  264.             config.frontend.chan_prompt = false;
  265.         }
  266.     }
  267.     if ($defined(args["initial_chans"])) {
  268.         var initial_chans = args["initial_chans"];
  269.         config.frontend.initial_chans = initial_chans;
  270.     }
  271.  
  272.     /* Load prompt option from query string. */
  273.     if ($defined(args["prompt"])) {
  274.         if (args["prompt"] == 1)
  275.             config.frontend.prompt = true;
  276.         else
  277.             config.frontend.prompt = false;
  278.     }
  279.  
  280.     /* Load chan_prompt option from query string. */
  281.     if ($defined(args["chan_prompt"])) {
  282.         if (args["chan_prompt"] == 1)
  283.             config.frontend.chan_prompt = true;
  284.         else
  285.             config.frontend.chan_prompt = false;
  286.     }
  287.  
  288.     /* Load chan_list_on_start option from query string. */
  289.     if ($defined(args["chan_list_on_start"])) {
  290.         if (args["chan_list_on_start"] == 1)
  291.             config.atheme.chan_list_on_start = true;
  292.         else
  293.             config.atheme.chan_list_on_start = false;
  294.     }
  295.  
  296.     /* Load colours from query string. */
  297.     if ($defined(args["fg_color"])) {
  298.         config.ui.fg_color = args["fg_color"];
  299.         config.ui.fg_sec_color = args["fg_color"];
  300.     }
  301.     if ($defined(args["fg_sec_color"]))
  302.         config.ui.fg_sec_color = args["fg_sec_color"];
  303.     if ($defined(args["bg_color"]))
  304.         config.ui.bg_color = args["bg_color"];
  305.  
  306.     /* Subtitute '.' characters in the nick with random digits. */
  307.     if (config.frontend.initial_nick.indexOf(".") != -1) {
  308.         var nick = config.frontend.initial_nick;
  309.         config.frontend.initial_nick = qwebirc.config.randSub(nick);
  310.         config.frontend.initial_nick_rand = true;
  311.     }
  312.     else
  313.         config.frontend.initial_nick_rand = false;
  314.  
  315.     /* Insert any needed # symbols into channel names. */
  316.     if(config.frontend.initial_chans) {
  317.         var cdata = config.frontend.initial_chans.split(" ");
  318.         var chans = cdata[0].split(" ")[0].split(",");
  319.  
  320.         for(var i=0;i<chans.length;i++) {
  321.             if(chans[i].charAt(0) != '#')
  322.                 chans[i] = "#" + chans[i]
  323.         }
  324.  
  325.         cdata[0] = chans.join(",");
  326.         config.frontend.initial_chans = cdata.join(" ");
  327.     }
  328.  
  329.     return config;
  330. };
  331.  
  332. /* Loads settings from cookies. */
  333. qwebirc.config.loadCookieSettings = function(config) {
  334.     var cookie = new Hash.Cookie("iris-settings", {duration: 3650, autoSave: false});
  335.     for (var i = 0; i < qwebirc.options.Options.length; i++) {
  336.         var category = qwebirc.options.Options[i].category;
  337.         var option = qwebirc.options.Options[i].option;
  338.         var cookieName = category + "." + option;
  339.         if ($defined(cookie.get(cookieName)))
  340.             config[category][option] = cookie.get(cookieName);
  341.     }
  342. };
  343.  
  344. /* Save setings to cookies. */
  345. qwebirc.config.saveUserSettings = function(config) {
  346.     var cookie = new Hash.Cookie("iris-settings", {duration: 3650, autoSave: false});
  347.     cookie.erase();
  348.     cookie = new Hash.Cookie("iris-settings", {duration: 3650, autoSave: false});
  349.     for (var i = 0; i < qwebirc.options.Options.length; i++) {
  350.         var category = qwebirc.options.Options[i].category;
  351.         var option = qwebirc.options.Options[i].option;
  352.         var cookieName = category + "." + option;
  353.         cookie.set(cookieName, config[category][option]);
  354.     }
  355.     cookie.save();
  356. };
  357.  
  358. /* Substitute dots in configured nicks with random numbers. */
  359. qwebirc.config.randSub = function(nick) {
  360.     var getDigit = function() { return Math.floor(Math.random() * 10); }
  361.  
  362.     return nick.split("").map(function(v) {
  363.         if(v == ".") {
  364.             return getDigit();
  365.         } else {
  366.             return v;
  367.         }
  368.     }).join("");
  369. };
  370.  
  371. /* Parse a channel out of a provided URL, if one is set.
  372.    Returns the provided channel (potentially with key), or nothing. */
  373. qwebirc.config.parseIRCURL = function(url) {
  374.     if(url.indexOf(":") == 0)
  375.         return;
  376.     var schemeComponents = url.splitMax(":", 2);
  377.     if(schemeComponents[0].toLowerCase() != "irc" && schemeComponents[0].toLowerCase() != "ircs") {
  378.         alert("Bad IRC URL scheme.");
  379.         return;
  380.     }
  381.  
  382.     if(url.indexOf("/") == 0) {
  383.         /* irc: */
  384.         return;
  385.     }
  386.  
  387.     var pathComponents = url.splitMax("/", 4);
  388.     if(pathComponents.length < 4 || pathComponents[3] == "") {
  389.         /* irc://abc */
  390.         return;
  391.     }
  392.  
  393.     var args, queryArgs;
  394.     if(pathComponents[3].indexOf("?") > -1) {
  395.         queryArgs = qwebirc.util.parseURI(pathComponents[3]);
  396.         args = pathComponents[3].splitMax("?", 2)[0];
  397.     } else {
  398.         args = pathComponents[3];
  399.     }
  400.     var parts = args.split(",");
  401.  
  402.     var channel = parts[0];
  403.     if(channel.charAt(0) != "#")
  404.         channel = "#" + channel;
  405.  
  406.     var not_supported = [], needkey = false, key;
  407.     for(var i=1;i<parts.length;i++) {
  408.         var value = parts[i];
  409.         if(value == "needkey") {
  410.             needkey = true;
  411.         } else {
  412.             not_supported.push(value);
  413.         }
  414.     }
  415.  
  416.     if($defined(queryArgs)) {
  417.         for(var key_ in queryArgs) {
  418.             var value = queryArgs[key_];
  419.  
  420.             if(key_ == "key") {
  421.                 key = value;
  422.                 needkey = true;
  423.             } else {
  424.                 not_supported.push(key_);
  425.             }
  426.         }
  427.     }
  428.  
  429.     if(needkey) {
  430.         if(!$defined(key))
  431.             key = prompt("Please enter the password for channel " + channel + ":");
  432.         if($defined(key))
  433.             channel = channel + " " + key;
  434.     }
  435.  
  436.     if(not_supported.length > 0)
  437.         alert("The following IRC URL components were not accepted: " + not_supported.join(", ") + ".");
  438.  
  439.     return channel;
  440. };
  441.  
  442. /* qwebirc -- Copyright (C) 2008-2010 Chris Porter and the qwebirc project --- All rights reserved. */
  443.  
  444.  
  445. qwebirc.util.crypto.getARC4Stream = function(key, length) {
  446.   var s = [];
  447.  
  448.   var keyint = [];
  449.   for(var i=0;i<key.length;i++)
  450.     keyint.push(key.charCodeAt(i));
  451.  
  452.   for(var i=0;i<256;i++)
  453.     s[i] = i;
  454.  
  455.   var j = 0;
  456.   for(var i=0;i<256;i++) {
  457.     j = (j + s[i] + keyint[i % key.length]) & 255;
  458.     var w = s[i]; s[i] = s[j]; s[j] = w;
  459.   }
  460.  
  461.   var output = [];
  462.   var i = 0;
  463.   var j = 0;
  464.   for(var k=0;k<length;k++) {
  465.     i = (i + 1) & 255;
  466.     j = (j + s[i]) & 255;
  467.  
  468.     var w = s[i]; s[i] = s[j]; s[j] = w;
  469.     output.push(s[(s[i] + s[j]) & 255]);
  470.   }
  471.   return output;
  472. }
  473.  
  474. qwebirc.util.crypto.xorStreams = function(data, prngstream) {
  475.   if(data.length != prngstream.length)
  476.     return;
  477.  
  478.   var output = [];
  479.   for(var i=0;i<data.length;i++)
  480.     output.push(String.fromCharCode(data.charCodeAt(i) ^ prngstream[i]));
  481.  
  482.   return output.join("");
  483. }
  484.  
  485. qwebirc.util.crypto.ARC4 = function(key, data) {
  486.   var prngstream = qwebirc.util.crypto.getARC4Stream(key, data.length + 1024);
  487.   /* burn first 1024 bytes */
  488.   prngstream = prngstream.slice(1024);
  489.  
  490.   return qwebirc.util.crypto.xorStreams(data, prngstream);
  491. }
  492.  
  493. Array.prototype.indexFromEnd = function(d) {
  494.   var p = this;
  495.  
  496.   if(d < 0)
  497.     return p[p.length + d];
  498.  
  499.   return p[d];
  500. }
  501.  
  502. qwebirc.util.dictCopy = function(d) {
  503.   var n = {};
  504.   for(var k in d)
  505.     n[k] = d[k];
  506.  
  507.   return n;
  508. }
  509.  
  510. /* how horribly inefficient */
  511. String.prototype.replaceAll = function(f, t) {
  512.   var i = this.indexOf(f);
  513.   var c = this;
  514.  
  515.   while(i > -1) {
  516.     c = c.replace(f, t);
  517.     i = c.indexOf(f);
  518.   }
  519.   return c;
  520. }
  521.  
  522. /* how horribly inefficient (again) */
  523. String.prototype.splitMax = function(by, max) {
  524.   var items = this.split(by);
  525.   var newitems = items.slice(0, max-1);
  526.  
  527.   if(items.length >= max)
  528.     newitems.push(items.slice(max-1).join(by));
  529.  
  530.   return newitems;
  531. }
  532.  
  533. /* returns the arguments */
  534. qwebirc.util.parseURI = function(uri) {
  535.   var result = {}
  536.  
  537.   var start = uri.indexOf('?');
  538.   if(start == -1)
  539.     return result;
  540.  
  541.   var querystring = uri.substring(start + 1);
  542.  
  543.   var args = querystring.split("&");
  544.  
  545.   for(var i=0;i<args.length;i++) {
  546.     var r = args[i].splitMax("=", 2);
  547.     if(r.length < 2)
  548.       continue;
  549.  
  550.     result[unescape(r[0])] = unescape(r[1]);
  551.   }
  552.  
  553.   return result;
  554. }
  555.  
  556. qwebirc.util.DaysOfWeek = {
  557.   0: "Sun",
  558.   1: "Mon",
  559.   2: "Tue",
  560.   3: "Wed",
  561.   4: "Thu",
  562.   5: "Fri",
  563.   6: "Sat"
  564. };
  565.  
  566. qwebirc.util.MonthsOfYear = {
  567.   0: "Jan",
  568.   1: "Feb",
  569.   2: "Mar",
  570.   3: "Apr",
  571.   4: "May",
  572.   5: "Jun",
  573.   6: "Jul",
  574.   7: "Aug",
  575.   8: "Sep",
  576.   9: "Oct",
  577.   10: "Nov",
  578.   11: "Dec"
  579. };
  580.  
  581. qwebirc.util.NBSPCreate = function(text, element) {
  582.   var e = text.split("  ");
  583.   for(var i=0;i<e.length;i++) {
  584.     var tn = document.createTextNode(e[i]);
  585.     element.appendChild(tn);
  586.  
  587.     if(i != e.length - 1) {
  588.       var e2 = new Element("span");
  589.       e2.set("html", "&nbsp;&nbsp;");
  590.       element.appendChild(e2);
  591.     }
  592.   }
  593. };
  594.  
  595. qwebirc.util.longtoduration = function(l) {
  596.   var seconds = l % 60;
  597.   var minutes = Math.floor((l % 3600) / 60);
  598.   var hours = Math.floor((l % (3600 * 24)) / 3600);
  599.   var days = Math.floor(l / (24*3600));
  600.  
  601.   return days + " days " + hours + " hours " + minutes + " minutes " + seconds + " seconds";
  602. }
  603.  
  604. qwebirc.util.pad = function(x) {
  605.   x = "" + x;
  606.   if(x.length == 1)
  607.     return "0" + x;
  608.   return x
  609. }
  610.  
  611. RegExp.escape = function(text) {
  612.   if(!arguments.callee.sRE) {
  613.     var specials = [
  614.       '/', '.', '*', '+', '?', '|',
  615.       '(', ')', '[', ']', '{', '}', '\\'
  616.     ];
  617.     arguments.callee.sRE = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
  618.   }
  619.  
  620.   return text.replace(arguments.callee.sRE, '\\$1');
  621. }
  622.  
  623. qwebirc.ui.insertAt = function(position, parent, element) {
  624.   if(!parent.childNodes || (position >= parent.childNodes.length)) {
  625.     parent.appendChild(element);
  626.   } else {
  627.     parent.insertBefore(element, parent.childNodes[position]);
  628.   }
  629. }
  630.  
  631. qwebirc.util.setCaretPos = function(obj, pos) {
  632.   if($defined(obj.selectionStart)) {
  633.     obj.focus();
  634.     obj.setSelectionRange(pos, pos);
  635.   } else if(obj.createTextRange) {
  636.     var range = obj.createTextRange();
  637.     range.move("character", pos);
  638.     range.select();
  639.   }
  640. }
  641.  
  642. qwebirc.util.setAtEnd = function(obj) {
  643.   qwebirc.util.setCaretPos(obj.value.length);
  644. }
  645.  
  646. qwebirc.util.getCaretPos = function(element) {
  647.   if($defined(element.selectionStart))
  648.     return element.selectionStart;
  649.  
  650.   if(document.selection) {
  651.     element.focus();
  652.     var sel = document.selection.createRange();
  653.     sel.moveStart("character", -element.value.length);
  654.     return sel.text.length;
  655.   }
  656. }
  657.  
  658. qwebirc.util.browserVersion = function() {
  659.   //return "engine: " + Browser.Engine.name + " platform: " + Browser.Platform.name + " user agent: " + navigator.userAgent;
  660.   return navigator.userAgent;
  661. }
  662.  
  663. qwebirc.util.getEnclosedWord = function(text, position) {
  664.   var l = text.split("");
  665.   var buf = [];
  666.  
  667.   if(text == "")
  668.     return;
  669.  
  670.   var start = position - 1;
  671.   if(start < 0) {
  672.     /* special case: starting with space */
  673.     start = 0;
  674.   } else {
  675.     /* work back until we find the first space */
  676.     for(;start>=0;start--) {
  677.       if(l[start] == ' ') {
  678.         start = start + 1;
  679.         break;
  680.       }
  681.     }
  682.   }
  683.  
  684.   if(start < 0)
  685.     start = 0;
  686.  
  687.   var s = text.substring(start);
  688.   var pos = s.indexOf(" ");
  689.   if(pos != -1)
  690.     s = s.substring(0, pos);
  691.  
  692.   return [start, s];
  693. }
  694.  
  695. String.prototype.startsWith = function(what) {
  696.   return this.substring(0, what.length) == what;
  697. }
  698.  
  699. /* NOT cryptographically secure! */
  700. qwebirc.util.randHexString = function(numBytes) {
  701.   var getByte = function() {
  702.     return (((1+Math.random())*0x100)|0).toString(16).substring(1);
  703.   };
  704.  
  705.   var l = [];
  706.   for(var i=0;i<numBytes;i++)
  707.     l.push(getByte());
  708.  
  709.   return l.join("");
  710. }
  711.  
  712. qwebirc.util.importJS = function(name, watchFor, onload) {
  713.   var script = document.createElement("script");
  714.   script.type = "text/javascript";
  715.   script.src = name;
  716.  
  717.   if(Browser.Engine.trident) {
  718.     /* HORRID */
  719.     var checkFn = function() {
  720.       if(eval("typeof " + watchFor) != "undefined") {
  721.         onload();
  722.       } else {
  723.         checkFn.delay(100);
  724.       }
  725.     }
  726.     checkFn();
  727.   } else {
  728.     script.onload = onload;
  729.   }
  730.   document.getElementsByTagName("head")[0].appendChild(script);
  731. }
  732.  
  733. qwebirc.util.createInput = function(type, parent, name, selected, id) {
  734.   var r;
  735.   if(Browser.Engine.trident) {
  736.     if(name) {
  737.       name = " name=\"" + escape(name) + "\"";
  738.     } else {
  739.       name = "";
  740.     }
  741.     if(id) {
  742.       id = " id=\"" + escape(id) + "\"";
  743.     } else {
  744.       id = "";
  745.     }
  746.     try {
  747.       return $(document.createElement("<input type=\"" + type + "\"" + name + id + " " + (selected?" checked":"") + "/>"));
  748.     } catch(e) {
  749.       /* fallthough, trying it the proper way... */
  750.     }
  751.   }
  752.  
  753.   r = new Element("input");
  754.   r.type = type;
  755.   if(name)
  756.     r.name = name;
  757.   if(id)
  758.     r.id = id;
  759.  
  760.   if(selected)
  761.     r.checked = true;
  762.  
  763.   parent.appendChild(r);
  764.   return r;
  765. }
  766.  
  767. /* From: www.webtoolkit.info */
  768. qwebirc.util.b64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  769. qwebirc.util.b64Encode = function(data) {
  770.   var output = [];
  771.   var table = qwebirc.util.b64Table;
  772.   for(var i=0;i<data.length;) {
  773.     var chr1 = data.charCodeAt(i++);
  774.     var chr2 = data.charCodeAt(i++);
  775.     var chr3 = data.charCodeAt(i++);
  776.  
  777.     var enc1 = chr1 >> 2;
  778.     var enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  779.     var enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  780.     var enc4 = chr3 & 63;
  781.  
  782.     if(isNaN(chr2)) {
  783.       enc3 = enc4 = 64;
  784.     } else if(isNaN(chr3)) {
  785.       enc4 = 64;
  786.     }
  787.  
  788.     output.push(table.charAt(enc1) + table.charAt(enc2) + table.charAt(enc3) + table.charAt(enc4));
  789.   }
  790.   return output.join("");
  791. }
  792.  
  793. /* From: www.webtoolkit.info */
  794. qwebirc.util.b64Decode = function(data) {
  795.   data = data.replace(/[^A-Za-z0-9\+\/\=]/g, "");
  796.  
  797.   var output = [];
  798.   var table = qwebirc.util.b64Table;
  799.   for(var i=0;i<data.length;) {
  800.     var enc1 = table.indexOf(data.charAt(i++));
  801.     var enc2 = table.indexOf(data.charAt(i++));
  802.     var enc3 = table.indexOf(data.charAt(i++));
  803.     var enc4 = table.indexOf(data.charAt(i++));
  804.  
  805.     var chr1 = (enc1 << 2) | (enc2 >> 4);
  806.     var chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  807.     var chr3 = ((enc3 & 3) << 6) | enc4;
  808.  
  809.     output.push(String.fromCharCode(chr1));
  810.     if (enc3 != 64)
  811.       output.push(String.fromCharCode(chr2));
  812.     if (enc4 != 64)
  813.       output.push(String.fromCharCode(chr3));
  814.   }
  815.  
  816.   return output.join("");
  817. }
  818.  
  819. qwebirc.util.composeAnd = function() {
  820.  var xargs = arguments;
  821.  
  822.   return function() {
  823.     for(var i=0;i<xargs.length;i++)
  824.       if(!xargs[i].apply(this, arguments))
  825.         return false;
  826.  
  827.     return true;
  828.   }
  829. }
  830.  
  831. qwebirc.util.invertFn = function(fn) {
  832.   return function() {
  833.     return !fn.apply(this, arguments);
  834.   }
  835. }
  836.  
  837. qwebirc.util.deviceHasKeyboard = function() {
  838.   var determine = function() {
  839.     if(Browser.Engine.ipod)
  840.       return true;
  841.  
  842.     var MOBILE_UAs = ["Nintendo Wii", " PIE", "BlackBerry", "IEMobile", "Windows CE", "Nokia", "Opera Mini", "Mobile", "mobile", "Pocket", "pocket", "Android"];
  843.     /* safari not included because iphones/ipods send that, and we checked for iphone/ipod specifically above */
  844.     var DESKTOP_UAs = ["Chrome", "Firefox", "Camino", "Iceweasel", "K-Meleon", "Konqueror", "SeaMonkey", "Windows NT", "Windows 9"];
  845.  
  846.     var ua = navigator.userAgent;
  847.  
  848.     var contains = function(v) {
  849.       return ua.indexOf(v) > -1;
  850.     }
  851.  
  852.     for(var i=0;i<MOBILE_UAs.length;i++)
  853.       if(contains(MOBILE_UAs[i]))
  854.         return false;
  855.  
  856.     for(var i=0;i<DESKTOP_UAs.length;i++)
  857.       if(contains(DESKTOP_UAs[i]))
  858.         return true;
  859.  
  860.     return false;
  861.   };
  862.   var v = determine();
  863.  
  864.   qwebirc.util.deviceHasKeyboard = function() {
  865.     return v;
  866.   }
  867.  
  868.   return v;
  869. }
  870.  
  871. qwebirc.util.generateID_ID = 0;
  872. qwebirc.util.generateID = function() {
  873.   return "qqa-" + qwebirc.util.generateID_ID++;
  874. }
  875.  
  876. /*
  877.  * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
  878.  * Digest Algorithm, as defined in RFC 1321.
  879.  * Copyright (C) Paul Johnston 1999 - 2000.
  880.  * See http://pajhome.org.uk/site/legal.html for details.
  881.  */
  882.  
  883. /*
  884.  * Converted freestanding JavaScript code to fully encapsulated object.
  885.  * Andrew Collins, andrewrcollins@yahoo.com, 2000-11-28
  886.  */
  887.  
  888. /*
  889.  * MD5
  890.  *
  891.  * Usage:
  892.  *
  893.  *   var object = new MD5()
  894.  *
  895.  *     Returns a MD5 object.
  896.  *
  897.  *   object.digest(input)
  898.  *
  899.  *     Returns MD5 message digest of input.
  900.  *
  901.  * Example:
  902.  *
  903.  *   var object = new MD5();
  904.  *
  905.  *   // Examples drawn from RFC1321 test suite
  906.  *   object.digest("");
  907.  *   // d41d8cd98f00b204e9800998ecf8427e
  908.  *
  909.  *   object.digest("a");
  910.  *   // 0cc175b9c0f1b6a831c399e269772661
  911.  *
  912.  *   object.digest("abc");
  913.  *   // 900150983cd24fb0d6963f7d28e17f72
  914.  *
  915.  *   object.digest("message digest");
  916.  *   // f96b697d7cb7938d525a2f31aaf161d0
  917.  *
  918.  *   object.digest("abcdefghijklmnopqrstuvwxyz");
  919.  *   // c3fcd3d76192e4007dfb496cca67e13b
  920.  *
  921.  *   object.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
  922.  *   // d174ab98d277d9f5a5611c2c9f419d9f
  923.  *
  924.  *   object.digest("12345678901234567890123456789012345678901234567890123456789012345678901234567890");
  925.  *   // 57edf4a22be3c955ac49da2e2107b67a
  926.  */
  927.  
  928. qwebirc.util.crypto.MD5 = function() {
  929.   this.digest = calcMD5;
  930.  
  931. /*
  932.  * Convert a 32-bit number to a hex string with ls-byte first
  933.  */
  934.   var hex_chr = "0123456789abcdef";
  935.   function rhex(num)
  936.   {
  937.     var str = "";
  938.     for(var j = 0; j <= 3; j++)
  939.       str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) + hex_chr.charAt((num >> (j * 8)) & 0x0F);
  940.     return str;
  941.   }
  942.  
  943. /*
  944.  * Convert a string to a sequence of 16-word blocks, stored as an array.
  945.  * Append padding bits and the length, as described in the MD5 standard.
  946.  */
  947.   function str2blks_MD5(str)
  948.   {
  949.     var nblk = ((str.length + 8) >> 6) + 1;
  950.     var blks = new Array(nblk * 16);
  951.     for(var i = 0; i < nblk * 16; i++) blks[i] = 0;
  952.     for(var i = 0; i < str.length; i++)
  953.       blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
  954.     blks[i >> 2] |= 0x80 << ((i % 4) * 8);
  955.     blks[nblk * 16 - 2] = str.length * 8;
  956.     return blks;
  957.   }
  958.  
  959. /*
  960.  * Add integers, wrapping at 2^32
  961.  */
  962.   function add(x, y)
  963.   {
  964.     return ((x&0x7FFFFFFF) + (y&0x7FFFFFFF)) ^ (x&0x80000000) ^ (y&0x80000000);
  965.   }
  966.  
  967. /*
  968.  * Bitwise rotate a 32-bit number to the left
  969.  */
  970. function rol(num, cnt)
  971.   {
  972.     return (num << cnt) | (num >>> (32 - cnt));
  973.   }
  974.  
  975. /*
  976.  * These functions implement the basic operation for each round of the
  977.  * algorithm.
  978.  */
  979.   function cmn(q, a, b, x, s, t)
  980.   {
  981.     return add(rol(add(add(a, q), add(x, t)), s), b);
  982.   }
  983.   function ff(a, b, c, d, x, s, t)
  984.   {
  985.     return cmn((b & c) | ((~b) & d), a, b, x, s, t);
  986.   }
  987.   function gg(a, b, c, d, x, s, t)
  988.   {
  989.     return cmn((b & d) | (c & (~d)), a, b, x, s, t);
  990.   }
  991.   function hh(a, b, c, d, x, s, t)
  992.   {
  993.     return cmn(b ^ c ^ d, a, b, x, s, t);
  994.   }
  995.   function ii(a, b, c, d, x, s, t)
  996.   {
  997.     return cmn(c ^ (b | (~d)), a, b, x, s, t);
  998.   }
  999.  
  1000. /*
  1001.  * Take a string and return the hex representation of its MD5.
  1002.  */
  1003.   function calcMD5(str)
  1004.   {
  1005.     var x = str2blks_MD5(str);
  1006.     var a = 0x67452301;
  1007.     var b = 0xEFCDAB89;
  1008.     var c = 0x98BADCFE;
  1009.     var d = 0x10325476;
  1010.  
  1011.     for(var i = 0; i < x.length; i += 16)
  1012.     {
  1013.       var olda = a;
  1014.       var oldb = b;
  1015.       var oldc = c;
  1016.       var oldd = d;
  1017.  
  1018.       a = ff(a, b, c, d, x[i+ 0], 7 , 0xD76AA478);
  1019.       d = ff(d, a, b, c, x[i+ 1], 12, 0xE8C7B756);
  1020.       c = ff(c, d, a, b, x[i+ 2], 17, 0x242070DB);
  1021.       b = ff(b, c, d, a, x[i+ 3], 22, 0xC1BDCEEE);
  1022.       a = ff(a, b, c, d, x[i+ 4], 7 , 0xF57C0FAF);
  1023.       d = ff(d, a, b, c, x[i+ 5], 12, 0x4787C62A);
  1024.       c = ff(c, d, a, b, x[i+ 6], 17, 0xA8304613);
  1025.       b = ff(b, c, d, a, x[i+ 7], 22, 0xFD469501);
  1026.       a = ff(a, b, c, d, x[i+ 8], 7 , 0x698098D8);
  1027.       d = ff(d, a, b, c, x[i+ 9], 12, 0x8B44F7AF);
  1028.       c = ff(c, d, a, b, x[i+10], 17, 0xFFFF5BB1);
  1029.       b = ff(b, c, d, a, x[i+11], 22, 0x895CD7BE);
  1030.       a = ff(a, b, c, d, x[i+12], 7 , 0x6B901122);
  1031.       d = ff(d, a, b, c, x[i+13], 12, 0xFD987193);
  1032.       c = ff(c, d, a, b, x[i+14], 17, 0xA679438E);
  1033.       b = ff(b, c, d, a, x[i+15], 22, 0x49B40821);
  1034.  
  1035.       a = gg(a, b, c, d, x[i+ 1], 5 , 0xF61E2562);
  1036.       d = gg(d, a, b, c, x[i+ 6], 9 , 0xC040B340);
  1037.       c = gg(c, d, a, b, x[i+11], 14, 0x265E5A51);
  1038.       b = gg(b, c, d, a, x[i+ 0], 20, 0xE9B6C7AA);
  1039.       a = gg(a, b, c, d, x[i+ 5], 5 , 0xD62F105D);
  1040.       d = gg(d, a, b, c, x[i+10], 9 , 0x02441453);
  1041.       c = gg(c, d, a, b, x[i+15], 14, 0xD8A1E681);
  1042.       b = gg(b, c, d, a, x[i+ 4], 20, 0xE7D3FBC8);
  1043.       a = gg(a, b, c, d, x[i+ 9], 5 , 0x21E1CDE6);
  1044.       d = gg(d, a, b, c, x[i+14], 9 , 0xC33707D6);
  1045.       c = gg(c, d, a, b, x[i+ 3], 14, 0xF4D50D87);
  1046.       b = gg(b, c, d, a, x[i+ 8], 20, 0x455A14ED);
  1047.       a = gg(a, b, c, d, x[i+13], 5 , 0xA9E3E905);
  1048.       d = gg(d, a, b, c, x[i+ 2], 9 , 0xFCEFA3F8);
  1049.       c = gg(c, d, a, b, x[i+ 7], 14, 0x676F02D9);
  1050.       b = gg(b, c, d, a, x[i+12], 20, 0x8D2A4C8A);
  1051.  
  1052.       a = hh(a, b, c, d, x[i+ 5], 4 , 0xFFFA3942);
  1053.       d = hh(d, a, b, c, x[i+ 8], 11, 0x8771F681);
  1054.       c = hh(c, d, a, b, x[i+11], 16, 0x6D9D6122);
  1055.       b = hh(b, c, d, a, x[i+14], 23, 0xFDE5380C);
  1056.       a = hh(a, b, c, d, x[i+ 1], 4 , 0xA4BEEA44);
  1057.       d = hh(d, a, b, c, x[i+ 4], 11, 0x4BDECFA9);
  1058.       c = hh(c, d, a, b, x[i+ 7], 16, 0xF6BB4B60);
  1059.       b = hh(b, c, d, a, x[i+10], 23, 0xBEBFBC70);
  1060.       a = hh(a, b, c, d, x[i+13], 4 , 0x289B7EC6);
  1061.       d = hh(d, a, b, c, x[i+ 0], 11, 0xEAA127FA);
  1062.       c = hh(c, d, a, b, x[i+ 3], 16, 0xD4EF3085);
  1063.       b = hh(b, c, d, a, x[i+ 6], 23, 0x04881D05);
  1064.       a = hh(a, b, c, d, x[i+ 9], 4 , 0xD9D4D039);
  1065.       d = hh(d, a, b, c, x[i+12], 11, 0xE6DB99E5);
  1066.       c = hh(c, d, a, b, x[i+15], 16, 0x1FA27CF8);
  1067.       b = hh(b, c, d, a, x[i+ 2], 23, 0xC4AC5665);
  1068.  
  1069.       a = ii(a, b, c, d, x[i+ 0], 6 , 0xF4292244);
  1070.       d = ii(d, a, b, c, x[i+ 7], 10, 0x432AFF97);
  1071.       c = ii(c, d, a, b, x[i+14], 15, 0xAB9423A7);
  1072.       b = ii(b, c, d, a, x[i+ 5], 21, 0xFC93A039);
  1073.       a = ii(a, b, c, d, x[i+12], 6 , 0x655B59C3);
  1074.       d = ii(d, a, b, c, x[i+ 3], 10, 0x8F0CCC92);
  1075.       c = ii(c, d, a, b, x[i+10], 15, 0xFFEFF47D);
  1076.       b = ii(b, c, d, a, x[i+ 1], 21, 0x85845DD1);
  1077.       a = ii(a, b, c, d, x[i+ 8], 6 , 0x6FA87E4F);
  1078.       d = ii(d, a, b, c, x[i+15], 10, 0xFE2CE6E0);
  1079.       c = ii(c, d, a, b, x[i+ 6], 15, 0xA3014314);
  1080.       b = ii(b, c, d, a, x[i+13], 21, 0x4E0811A1);
  1081.       a = ii(a, b, c, d, x[i+ 4], 6 , 0xF7537E82);
  1082.       d = ii(d, a, b, c, x[i+11], 10, 0xBD3AF235);
  1083.       c = ii(c, d, a, b, x[i+ 2], 15, 0x2AD7D2BB);
  1084.       b = ii(b, c, d, a, x[i+ 9], 21, 0xEB86D391);
  1085.  
  1086.       a = add(a, olda);
  1087.       b = add(b, oldb);
  1088.       c = add(c, oldc);
  1089.       d = add(d, oldd);
  1090.     }
  1091.     return rhex(a) + rhex(b) + rhex(c) + rhex(d);
  1092.   }
  1093. }
  1094.  
  1095. qwebirc.sessionCount = 0;
  1096.  
  1097. /* Stores settings and handles for a single IRC connection. */
  1098. qwebirc.session = new Class({
  1099.  
  1100.     /* The IRC connection instance. */
  1101.     irc: null,
  1102.  
  1103.     /* Atheme state. */
  1104.     atheme: {
  1105.         state: null,
  1106.         user: null,
  1107.         token: null
  1108.     },
  1109.  
  1110.     /* UI windows belonging to this session. */
  1111.     windows: {},
  1112.  
  1113.     initialize: function() {
  1114.  
  1115.         /* Load any Atheme login state. */
  1116.         cookie = new Hash.Cookie("iris-auth");
  1117.         if ($defined(cookie.get("user"))) {
  1118.             this.atheme.user = cookie.get("user");
  1119.             this.atheme.secret = cookie.get("token");
  1120.         }
  1121.  
  1122.         /* Check our Atheme login state. */
  1123.         qwebirc.ui.Atheme.check(this);
  1124.     }
  1125. });
  1126.  
  1127. qwebirc.sound.domReady = false;
  1128. window.addEvent("domready", function() {
  1129.   qwebirc.sound.domReady = true;
  1130. });
  1131.  
  1132. qwebirc.sound.SoundPlayer = new Class({
  1133.   Implements: [Events],
  1134.   session: null,
  1135.   initialize: function(session) {
  1136.     this.session = session;
  1137.     this.loadingSWF = false;
  1138.     this.loadedSWF = false;
  1139.   },
  1140.   go: function() {
  1141.     if(qwebirc.sound.domReady) {
  1142.       this.loadSoundManager();
  1143.     } else {
  1144.       window.addEvent("domready", function() {
  1145.         this.loadSoundManager();
  1146.       }.bind(this));
  1147.     }
  1148.   },
  1149.   loadSoundManager: function() {
  1150.     if(this.loadingSWF)
  1151.       return;
  1152.     this.loadingSWF = true;
  1153.     if(eval("typeof soundManager") != "undefined") {
  1154.       this.loadedSWF = true;
  1155.       this.fireEvent("ready");
  1156.       return;
  1157.     }
  1158.  
  1159.     var debugMode = false;
  1160.     qwebirc.util.importJS(conf.frontend.static_base_url + "js/" + (debugMode?"soundmanager2":"soundmanager2-nodebug-jsmin") + ".js", "soundManager", function() {
  1161.       soundManager.url = conf.frontend.static_base_url + "sound/";
  1162.  
  1163.       soundManager.debugMode = debugMode;
  1164.       soundManager.useConsole = debugMode;
  1165.       soundManager.onload = function() {
  1166.         this.loadedSWF = true;
  1167.         this.fireEvent("ready");
  1168.       }.bind(this);
  1169.       soundManager.beginDelayedInit();
  1170.     }.bind(this));
  1171.   },
  1172.   createSound: function(name, src) {
  1173.     soundManager.createSound(name, src);
  1174.   },
  1175.   playSound: function(name) {
  1176.     soundManager.play(name);
  1177.   },
  1178.   beep: function() {
  1179.     if(!this.beepLoaded) {
  1180.       this.createSound("beep", conf.frontend.static_base_url + "sound/beep3.mp3");
  1181.       this.beepLoaded = true;
  1182.     }
  1183.     this.playSound("beep");
  1184.   }
  1185. });
  1186.  
  1187. qwebirc.VERSION = "0.90"
  1188.  
  1189. qwebirc.irc.AthemeQuery = {};
  1190.  
  1191. /**
  1192.  * Build a generic request to Atheme.
  1193.  *
  1194.  * \param command The command being requested.
  1195.  */
  1196. qwebirc.irc.AthemeQuery.newRequest = function(command) {
  1197.  
  1198.     /* New login request. */
  1199.     var cacheAvoidance = qwebirc.util.randHexString(16);
  1200.     var r = new Request.JSON({
  1201.         url: conf.frontend.dynamic_base_url + "a/" + command + "?r=" + cacheAvoidance,
  1202.         async: true
  1203.     });
  1204.  
  1205.     /* Try to minimise the amount of headers. */
  1206.     r.headers = new Hash;
  1207.     r.addEvent("request", function() {
  1208.         var setHeader = function(key, value) {
  1209.             try {
  1210.                 this.setRequestHeader(key, value);
  1211.             } catch(e) {
  1212.             }
  1213.         }.bind(this);
  1214.  
  1215.         setHeader("User-Agent", null);
  1216.         setHeader("Accept", null);
  1217.         setHeader("Accept-Language", null);
  1218.     }.bind(r.xhr));
  1219.     if(Browser.Engine.trident)
  1220.         r.setHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
  1221.  
  1222.     return r;
  1223. }
  1224.  
  1225.  
  1226. /**
  1227.  * Login to Atheme, getting an authentication token.
  1228.  * Callback signature is callback(token), where token is an authentication
  1229.  * token for later requests, the empty string to indicate authentication
  1230.  * failure, or null to indicate connection failure.
  1231.  *
  1232.  * \param callback Function to call to inform of results.
  1233.  * \param user Username as string.
  1234.  * \param pass Password as string.
  1235.  */
  1236. qwebirc.irc.AthemeQuery.login = function(callback, user, pass) {
  1237.  
  1238.     r = qwebirc.irc.AthemeQuery.newRequest("l");
  1239.  
  1240.     r.addEvent("failure", function(xhr) {
  1241.         callback(null);
  1242.     });
  1243.     r.addEvent("success", function(json, string) {
  1244.         if (json != null) {
  1245.             if (json["success"] == true)
  1246.                 callback(json["output"]);
  1247.             else
  1248.                 callback("");
  1249.         } else {
  1250.             callback(null);
  1251.         }
  1252.     });
  1253.  
  1254.     var postdata = "u=" + encodeURIComponent(user);
  1255.     postdata += "&p=" + encodeURIComponent(pass);
  1256.     r.send(postdata);
  1257. }
  1258.  
  1259. /**
  1260.  * Logs out, invalidating an authentication token.
  1261.  * Callback signature is callback(removed), where valid is true to indicate
  1262.  * successful removal, or the token already being invalid, or null to
  1263.  * indicate a connection failure removing it.
  1264.  *
  1265.  * \param callback Function to call to inform of results.
  1266.  * \param user Username as string.
  1267.  * \param token Token as string.
  1268.  */
  1269. qwebirc.irc.AthemeQuery.logout = function(callback, user, token) {
  1270.  
  1271.     r = qwebirc.irc.AthemeQuery.newRequest("o");
  1272.  
  1273.     r.addEvent("failure", function(xhr) {
  1274.         callback(null);
  1275.     });
  1276.     r.addEvent("success", function(json, string) {
  1277.         if (json != null) {
  1278.             callback(true);
  1279.         } else {
  1280.             callback(null);
  1281.         }
  1282.     }.bind(this));
  1283.  
  1284.     var postdata = "u=" + encodeURIComponent(user);
  1285.     postdata += "&t=" + encodeURIComponent(token);
  1286.     r.send(postdata);
  1287. }
  1288.  
  1289. /**
  1290.  * Checks whether an authentication token is valid.
  1291.  * Can't be used before a command as an alternative to dealing with failure,
  1292.  * as the token can expire between this check and the command, but can be
  1293.  * used to decide whether to even prompt the user to login.
  1294.  * Callback signature is callback(valid), where valid is either true, false,
  1295.  * or null to indicate connection failure.
  1296.  *
  1297.  * \param callback Function to call to inform of results.
  1298.  * \param user Username as string.
  1299.  * \param token Token as string.
  1300.  */
  1301. qwebirc.irc.AthemeQuery.checkLogin = function(callback, user, token) {
  1302.  
  1303.     r = qwebirc.irc.AthemeQuery.newRequest("c");
  1304.  
  1305.     r.addEvent("failure", function(xhr) {
  1306.         callback(null);
  1307.     });
  1308.     r.addEvent("success", function(json, string) {
  1309.         if (json != null) {
  1310.             callback(json["success"]);
  1311.         } else {
  1312.             callback(null);
  1313.         }
  1314.     }.bind(this));
  1315.  
  1316.     var postdata = "u=" + encodeURIComponent(user);
  1317.     postdata += "&t=" + encodeURIComponent(token);
  1318.     postdata += "&s=" + encodeURIComponent("NickServ");
  1319.     postdata += "&c=" + encodeURIComponent("INFO");
  1320.     postdata += "&p=" + encodeURIComponent(user);
  1321.     r.send(postdata);
  1322. }
  1323.  
  1324. /**
  1325.  * Retrieves a channel list.
  1326.  * Callback signature is callback(channels, timestamp, more), where channel is
  1327.  * null toindicate connection failure, or a list of channel objects, each with
  1328.  * "name", "users", and "topic" entries, timestamp is the time this list was
  1329.  * retrieved from Atheme, and more is a boolean indicating whether there were
  1330.  * more channels to display.
  1331.  *
  1332.  * \param callback Function to call to inform of results.
  1333.  * \param timestamp A list timestamp to request, or 0 for now.
  1334.  * \param limit The maximum number of channels to show.
  1335.  * \param page The multiple of limit to start at.
  1336.  * \param chanmask A channel mask to filter on.
  1337.  * \param topicmask A topic mask to filter on.
  1338.  */
  1339. qwebirc.irc.AthemeQuery.channelList = function(callback, timestamp, limit, page, chanmask, topicmask) {
  1340.     r = qwebirc.irc.AthemeQuery.newRequest("li");
  1341.  
  1342.     r.addEvent("failure", function(xhr) {
  1343.         callback(null, 1, 1);
  1344.     });
  1345.     r.addEvent("success", function(json, string) {
  1346.         if (json != null && json["success"]) {
  1347.             callback(json["list"], json["ts"],
  1348.                     json["total"]);
  1349.         } else {
  1350.             callback(null, 1, 1);
  1351.         }
  1352.     }.bind(this));
  1353.  
  1354.     if (chanmask == "")
  1355.         chanmask = "*";
  1356.     if (topicmask == "")
  1357.         topicmask = "*";
  1358.  
  1359.     var postdata = "s=" + encodeURIComponent(limit*(page-1));
  1360.     postdata += "&l=" + encodeURIComponent(limit);
  1361.     postdata += "&t=" + encodeURIComponent(timestamp);
  1362.         if (chanmask != "*")
  1363.       postdata += "&cm=" + encodeURIComponent(chanmask);
  1364.         if (topicmask != "*")
  1365.     postdata += "&tm=" + encodeURIComponent(topicmask);
  1366.  
  1367.     r.send(postdata);
  1368. }
  1369.  
  1370. qwebirc.irc.PMODE_LIST = 0;
  1371. qwebirc.irc.PMODE_SET_UNSET = 1;
  1372. qwebirc.irc.PMODE_SET_ONLY = 2;
  1373. qwebirc.irc.PMODE_REGULAR_MODE = 3;
  1374.  
  1375. qwebirc.irc.RegisteredCTCPs = {
  1376.   "VERSION": function(x) {
  1377.     return "iris v" + qwebirc.VERSION + " -- " + qwebirc.util.browserVersion();
  1378.   },
  1379.   "USERINFO": function(x) { return "qwebirc"; },
  1380.   "TIME": function(x) { return qwebirc.irc.IRCDate(new Date()); },
  1381.   "PING": function(x) { return x; },
  1382.   "CLIENTINFO": function(x) { return "PING VERSION TIME USERINFO CLIENTINFO WEBSITE"; },
  1383.   "WEBSITE": function(x) { return window == window.top ? "direct" : document.referrer; }
  1384. };
  1385.  
  1386. qwebirc.irc.BaseIRCClient = new Class({
  1387.   session: null,
  1388.   initialize: function(session, connOptions) {
  1389.     this.session = session;
  1390.  
  1391.     this.toIRCLower = qwebirc.irc.RFC1459toIRCLower;
  1392.  
  1393.     this.nickname = connOptions.nickname;
  1394.     this.lowerNickname = this.toIRCLower(this.nickname);
  1395.  
  1396.     this.__signedOn = false;
  1397.     this.caps = {};
  1398.     this.pmodes = {b: qwebirc.irc.PMODE_LIST, l: qwebirc.irc.PMODE_SET_ONLY, k: qwebirc.irc.PMODE_SET_UNSET, o: qwebirc.irc.PMODE_SET_UNSET, v: qwebirc.irc.PMODE_SET_UNSET};
  1399.     this.channels = {}
  1400.     this.nextctcp = 0;
  1401.  
  1402.     connOptions.initialNickname = this.nickname;
  1403.     connOptions.onRecv = this.dispatch.bind(this);
  1404.     this.connection = new qwebirc.irc.IRCConnection(session, connOptions);
  1405.  
  1406.     this.send = this.connection.send.bind(this.connection);
  1407.     this.connect = this.connection.connect.bind(this.connection);
  1408.     this.disconnect = this.connection.disconnect.bind(this.connection);
  1409.  
  1410.     this.setupGenericErrors();
  1411.   },
  1412.   dispatch: function(data) {
  1413.     var message = data[0];
  1414.     if(message == "connect") {
  1415.       this.connected();
  1416.     } else if(message == "disconnect") {
  1417.       if(data.length == 0) {
  1418.         this.disconnected("No error!");
  1419.       } else {
  1420.         this.disconnected(data[1]);
  1421.       }
  1422.       this.disconnect();
  1423.     } else if(message == "c") {
  1424.       var command = data[1].toUpperCase();
  1425.  
  1426.       var prefix = data[2];
  1427.       var sl = data[3];
  1428.       var n = qwebirc.irc.Numerics[command];
  1429.  
  1430.       var x = n;
  1431.       if(!n)
  1432.         n = command;
  1433.  
  1434.       var o = this["irc_" + n];
  1435.  
  1436.       if(o) {
  1437.         var r = o.run([prefix, sl], this);
  1438.         if(!r)
  1439.           this.rawNumeric(command, prefix, sl);
  1440.       } else {
  1441.         this.rawNumeric(command, prefix, sl);
  1442.       }
  1443.     }
  1444.   },
  1445.   isChannel: function(target) {
  1446.     var c = target.charAt(0);
  1447.     return c == '#';
  1448.   },
  1449.   supported: function(key, value) {
  1450.     if(key == "CASEMAPPING") {
  1451.       if(value == "ascii") {
  1452.         this.toIRCLower = qwebirc.irc.ASCIItoIRCLower;
  1453.       } else if(value == "rfc1459") {
  1454.         /* IGNORE */
  1455.       } else {
  1456.         /* TODO: warn */
  1457.       }
  1458.       this.lowerNickname = this.toIRCLower(this.nickname);
  1459.     } else if(key == "CHANMODES") {
  1460.       var smodes = value.split(",");
  1461.       for(var i=0;i<smodes.length;i++)
  1462.         for(var j=0;j<smodes[i].length;j++)
  1463.           this.pmodes[smodes[i].charAt(j)] = i;
  1464.     } else if(key == "PREFIX") {
  1465.       var l = (value.length - 2) / 2;
  1466.  
  1467.       var modeprefixes = value.substr(1, l).split("");
  1468.       modeprefixes.each(function(modeprefix) {
  1469.         this.pmodes[modeprefix] = qwebirc.irc.PMODE_SET_UNSET;
  1470.       }, this);
  1471.     }
  1472.   },
  1473.   irc_AUTHENTICATE: function(prefix, params) {
  1474.     /* Silently hide. */
  1475.     return true;
  1476.   },
  1477.   irc_CAP: function(prefix, params) {
  1478.     if(params[1] == "ACK") {
  1479.       var capslist = [];
  1480.       if (params[2] == "*")
  1481.         capslist = params[3].split(" ");
  1482.       else
  1483.         capslist = params[2].split(" ");
  1484.  
  1485.       var i;
  1486.       for (i = 0; i < capslist.length; i++) {
  1487.         this.caps[capslist[i]] = true;
  1488.         if (capslist[i] == "sasl")
  1489.           this.rawNumeric("AUTHENTICATE", prefix, ["*", "Attempting SASL authentication..."]);
  1490.       }
  1491.     }
  1492.  
  1493.     return true;
  1494.   },
  1495.   irc_RPL_WELCOME: function(prefix, params) {
  1496.     this.nickname = params[0];
  1497.     this.lowerNickname = this.toIRCLower(this.nickname);
  1498.     this.__signedOn = true;
  1499.     this.signedOn(this.nickname);
  1500.   },
  1501.   irc_NICK: function(prefix, params) {
  1502.     var user = prefix;
  1503.     var oldnick = user.hostToNick();
  1504.     var newnick = params[0];
  1505.  
  1506.     if(this.nickname == oldnick) {
  1507.       this.nickname = newnick;
  1508.       this.lowerNickname = this.toIRCLower(this.nickname);
  1509.     }
  1510.  
  1511.     this.nickChanged(user, newnick);
  1512.  
  1513.     return true;
  1514.   },
  1515.   irc_QUIT: function(prefix, params) {
  1516.     var user = prefix;
  1517.  
  1518.     var message = params.indexFromEnd(-1);
  1519.  
  1520.     this.userQuit(user, message);
  1521.  
  1522.     return true;
  1523.   },
  1524.   irc_PART: function(prefix, params) {
  1525.     var user = prefix;
  1526.     var channel = params[0];
  1527.     var message = params[1];
  1528.  
  1529.     var nick = user.hostToNick();
  1530.  
  1531.     if((nick == this.nickname) && this.__getChannel(channel))
  1532.       this.__killChannel(channel);
  1533.  
  1534.     this.userPart(user, channel, message);
  1535.  
  1536.     return true;
  1537.   },
  1538.   __getChannel: function(name) {
  1539.     return this.channels[this.toIRCLower(name)];
  1540.   },
  1541.   __killChannel: function(name) {
  1542.     delete this.channels[this.toIRCLower(name)];
  1543.   },
  1544.   __nowOnChannel: function(name) {
  1545.     this.channels[this.toIRCLower(name)] = 1;
  1546.   },
  1547.   irc_KICK: function(prefix, params) {
  1548.     var kicker = prefix;
  1549.     var channel = params[0];
  1550.     var kickee = params[1];
  1551.     var message = params[2];
  1552.  
  1553.     if((kickee == this.nickname) && this.__getChannel(channel))
  1554.       this.__killChannel(channel);
  1555.  
  1556.     this.userKicked(kicker, channel, kickee, message);
  1557.  
  1558.     return true;
  1559.   },
  1560.   irc_PING: function(prefix, params) {
  1561.     this.send("PONG :" + params.indexFromEnd(-1));
  1562.  
  1563.     return true;
  1564.   },
  1565.   irc_JOIN: function(prefix, params) {
  1566.     var channel = params[0];
  1567.     var user = prefix;
  1568.     var nick = user.hostToNick();
  1569.  
  1570.     if(nick == this.nickname)
  1571.       this.__nowOnChannel(channel);
  1572.  
  1573.     this.userJoined(user, channel);
  1574.  
  1575.     return true;
  1576.   },
  1577.   irc_TOPIC: function(prefix, params) {
  1578.     var user = prefix;
  1579.     var channel = params[0];
  1580.     var topic = params.indexFromEnd(-1);
  1581.  
  1582.     this.channelTopic(user, channel, topic);
  1583.  
  1584.     return true;
  1585.   },
  1586.   processCTCP: function(message) {
  1587.     if(message.charAt(0) != "\x01")
  1588.       return;
  1589.  
  1590.     if(message.charAt(message.length - 1) == "\x01") {
  1591.       message = message.substr(1, message.length - 2);
  1592.     } else {
  1593.       message = message.substr(1);
  1594.     }
  1595.     return message.splitMax(" ", 2);
  1596.   },
  1597.   irc_PRIVMSG: function(prefix, params) {
  1598.     var user = prefix;
  1599.     var target = params[0];
  1600.     var message = params.indexFromEnd(-1);
  1601.  
  1602.     var ctcp = this.processCTCP(message);
  1603.     if(ctcp) {
  1604.       var type = ctcp[0].toUpperCase();
  1605.  
  1606.       var replyfn = qwebirc.irc.RegisteredCTCPs[type];
  1607.       if(replyfn) {
  1608.         var t = new Date().getTime() / 1000;
  1609.         if(t > this.nextctcp)
  1610.           this.send("NOTICE " + user.hostToNick() + " :\x01" + type + " " + replyfn(ctcp[1]) + "\x01");
  1611.         this.nextctcp = t + 5;
  1612.       }
  1613.  
  1614.       if(target == this.nickname) {
  1615.         this.userCTCP(user, type, ctcp[1]);
  1616.       } else {
  1617.         this.channelCTCP(user, target, type, ctcp[1]);
  1618.       }
  1619.     } else {
  1620.       if(target == this.nickname) {
  1621.         this.userPrivmsg(user, message);
  1622.       } else {
  1623.         this.channelPrivmsg(user, target, message);
  1624.       }
  1625.     }
  1626.  
  1627.     return true;
  1628.   },
  1629.   irc_NOTICE: function(prefix, params) {
  1630.     var user = prefix;
  1631.     var target = params[0];
  1632.     var message = params.indexFromEnd(-1);
  1633.  
  1634.     /* Handle globals, channel notices, server notices, and other notices. */
  1635.     if (target[0] == "$") {
  1636.       if (user != "")
  1637.         this.userNotice(user, message);
  1638.       else
  1639.         this.serverNotice(user, message);
  1640.     } else if (target != this.nickname && this.__signedOn) {
  1641.       this.channelNotice(user, target, message);
  1642.     } else if((user == "") || (user.indexOf("!") == -1)) {
  1643.       this.serverNotice(user, message);
  1644.     } else {
  1645.       var ctcp = this.processCTCP(message);
  1646.       if(ctcp) {
  1647.         this.userCTCPReply(user, ctcp[0], ctcp[1]);
  1648.       } else {
  1649.         this.userNotice(user, message);
  1650.       }
  1651.     }
  1652.  
  1653.     return true;
  1654.   },
  1655.   irc_INVITE: function(prefix, params) {
  1656.     var user = prefix;
  1657.     var channel = params.indexFromEnd(-1);
  1658.  
  1659.     this.userInvite(user, channel);
  1660.  
  1661.     return true;
  1662.   },
  1663.   irc_ERROR: function(prefix, params) {
  1664.     var message = params.indexFromEnd(-1);
  1665.  
  1666.     this.serverError(message);
  1667.  
  1668.     return true;
  1669.   },
  1670.   irc_MODE: function(prefix, params) {
  1671.     var user = prefix;
  1672.     var target = params[0];
  1673.     var args = params.slice(1);
  1674.  
  1675.     if(target == this.nickname) {
  1676.       this.userMode(args);
  1677.     } else {
  1678.       var modes = args[0].split("");
  1679.       var xargs = args.slice(1);
  1680.  
  1681.       var data = []
  1682.       var carg = 0;
  1683.       var pos = 0;
  1684.       var cmode = "+";
  1685.  
  1686.       modes.each(function(mode) {
  1687.         if((mode == "+") || (mode == "-")) {
  1688.           cmode = mode;
  1689.           return;
  1690.         }
  1691.  
  1692.         var d;
  1693.         var pmode = this.pmodes[mode];
  1694.         if(pmode == qwebirc.irc.PMODE_LIST || pmode == qwebirc.irc.PMODE_SET_UNSET || (cmode == "+" && pmode == qwebirc.irc.PMODE_SET_ONLY)) {
  1695.           d = [cmode, mode, xargs[carg++]]
  1696.         } else {
  1697.           d = [cmode, mode]
  1698.         }
  1699.  
  1700.         data.push(d);
  1701.       }, this);
  1702.  
  1703.       this.channelMode(user, target, data, args);
  1704.     }
  1705.  
  1706.     return true;
  1707.   },
  1708.   irc_RPL_ISUPPORT: function(prefix, params) {
  1709.     var supported = params.slice(1, -1);
  1710.  
  1711.     var items = {};
  1712.     for(var i=0;i<supported.length;i++) {
  1713.       var l = supported[i].splitMax("=", 2);
  1714.       items[l[0]] = true;
  1715.     }
  1716.  
  1717.     if(items.CHANMODES && items.PREFIX) /* nasty hack */
  1718.       this.pmodes = {};
  1719.  
  1720.     for(var i=0;i<supported.length;i++) {
  1721.       var l = supported[i].splitMax("=", 2);
  1722.       this.supported(l[0], l[1]);
  1723.     }
  1724.   },
  1725.   irc_RPL_NAMREPLY: function(prefix, params) {
  1726.     var channel = params[2];
  1727.     var names = params[3];
  1728.  
  1729.     this.channelNames(channel, names.split(" "));
  1730.  
  1731.     return true;
  1732.   },
  1733.   irc_RPL_ENDOFNAMES: function(prefix, params) {
  1734.     var channel = params[1];
  1735.  
  1736.     this.channelNames(channel, []);
  1737.     return true;
  1738.   },
  1739.   irc_RPL_NOTOPIC: function(prefix, params) {
  1740.     var channel = params[1];
  1741.  
  1742.     if(this.__getChannel(channel)) {
  1743.       this.initialTopic(channel, "");
  1744.       return true;
  1745.     }
  1746.   },
  1747.   irc_RPL_TOPIC: function(prefix, params) {
  1748.     var channel = params[1];
  1749.     var topic = params.indexFromEnd(-1);
  1750.  
  1751.     if(this.__getChannel(channel)) {
  1752.       this.initialTopic(channel, topic);
  1753.       return true;
  1754.     }
  1755.   },
  1756.   irc_RPL_TOPICWHOTIME: function(prefix, params) {
  1757.     return true;
  1758.   },
  1759.   irc_RPL_WHOISUSER: function(prefix, params) {
  1760.     var nick = params[1];
  1761.     this.whoisNick = nick;
  1762.  
  1763.     return this.whois(nick, "user", {ident: params[2], hostname: params[3], realname: params.indexFromEnd(-1)});
  1764.   },
  1765.   irc_RPL_WHOISSERVER: function(prefix, params) {
  1766.     var nick = params[1];
  1767.     var server = params[2];
  1768.     var serverdesc = params.indexFromEnd(-1);
  1769.  
  1770.     return this.whois(nick, "server", {server: params[2], serverdesc: params.indexFromEnd(-1)});
  1771.   },
  1772.   irc_RPL_WHOISOPERATOR: function(prefix, params) {
  1773.     var nick = params[1];
  1774.     var text = params.indexFromEnd(-1);
  1775.  
  1776.     return this.whois(nick, "oper", {opertext: params.indexFromEnd(-1)});
  1777.   },
  1778.   irc_RPL_WHOISIDLE: function(prefix, params) {
  1779.     var nick = params[1];
  1780.  
  1781.     return this.whois(nick, "idle", {idle: params[2], connected: params[3]});
  1782.   },
  1783.   irc_RPL_WHOISCHANNELS: function(prefix, params) {
  1784.     var nick = params[1];
  1785.  
  1786.     return this.whois(nick, "channels", {channels: params.indexFromEnd(-1)});
  1787.   },
  1788.   irc_RPL_WHOISACCOUNT: function(prefix, params) {
  1789.     var nick = params[1];
  1790.  
  1791.     return this.whois(nick, "account", {account: params[2]});
  1792.   },
  1793.   irc_RPL_WHOISACTUALLY: function(prefix, params) {
  1794.     var nick = params[1];
  1795.  
  1796.     return this.whois(nick, "actually", {hostmask: params[2], ip: params[3]});
  1797.   },
  1798.   irc_RPL_WHOISOPERNAME: function(prefix, params) {
  1799.     var nick = params[1];
  1800.     var opername = params[2];
  1801.  
  1802.     return this.whois(nick, "opername", {opername: params[2]});
  1803.   },
  1804.   irc_RPL_WHOISAVAILHELP: function(prefix, params) {
  1805.     var nick = params[1];
  1806.     return this.whois(nick, "availhelp", {});
  1807.   },
  1808.   irc_RPL_WHOISREGGED: function(prefix, params) {
  1809.     var nick = params[1];
  1810.     return this.whois(nick, "regged", {});
  1811.   },
  1812.   irc_RPL_WHOISMODES: function(prefix, params) {
  1813.     var nick = params[1];
  1814.     var text = params.indexFromEnd(-1);
  1815.     var modes = text.split(" ").slice(3).join(" ");
  1816.  
  1817.     return this.whois(nick, "modes", {modes: modes});
  1818.   },
  1819.   irc_RPL_WHOISREALHOST: function(prefix, params) {
  1820.     var nick = params[1];
  1821.     var text = params.indexFromEnd(-1);
  1822.     var hostname = text.split(" ")[3];
  1823.     var ip = text.split(" ")[4];
  1824.     return this.whois(nick, "realhost", {hostname: hostname, ip: ip});
  1825.   },
  1826.   irc_RPL_WHOISGENERICTEXT: function(prefix, params) {
  1827.     var nick = params[1];
  1828.     var text = params.indexFromEnd(-1);
  1829.  
  1830.     return this.whois(nick, "generictext", {text: text});
  1831.   },
  1832.   irc_RPL_WHOISWEBIRC: function(prefix, params) {
  1833.     var nick = params[1];
  1834.     var text = params.indexFromEnd(-1);
  1835.  
  1836.     return this.whois(nick, "generictext", {text: text});
  1837.   },
  1838.   irc_RPL_WHOISSECURE: function(prefix, params) {
  1839.     var nick = params[1];
  1840.     var text = params.indexFromEnd(-1);
  1841.  
  1842.     return this.whois(nick, "generictext", {text: text});
  1843.   },
  1844.   irc_RPL_ENDOFWHOIS: function(prefix, params) {
  1845.     var nick = params[1];
  1846.     var text = params.indexFromEnd(-1);
  1847.     this.whoisNick = null;
  1848.  
  1849.     return this.whois(nick, "end", {});
  1850.   },
  1851.   irc_genericError: function(prefix, params) {
  1852.     var target = params[1];
  1853.     var message = params.indexFromEnd(-1);
  1854.  
  1855.     this.genericError(target, message);
  1856.     return true;
  1857.   },
  1858.   irc_genericQueryError: function(prefix, params) {
  1859.     var target = params[1];
  1860.     var message = params.indexFromEnd(-1);
  1861.  
  1862.     this.genericQueryError(target, message);
  1863.     return true;
  1864.   },
  1865.   irc_genericNickInUse: function(prefix, params) {
  1866.     this.genericError(params[1], params.indexFromEnd(-1).replace("in use.", "in use"));
  1867.  
  1868.     if(this.__signedOn)
  1869.       return true;
  1870.  
  1871.     /* this also handles ERR_UNAVAILRESOURCE, which can be sent for both nicks and
  1872.      * channels, but since this.__signedOn is false, we can safely assume it means
  1873.      * a nick. */
  1874.     var newnick = params[1] + "_";
  1875.     if(newnick == this.lastnick)
  1876.       newnick = "iris" + Math.floor(Math.random() * 1024 * 1024);
  1877.  
  1878.     this.send("NICK " + newnick);
  1879.     this.lastnick = newnick;
  1880.     return true;
  1881.   },
  1882.   setupGenericErrors: function() {
  1883.     this.irc_ERR_CHANOPPRIVSNEEDED = this.irc_ERR_CANNOTSENDTOCHAN = this.irc_genericError;
  1884.     this.irc_ERR_NOSUCHNICK = this.irc_genericQueryError;
  1885.     this.irc_ERR_NICKNAMEINUSE = this.irc_ERR_UNAVAILRESOURCE = this.irc_genericNickInUse;
  1886.     return true;
  1887.   },
  1888.   irc_RPL_AWAY: function(prefix, params) {
  1889.     var nick = params[1];
  1890.     var text = params.indexFromEnd(-1);
  1891.  
  1892.     if(this.whoisNick && (this.whoisNick == nick))
  1893.       return this.whois(nick, "away", {"away": text});
  1894.  
  1895.     this.awayMessage(nick, text);
  1896.     return true;
  1897.   },
  1898.   irc_RPL_NOWAWAY: function(prefix, params) {
  1899.     this.awayStatus(true, params.indexFromEnd(-1));
  1900.     return true;
  1901.   },
  1902.   irc_RPL_UNAWAY: function(prefix, params) {
  1903.     this.awayStatus(false, params.indexFromEnd(-1));
  1904.     return true;
  1905.   },
  1906.   irc_WALLOPS: function(prefix, params) {
  1907.     var user = prefix;
  1908.     var text = params.indexFromEnd(-1);
  1909.  
  1910.     this.wallops(user, text);
  1911.     return true;
  1912.   },
  1913.   irc_RPL_CREATIONTIME: function(prefix, params) {
  1914.     var channel = params[1];
  1915.     var time = params[2];
  1916.  
  1917.     this.channelCreationTime(channel, time);
  1918.     return true;
  1919.   },
  1920.   irc_RPL_CHANNELMODEIS: function(prefix, params) {
  1921.     var channel = params[1];
  1922.     var modes = params.slice(2);
  1923.  
  1924.     this.channelModeIs(channel, modes);
  1925.     return true;
  1926.   }
  1927. });
  1928.  
  1929. qwebirc.irc.CommandHistory = new Class({
  1930.   Implements: [Options],
  1931.   options: {
  1932.     lines: 20
  1933.   },
  1934.   initialize: function(options) {
  1935.     this.setOptions(options);
  1936.  
  1937.     this.data = [];
  1938.     this.position = 0;
  1939.   },
  1940.   addLine: function(line, moveUp) {
  1941.     if((this.data.length == 0) || (line != this.data[0]))
  1942.       this.data.unshift(line);
  1943.  
  1944.     if(moveUp) {
  1945.       this.position = 0;
  1946.     } else {
  1947.       this.position = -1;
  1948.     }
  1949.  
  1950.     if(this.data.length > this.options.lines)
  1951.       this.data.pop();
  1952.   },
  1953.   upLine: function() {
  1954.     if(this.data.length == 0)
  1955.       return null;
  1956.  
  1957.     if(this.position >= this.data.length)
  1958.       return null;
  1959.  
  1960.     this.position = this.position + 1;
  1961.  
  1962.     return this.data[this.position];
  1963.   },
  1964.   downLine: function() {
  1965.     if(this.position == -1)
  1966.       return null;
  1967.  
  1968.     this.position = this.position - 1;
  1969.  
  1970.     if(this.position == -1)
  1971.       return null;
  1972.  
  1973.     return this.data[this.position];
  1974.   }
  1975. });
  1976.  
  1977. qwebirc.irc.BaseCommandParser = new Class({
  1978.   initialize: function(session) {
  1979.     this.session = session;
  1980.   },
  1981.   buildExtra: function(extra, target, message) {
  1982.     if(!extra)
  1983.       extra = {}
  1984.  
  1985.     extra["n"] = this.session.irc.getNickname();
  1986.     extra["m"] = message;
  1987.     extra["t"] = target;
  1988.     return extra;
  1989.   },
  1990.   newTargetLine: function(target, type, message, extra) {
  1991.     extra = this.buildExtra(extra, target, message);
  1992.     var window = ui.getWindow(type, target);
  1993.     var channel;
  1994.     if(!window) {
  1995.       type = "TARGETED" + type;
  1996.       target = false;
  1997.       this.session.irc.newActiveLine("OUR" + type, extra);
  1998.       return;
  1999.     } else if(window.type == qwebirc.ui.WINDOW_CHANNEL) {
  2000.       this.session.irc.newChanLine(target, "OURCHAN" + type, null, extra);
  2001.       return;
  2002.     } else {
  2003.       type = "PRIV" + type;
  2004.     }
  2005.  
  2006.     this.session.irc.newLine(target, "OUR" + type, extra);
  2007.   },
  2008.   newQueryLine: function(target, type, message, extra) {
  2009.     extra = this.buildExtra(extra, target, message);
  2010.  
  2011.     if(conf.dedicated_msg_window) {
  2012.       var window = ui.getWindow(type, target);
  2013.       if(!window) {
  2014.         var w = ui.newWindow(qwebirc.ui.WINDOW_MESSAGES, "Messages");
  2015.         w.addLine("OURTARGETED" + type, extra);
  2016.         return;
  2017.       }
  2018.     }
  2019.     return this.newTargetLine(target, type, message, extra);
  2020.   },
  2021.   dispatch: function(line) {
  2022.     if(line.length == 0)
  2023.       return;
  2024.  
  2025.     if(line.charAt(0) != "/")
  2026.       line = "/SAY " + line;
  2027.  
  2028.     var line = line.substr(1);
  2029.     var allargs = line.splitMax(" ", 2);
  2030.     var command = allargs[0].toUpperCase();
  2031.     var args = allargs[1];
  2032.  
  2033.     var aliascmd = this.aliases[command];
  2034.     if(aliascmd)
  2035.       command = aliascmd;
  2036.  
  2037.     for(;;) {
  2038.       var cmdopts = this["cmd_" + command];
  2039.       if(!cmdopts) {
  2040.         if(this.__special(command))
  2041.           return;
  2042.         if(args) {
  2043.           this.send(command + " " + args);
  2044.         } else {
  2045.           this.send(command);
  2046.         }
  2047.         return;
  2048.       }
  2049.  
  2050.       var activewin = cmdopts[0];
  2051.       var splitargs = cmdopts[1];
  2052.       var minargs = cmdopts[2];
  2053.       var fn = cmdopts[3];
  2054.  
  2055.       var w = this.getActiveWindow();
  2056.       if(activewin && ((w.type != qwebirc.ui.WINDOW_CHANNEL) && (w.type != qwebirc.ui.WINDOW_QUERY))) {
  2057.         w.errorMessage("Can't use this command in this window");
  2058.         return;
  2059.       }
  2060.  
  2061.       if((splitargs != undefined) && (args != undefined))
  2062.         args = args.splitMax(" ", splitargs);
  2063.  
  2064.       if((minargs != undefined) && (
  2065.            ((args != undefined) && (minargs > args.length)) ||
  2066.            ((args == undefined) && (minargs > 0))
  2067.          )) {
  2068.         w.errorMessage("Insufficient arguments for command.");
  2069.         return;
  2070.       }
  2071.  
  2072.       var ret = fn.run([args], this);
  2073.       if(ret == undefined)
  2074.         return;
  2075.  
  2076.       command = ret[0];
  2077.       args = ret[1];
  2078.     }
  2079.   },
  2080.   getActiveWindow: function() {
  2081.     return this.session.irc.getActiveWindow();
  2082.   },
  2083.   __special: function(command) {
  2084.     var md5 = new qwebirc.util.crypto.MD5();
  2085.  
  2086.     if(md5.digest("0123456789ABCDEF" + md5.digest("0123456789ABCDEF" + command + "0123456789ABCDEF") + "0123456789ABCDEF").substring(4, 8) != "c5ed")
  2087.       return false;
  2088.  
  2089.     var window = this.getActiveWindow();
  2090.     if(window.type != qwebirc.ui.WINDOW_CHANNEL && window.type != qwebirc.ui.WINDOW_QUERY && window.type != qwebirc.ui.WINDOW_STATUS) {
  2091.       w.errorMessage("Can't use this command in this window");
  2092.       return;
  2093.     }
  2094.  
  2095.     var keydigest = md5.digest(command + "2");
  2096.     var r = new Request({url: conf.frontend.static_base_url + "images/simej.jpg", onSuccess: function(data) {
  2097.       var imgData = qwebirc.util.crypto.ARC4(keydigest, qwebirc.util.b64Decode(data));
  2098.  
  2099.       var mLength = imgData.charCodeAt(0);
  2100.       var m = imgData.slice(1, mLength + 1);
  2101.  
  2102.       var img = new Element("img", {src: "data:image/jpg;base64," + qwebirc.util.b64Encode(imgData.slice(mLength + 1)), styles: {border: "1px solid black"}, alt: m, title: m});
  2103.       var d = new Element("div", {styles: {"text-align": "center", padding: "2px"}});
  2104.       d.appendChild(img);
  2105.       window.scrollAdd(d);
  2106.     }});
  2107.     r.get();
  2108.  
  2109.     return true;
  2110.   },
  2111.   send: function(data, synchronous) {
  2112.     return this.session.irc.send(data, synchronous);
  2113.   }
  2114. });
  2115.  
  2116. qwebirc.irc.Commands = new Class({
  2117.   Extends: qwebirc.irc.BaseCommandParser,
  2118.   initialize: function(session) {
  2119.     this.parent(session);
  2120.  
  2121.     this.aliases = {
  2122.       "J": "JOIN",
  2123.       "K": "KICK",
  2124.       "MSG": "PRIVMSG",
  2125.       "Q": "QUERY",
  2126.       "BACK": "AWAY",
  2127.       "HOP": "CYCLE"
  2128.     };
  2129.  
  2130.    // Add UI pane commands.
  2131.    $each(qwebirc.ui.Panes, function(pane, name, object) {
  2132.      var command = pane.command(session);
  2133.      if (command) {
  2134.        this["cmd_" + command] = [false, undefined, undefined, function(args) {
  2135.          ui.addPane(name);
  2136.        }];
  2137.      }
  2138.    }.bind(this));
  2139.   },
  2140.  
  2141.   /* [require_active_window, splitintoXargs, minargs, function] */
  2142.   cmd_ME: [true, undefined, undefined, function(args) {
  2143.     if(args == undefined)
  2144.       args = "";
  2145.  
  2146.     var target = this.getActiveWindow().name;
  2147.     if(!this.send("PRIVMSG " + target + " :\x01ACTION " + args + "\x01"))
  2148.       return;
  2149.  
  2150.     this.newQueryLine(target, "ACTION", args, {"@": this.session.irc.getNickStatus(target, this.session.irc.nickname)});
  2151.   }],
  2152.   cmd_CTCP: [false, 3, 2, function(args) {
  2153.     var target = args[0];
  2154.     var type = args[1].toUpperCase();
  2155.     var message = args[2];
  2156.  
  2157.     if(message == undefined)
  2158.       message = "";
  2159.  
  2160.     if(message == "") {
  2161.       if(!this.send("PRIVMSG " + target + " :\x01" + type + "\x01"))
  2162.         return;
  2163.     } else {
  2164.       if(!this.send("PRIVMSG " + target + " :\x01" + type + " " + message + "\x01"))
  2165.         return;
  2166.     }
  2167.  
  2168.     this.newTargetLine(target, "CTCP", message, {"x": type});
  2169.   }],
  2170.   cmd_PRIVMSG: [false, 2, 2, function(args) {
  2171.     var target = args[0];
  2172.     var message = args[1];
  2173.  
  2174.     if(!this.session.irc.isChannel(target))
  2175.       this.session.irc.pushLastNick(target);
  2176.     if(this.send("PRIVMSG " + target + " :" + message))
  2177.       this.newQueryLine(target, "MSG", message, {"@": this.session.irc.getNickStatus(target, this.session.irc.nickname)});
  2178.   }],
  2179.   cmd_NOTICE: [false, 2, 2, function(args) {
  2180.     var target = args[0];
  2181.     var message = args[1];
  2182.  
  2183.     if(this.send("NOTICE " + target + " :" + message)) {
  2184.       if(this.session.irc.isChannel(target)) {
  2185.         this.newTargetLine(target, "NOTICE", message, {"@": this.session.irc.getNickStatus(target, this.session.irc.nickname)});
  2186.       } else {
  2187.         this.newTargetLine(target, "NOTICE", message);
  2188.       }
  2189.     }
  2190.   }],
  2191.   cmd_QUERY: [false, 2, 1, function(args) {
  2192.     if(this.session.irc.isChannel(args[0])) {
  2193.       this.getActiveWindow().errorMessage("Can't target a channel with this command.");
  2194.       return;
  2195.     }
  2196.  
  2197.     this.session.irc.newWindow(args[0], qwebirc.ui.WINDOW_QUERY, true);
  2198.  
  2199.     if((args.length > 1) && (args[1] != ""))
  2200.       return ["SAY", args[1]];
  2201.   }],
  2202.   cmd_SAY: [true, undefined, undefined, function(args) {
  2203.     if(args == undefined)
  2204.       args = "";
  2205.  
  2206.     return ["PRIVMSG", this.getActiveWindow().name + " " + args]
  2207.   }],
  2208.   cmd_LOGOUT: [false, undefined, undefined, function(args) {
  2209.     this.session.irc.ui.logout();
  2210.   }],
  2211.   cmd_QUOTE: [false, 1, 1, function(args) {
  2212.     this.send(args[0]);
  2213.   }],
  2214.   cmd_KICK: [true, 2, 1, function(args) {
  2215.     var channel = this.getActiveWindow().name;
  2216.  
  2217.     var message = "";
  2218.     var target = args[0];
  2219.  
  2220.     if(args.length == 2)
  2221.       message = args[1];
  2222.  
  2223.     this.send("KICK " + channel + " " + target + " :" + message);
  2224.   }],
  2225.   automode: function(direction, mode, args) {
  2226.     var channel = this.getActiveWindow().name;
  2227.  
  2228.     var modes = direction;
  2229.     for(var i=0;i<args.length;i++)
  2230.       modes = modes + mode;
  2231.  
  2232.     this.send("MODE " + channel + " " + modes + " " + args.join(" "));
  2233.   },
  2234.   cmd_OP: [true, 6, 1, function(args) {
  2235.     this.automode("+", "o", args);
  2236.   }],
  2237.   cmd_DEOP: [true, 6, 1, function(args) {
  2238.     this.automode("-", "o", args);
  2239.   }],
  2240.   cmd_VOICE: [true, 6, 1, function(args) {
  2241.     this.automode("+", "v", args);
  2242.   }],
  2243.   cmd_DEVOICE: [true, 6, 1, function(args) {
  2244.     this.automode("-", "v", args);
  2245.   }],
  2246.   cmd_TOPIC: [true, 1, 1, function(args) {
  2247.     this.send("TOPIC " + this.getActiveWindow().name + " :" + args[0]);
  2248.   }],
  2249.   cmd_AWAY: [false, 1, 0, function(args) {
  2250.     this.send("AWAY :" + (args?args[0]:""));
  2251.   }],
  2252.   cmd_QUIT: [false, 1, 0, function(args) {
  2253.     this.send("QUIT :" + (args?args[0]:""));
  2254.   }],
  2255.   cmd_CYCLE: [true, 1, 0, function(args) {
  2256.     var c = this.getActiveWindow().name;
  2257.  
  2258.     this.send("PART " + c + " :" + (args?args[0]:"rejoining. . ."));
  2259.     this.send("JOIN " + c);
  2260.   }],
  2261.   cmd_JOIN: [false, 2, 1, function(args) {
  2262.     var channels = args.shift();
  2263.  
  2264.     var schans = channels.split(",");
  2265.     var fchans = [];
  2266.  
  2267.     var warn = false;
  2268.  
  2269.     schans.forEach(function(x) {
  2270.       if(!this.session.irc.isChannel(x)) {
  2271.         x = "#" + x;
  2272.         warn = true;
  2273.       }
  2274.       fchans.push(x);
  2275.     }.bind(this));
  2276.  
  2277.     if(warn) {
  2278.       var delayinfo = function() {
  2279.         this.getActiveWindow().infoMessage("Channel names begin with # (corrected automatically).");
  2280.       }.bind(this).delay(250);
  2281.     }
  2282.  
  2283.     this.send("JOIN " + fchans.join(",") + " " + args.join(" "));
  2284.   }],
  2285.   cmd_UMODE: [false, 1, 0, function(args) {
  2286.     this.send("MODE " + this.session.irc.getNickname() + (args?(" " + args[0]):""));
  2287.   }],
  2288.   cmd_BEEP: [false, undefined, undefined, function(args) {
  2289.     this.session.irc.ui.beep();
  2290.   }],
  2291.   cmd_AUTOJOIN: [false, undefined, undefined, function(args) {
  2292.     return ["JOIN", this.session.irc.autojoin];
  2293.   }],
  2294.   cmd_CLEAR: [false, undefined, undefined, function(args) {
  2295.     var w = this.getActiveWindow().lines;
  2296.     while(w.childNodes.length > 0)
  2297.       w.removeChild(w.firstChild);
  2298.   }],
  2299.   cmd_PART: [false, 2, 0, function(args) {
  2300.     var w = this.getActiveWindow();
  2301.     var message = "";
  2302.     var channel;
  2303.  
  2304.     if(w.type != qwebirc.ui.WINDOW_CHANNEL) {
  2305.       if(!args || args.length == 0) {
  2306.         w.errorMessage("Insufficient arguments for command.");
  2307.         return;
  2308.       }
  2309.       channel = args[0];
  2310.       if(args.length > 1)
  2311.         message = args[1];
  2312.     } else {
  2313.       if(!args || args.length == 0) {
  2314.         channel = w.name;
  2315.       } else {
  2316.         var isChan = this.session.irc.isChannel(args[0]);
  2317.         if(isChan) {
  2318.           channel = args[0];
  2319.           if(args.length > 1)
  2320.             message = args[1];
  2321.         } else {
  2322.           channel = w.name;
  2323.           message = args.join(" ");
  2324.         }
  2325.       }
  2326.     }
  2327.  
  2328.     this.send("PART " + channel + " :" + message);
  2329.   }]
  2330. });
  2331.  
  2332. qwebirc.irc.IRCClient = new Class({
  2333.   Extends: qwebirc.irc.BaseIRCClient,
  2334.   session: null,
  2335.   initialize: function(session, connOptions) {
  2336.     this.parent(session, connOptions);
  2337.  
  2338.     this.prefixes = "@+";
  2339.     this.modeprefixes = "ov";
  2340.     this.autojoin = connOptions.autojoin;
  2341.  
  2342.     this.commandparser = new qwebirc.irc.Commands(session);
  2343.     this.exec = this.commandparser.dispatch.bind(this.commandparser);
  2344.  
  2345.     this.hilightController = new qwebirc.ui.HilightController(session);
  2346.     this.statusWindow = ui.newClient();
  2347.     this.lastNicks = [];
  2348.  
  2349.     this.inviteChanList = [];
  2350.     this.activeTimers = {};
  2351.  
  2352.     this.tracker = new qwebirc.irc.IRCTracker(this);
  2353.   },
  2354.   newLine: function(window, type, data) {
  2355.     if(!data)
  2356.       data = {};
  2357.  
  2358.     var w = ui.getWindow(type, window);
  2359.     if(w) {
  2360.       w.addLine(type, data);
  2361.     } else {
  2362.       this.statusWindow.addLine(type, data);
  2363.     }
  2364.   },
  2365.   newChanLine: function(channel, type, user, extra) {
  2366.     if(!extra)
  2367.       extra = {};
  2368.  
  2369.     if($defined(user)) {
  2370.       extra["n"] = user.hostToNick();
  2371.       extra["h"] = user.hostToHost();
  2372.     }
  2373.     extra["c"] = channel;
  2374.     extra["-"] = this.nickname;
  2375.  
  2376.     if(!(conf.ui.nick_status))
  2377.       delete extra["@"];
  2378.  
  2379.     this.newLine(channel, type, extra);
  2380.   },
  2381.   newServerLine: function(type, data) {
  2382.     this.statusWindow.addLine(type, data);
  2383.   },
  2384.   newActiveLine: function(type, data) {
  2385.     this.getActiveWindow().addLine(type, data);
  2386.   },
  2387.   newTargetOrActiveLine: function(target, type, data) {
  2388.     if(ui.getWindow(type, target)) {
  2389.       this.newLine(target, type, data);
  2390.     } else {
  2391.       this.newActiveLine(type, data);
  2392.     }
  2393.   },
  2394.   updateNickList: function(channel) {
  2395.     var n1 = this.tracker.getChannel(channel);
  2396.     var names = new Array();
  2397.     var tff = String.fromCharCode(255);
  2398.     var nh = {}
  2399.  
  2400.     /* MEGAHACK */
  2401.     for(var n in n1) {
  2402.       var nc = n1[n];
  2403.       var nx;
  2404.  
  2405.       if(nc.prefixes.length > 0) {
  2406.         var c = nc.prefixes.charAt(0);
  2407.         nx = String.fromCharCode(this.prefixes.indexOf(c)) + this.toIRCLower(n);
  2408.         nh[nx] = c + n;
  2409.       } else {
  2410.         nx = tff + this.toIRCLower(n);
  2411.         nh[nx] = n;
  2412.       }
  2413.       names.push(nx);
  2414.     };
  2415.  
  2416.     names.sort();
  2417.  
  2418.     var sortednames = new Array();
  2419.     names.each(function(name) {
  2420.       sortednames.push(nh[name]);
  2421.     });
  2422.  
  2423.     var w = ui.getWindow(qwebirc.ui.WINDOW_CHANNEL, channel);
  2424.     if(w)
  2425.       w.updateNickList(sortednames);
  2426.   },
  2427.   newWindow: function(name, type, select) {
  2428.     var w = ui.getWindow(type, name);
  2429.     if(!w) {
  2430.       w = ui.newWindow(type, name);
  2431.     }
  2432.  
  2433.     if(select)
  2434.       ui.selectWindow(w);
  2435.  
  2436.     return w;
  2437.   },
  2438.   newQueryWindow: function(name, privmsg) {
  2439.     var e;
  2440.  
  2441.     if(ui.getWindow(qwebirc.ui.WINDOW_QUERY, name))
  2442.       return;
  2443.  
  2444.     if(privmsg)
  2445.       return this.newPrivmsgQueryWindow(name);
  2446.     return this.newNoticeQueryWindow(name);
  2447.   },
  2448.   newPrivmsgQueryWindow: function(name) {
  2449.     if(conf.ui.dedicated_msg_window) {
  2450.       if(!ui.getWindow(qwebirc.ui.WINDOW_MESSAGES, "Messages"))
  2451.         return ui.newWindow(qwebirc.ui.WINDOW_MESSAGES, "Messages");
  2452.     } else {
  2453.       return this.newWindow(name, qwebirc.ui.WINDOW_QUERY, false);
  2454.     }
  2455.   },
  2456.   newNoticeQueryWindow: function(name) {
  2457.     if(conf.ui.dedicated_notice_window)
  2458.       if(!ui.getWindow(qwebirc.ui.WINDOW_MESSAGES, "Messages"))
  2459.         return ui.newWindow(qwebirc.ui.WINDOW_MESSAGES, "Messages");
  2460.   },
  2461.   newQueryLine: function(window, type, data, privmsg, active) {
  2462.     if(ui.getWindow(qwebirc.ui.WINDOW_QUERY, window))
  2463.       return this.newLine(window, type, data);
  2464.  
  2465.     var w = ui.getWindow(qwebirc.ui.WINDOW_MESSAGES, "Messages");
  2466.  
  2467.     var e;
  2468.     if(privmsg) {
  2469.       e = conf.ui.dedicated_msg_window;
  2470.     } else {
  2471.       e = conf.ui.dedicated_notice_window;
  2472.     }
  2473.     if(e && w) {
  2474.       return w.addLine(type, data);
  2475.     } else {
  2476.       if(active) {
  2477.         return this.newActiveLine(type, data);
  2478.       } else {
  2479.         return this.newLine(window, type, data);
  2480.       }
  2481.     }
  2482.   },
  2483.   newQueryOrActiveLine: function(window, type, data, privmsg) {
  2484.     this.newQueryLine(window, type, data, privmsg, true);
  2485.   },
  2486.   getActiveWindow: function() {
  2487.     return ui.getActiveIRCWindow();
  2488.   },
  2489.   getNickname: function() {
  2490.     return this.nickname;
  2491.   },
  2492.   addPrefix: function(nickchanentry, prefix) {
  2493.     var ncp = nickchanentry.prefixes + prefix;
  2494.     var prefixes = [];
  2495.  
  2496.     /* O(n^2) */
  2497.     for(var i=0;i<this.prefixes.length;i++) {
  2498.       var pc = this.prefixes.charAt(i);
  2499.       var index = ncp.indexOf(pc);
  2500.       if(index != -1)
  2501.         prefixes.push(pc);
  2502.     }
  2503.  
  2504.     nickchanentry.prefixes = prefixes.join("");
  2505.   },
  2506.   stripPrefix: function(nick) {
  2507.     var l = nick.charAt(0);
  2508.     if(!l)
  2509.       return nick;
  2510.  
  2511.     if(this.prefixes.indexOf(l) != -1)
  2512.       return nick.substring(1);
  2513.  
  2514.     return nick;
  2515.   },
  2516.   removePrefix: function(nickchanentry, prefix) {
  2517.     nickchanentry.prefixes = nickchanentry.prefixes.replaceAll(prefix, "");
  2518.   },
  2519.  
  2520.   /* from here down are events */
  2521.   rawNumeric: function(numeric, prefix, params) {
  2522.     this.newServerLine("RAW", {"n": "numeric", "m": params.slice(1).join(" ")});
  2523.   },
  2524.   signedOn: function(nickname) {
  2525.     this.tracker = new qwebirc.irc.IRCTracker(this);
  2526.     this.nickname = nickname;
  2527.     this.newServerLine("SIGNON");
  2528.  
  2529.     if(this.autojoin) {
  2530.       this.exec("/AUTOJOIN");
  2531.     }
  2532.   },
  2533.   userJoined: function(user, channel) {
  2534.     var nick = user.hostToNick();
  2535.     var host = user.hostToHost();
  2536.  
  2537.     if((nick == this.nickname) && !ui.getWindow(qwebirc.ui.WINDOW_CHANNEL, channel))
  2538.       this.newWindow(channel, qwebirc.ui.WINDOW_CHANNEL, true);
  2539.     this.tracker.addNickToChannel(nick, channel);
  2540.  
  2541.     if(nick == this.nickname) {
  2542.       this.newChanLine(channel, "OURJOIN", user);
  2543.     } else {
  2544.       if(!conf.ui.hide_joinparts) {
  2545.         this.newChanLine(channel, "JOIN", user);
  2546.       }
  2547.     }
  2548.     this.updateNickList(channel);
  2549.   },
  2550.   userPart: function(user, channel, message) {
  2551.     var nick = user.hostToNick();
  2552.     var host = user.hostToHost();
  2553.  
  2554.     if(nick == this.nickname) {
  2555.       this.tracker.removeChannel(channel);
  2556.       var w = ui.getWindow(qwebirc.ui.WINDOW_CHANNEL, channel);
  2557.       if(w)
  2558.         ui.closeWindow(w);
  2559.     } else {
  2560.       this.tracker.removeNickFromChannel(nick, channel);
  2561.       if(!conf.ui.hide_joinparts) {
  2562.         this.newChanLine(channel, "PART", user, {"m": message});
  2563.       }
  2564.       this.updateNickList(channel);
  2565.     }
  2566.   },
  2567.   userKicked: function(kicker, channel, kickee, message) {
  2568.     if(kickee == this.nickname) {
  2569.       this.tracker.removeChannel(channel);
  2570.       var w = ui.getWindow(qwebirc.ui.WINDOW_CHANNEL, channel);
  2571.       if(w)
  2572.         ui.closeWindow(w);
  2573.     } else {
  2574.       this.tracker.removeNickFromChannel(kickee, channel);
  2575.       this.updateNickList(channel);
  2576.     }
  2577.  
  2578.     this.newChanLine(channel, "KICK", kicker, {"v": kickee, "m": message});
  2579.   },
  2580.   channelMode: function(user, channel, modes, raw) {
  2581.     modes.each(function(mo) {
  2582.       var direction = mo[0];
  2583.       var mode = mo[1];
  2584.  
  2585.       var prefixindex = this.modeprefixes.indexOf(mode);
  2586.       if(prefixindex == -1)
  2587.         return;
  2588.  
  2589.       var nick = mo[2];
  2590.       var prefixchar = this.prefixes.charAt(prefixindex);
  2591.  
  2592.       var nc = this.tracker.getOrCreateNickOnChannel(nick, channel);
  2593.       if(direction == "-") {
  2594.         this.removePrefix(nc, prefixchar);
  2595.       } else {
  2596.         this.addPrefix(nc, prefixchar);
  2597.       }
  2598.     }, this);
  2599.  
  2600.     this.newChanLine(channel, "MODE", user, {"m": raw.join(" ")});
  2601.  
  2602.     this.updateNickList(channel);
  2603.   },
  2604.   userQuit: function(user, message) {
  2605.     var nick = user.hostToNick();
  2606.  
  2607.     var channels = this.tracker.getNick(nick);
  2608.  
  2609.     var clist = [];
  2610.     for(var c in channels) {
  2611.       clist.push(c);
  2612.       if(!conf.ui.hide_joinparts) {
  2613.         this.newChanLine(c, "QUIT", user, {"m": message});
  2614.       }
  2615.     }
  2616.  
  2617.     this.tracker.removeNick(nick);
  2618.  
  2619.     clist.each(function(cli) {
  2620.       this.updateNickList(cli);
  2621.     }, this);
  2622.   },
  2623.   nickChanged: function(user, newnick) {
  2624.     var oldnick = user.hostToNick();
  2625.  
  2626.     if(oldnick == this.nickname)
  2627.       this.nickname = newnick;
  2628.  
  2629.     this.tracker.renameNick(oldnick, newnick);
  2630.  
  2631.     var channels = this.tracker.getNick(newnick);
  2632.     var found = false;
  2633.  
  2634.     for(var c in channels) {
  2635.       var found = true;
  2636.  
  2637.       this.newChanLine(c, "NICK", user, {"w": newnick});
  2638.       /* TODO: rename queries */
  2639.       this.updateNickList(c);
  2640.     }
  2641.  
  2642.     /* this is quite horrible */
  2643.     if(!found)
  2644.       this.newServerLine("NICK", {"w": newnick, n: user.hostToNick(), h: user.hostToHost(), "-": this.nickname});
  2645.   },
  2646.   channelTopic: function(user, channel, topic) {
  2647.     this.newChanLine(channel, "TOPIC", user, {"m": topic});
  2648.     ui.getWindow(qwebirc.ui.WINDOW_CHANNEL, channel).updateTopic(topic);
  2649.   },
  2650.   initialTopic: function(channel, topic) {
  2651.     ui.getWindow(qwebirc.ui.WINDOW_CHANNEL, channel).updateTopic(topic);
  2652.   },
  2653.   channelCTCP: function(user, channel, type, args) {
  2654.     if(args == undefined)
  2655.       args = "";
  2656.  
  2657.     var nick = user.hostToNick();
  2658.     if(type == "ACTION") {
  2659.       this.tracker.updateLastSpoke(nick, channel, new Date().getTime());
  2660.       this.newChanLine(channel, "CHANACTION", user, {"m": args, "c": channel, "@": this.getNickStatus(channel, nick)});
  2661.       return;
  2662.     }
  2663.  
  2664.     this.newChanLine(channel, "CHANCTCP", user, {"x": type, "m": args, "c": channel, "@": this.getNickStatus(channel, nick)});
  2665.   },
  2666.   userCTCP: function(user, type, args) {
  2667.     var nick = user.hostToNick();
  2668.     var host = user.hostToHost();
  2669.     if(args == undefined)
  2670.       args = "";
  2671.  
  2672.     if(type == "ACTION") {
  2673.       this.newQueryWindow(nick, true);
  2674.       this.newQueryLine(nick, "PRIVACTION", {"m": args, "x": type, "h": host, "n": nick}, true);
  2675.       return;
  2676.     }
  2677.  
  2678.     this.newTargetOrActiveLine(nick, "PRIVCTCP", {"m": args, "x": type, "h": host, "n": nick, "-": this.nickname});
  2679.   },
  2680.   userCTCPReply: function(user, type, args) {
  2681.     var nick = user.hostToNick();
  2682.     var host = user.hostToHost();
  2683.     if(args == undefined)
  2684.       args = "";
  2685.  
  2686.     this.newTargetOrActiveLine(nick, "CTCPREPLY", {"m": args, "x": type, "h": host, "n": nick, "-": this.nickname});
  2687.   },
  2688.   getNickStatus: function(channel, nick) {
  2689.     var n = this.tracker.getNickOnChannel(nick, channel);
  2690.     if(!$defined(n))
  2691.       return "";
  2692.  
  2693.     if(n.prefixes.length == 0)
  2694.       return "";
  2695.  
  2696.     return n.prefixes.charAt(0);
  2697.   },
  2698.   channelPrivmsg: function(user, channel, message) {
  2699.     var nick = user.hostToNick();
  2700.  
  2701.     this.tracker.updateLastSpoke(nick, channel, new Date().getTime());
  2702.     this.newChanLine(channel, "CHANMSG", user, {"m": message, "@": this.getNickStatus(channel, nick)});
  2703.   },
  2704.   channelNotice: function(user, channel, message) {
  2705.     this.newChanLine(channel, "CHANNOTICE", user, {"m": message, "@": this.getNickStatus(channel, user.hostToNick())});
  2706.   },
  2707.   userPrivmsg: function(user, message) {
  2708.     var nick = user.hostToNick();
  2709.     var host = user.hostToHost();
  2710.     this.newQueryWindow(nick, true);
  2711.     this.pushLastNick(nick);
  2712.     this.newQueryLine(nick, "PRIVMSG", {"m": message, "h": host, "n": nick}, true);
  2713.   },
  2714.   serverNotice: function(user, message) {
  2715.     if(user == "") {
  2716.       this.newServerLine("SERVERNOTICE", {"m": message});
  2717.     } else {
  2718.       this.newServerLine("PRIVNOTICE", {"m": message, "n": user});
  2719.     }
  2720.   },
  2721.   userNotice: function(user, message) {
  2722.     var nick = user.hostToNick();
  2723.     var host = user.hostToHost();
  2724.  
  2725.     if(conf.ui.dedicated_notice_window) {
  2726.       this.newQueryWindow(nick, false);
  2727.       this.newQueryOrActiveLine(nick, "PRIVNOTICE", {"m": message, "h": host, "n": nick}, false);
  2728.     } else {
  2729.       this.newTargetOrActiveLine(nick, "PRIVNOTICE", {"m": message, "h": host, "n": nick});
  2730.     }
  2731.   },
  2732.   __joinInvited: function() {
  2733.     this.exec("/JOIN " + this.inviteChanList.join(","));
  2734.     this.inviteChanList = [];
  2735.     delete this.activeTimers["serviceInvite"];
  2736.   },
  2737.   userInvite: function(user, channel) {
  2738.     var nick = user.hostToNick();
  2739.     var host = user.hostToHost();
  2740.  
  2741.     this.newServerLine("INVITE", {"c": channel, "h": host, "n": nick});
  2742.   },
  2743.   userMode: function(modes) {
  2744.     this.newServerLine("UMODE", {"m": modes, "n": this.nickname});
  2745.   },
  2746.   channelNames: function(channel, names) {
  2747.     if(names.length == 0) {
  2748.       this.updateNickList(channel);
  2749.       return;
  2750.     }
  2751.  
  2752.     names.each(function(nick) {
  2753.       var prefixes = [];
  2754.       var splitnick = nick.split("");
  2755.  
  2756.       splitnick.every(function(c, i) {
  2757.         if(this.prefixes.indexOf(c) == -1) {
  2758.           nick = nick.substr(i);
  2759.           return false;
  2760.         }
  2761.  
  2762.         prefixes.push(c);
  2763.         return true;
  2764.       }, this);
  2765.  
  2766.       var nc = this.tracker.addNickToChannel(nick, channel);
  2767.       prefixes.each(function(p) {
  2768.         this.addPrefix(nc, p);
  2769.       }, this);
  2770.     }, this);
  2771.   },
  2772.   disconnected: function(message) {
  2773.     for(var x in session.windows) {
  2774.       var w = session.windows[x];
  2775.       if(w.type == qwebirc.ui.WINDOW_CHANNEL)
  2776.         ui.closeWindow(w);
  2777.     }
  2778.     this.tracker = undefined;
  2779.  
  2780.     qwebirc.connected = false;
  2781.     this.newServerLine("DISCONNECT", {"m": message});
  2782.   },
  2783.   nickOnChanHasPrefix: function(nick, channel, prefix) {
  2784.     var entry = this.tracker.getNickOnChannel(nick, channel);
  2785.     if(!$defined(entry))
  2786.       return false; /* shouldn't happen */
  2787.  
  2788.     return entry.prefixes.indexOf(prefix) != -1;
  2789.   },
  2790.   nickOnChanHasAtLeastPrefix: function(nick, channel, prefix, betterThan) {
  2791.     var entry = this.tracker.getNickOnChannel(nick, channel);
  2792.     if(!$defined(entry))
  2793.       return false; /* shouldn't happen */
  2794.  
  2795.     /* this array is sorted */
  2796.     var pos = this.prefixes.indexOf(prefix);
  2797.     if(pos == -1)
  2798.       return false;  /* shouldn't happen */
  2799.  
  2800.     /* If we're looking for prefixes better than the given prefix, don't
  2801.      * include it itself. Otherwise, do. */
  2802.     if (!betterThan)
  2803.       pos = pos + 1;
  2804.  
  2805.     var modehash = {};
  2806.     this.prefixes.slice(0, pos).split("").each(function(x) {
  2807.       modehash[x] = true;
  2808.     });
  2809.  
  2810.     var prefixes = entry.prefixes;
  2811.     for(var i=0;i<prefixes.length;i++)
  2812.       if(modehash[prefixes.charAt(i)])
  2813.         return true;
  2814.  
  2815.     return false;
  2816.   },
  2817.   supported: function(key, value) {
  2818.     if(key == "PREFIX") {
  2819.       var l = (value.length - 2) / 2;
  2820.  
  2821.       this.modeprefixes = value.substr(1, l);
  2822.       this.prefixes = value.substr(l + 2, l);
  2823.     }
  2824.  
  2825.     this.parent(key, value);
  2826.   },
  2827.   connected: function() {
  2828.     qwebirc.connected = true;
  2829.     this.newServerLine("CONNECT");
  2830.   },
  2831.   serverError: function(message) {
  2832.     this.newServerLine("ERROR", {"m": message});
  2833.   },
  2834.   quit: function(message) {
  2835.     this.send("QUIT :" + message, true);
  2836.     this.disconnect();
  2837.   },
  2838.   disconnect: function() {
  2839.     for(var k in this.activeTimers) {
  2840.       this.activeTimers[k].cancel();
  2841.     };
  2842.     this.activeTimers = {};
  2843.  
  2844.     this.parent();
  2845.   },
  2846.   awayMessage: function(nick, message) {
  2847.     this.newQueryLine(nick, "AWAY", {"n": nick, "m": message}, true);
  2848.   },
  2849.   whois: function(nick, type, data) {
  2850.     var ndata = {"n": nick};
  2851.     var mtype;
  2852.  
  2853.     var xsend = function() {
  2854.       this.newTargetOrActiveLine(nick, "WHOIS" + mtype, ndata);
  2855.     }.bind(this);
  2856.  
  2857.     if(type == "user") {
  2858.       mtype = "USER";
  2859.       ndata.h = data.ident + "@" + data.hostname;
  2860.       xsend();
  2861.       mtype = "REALNAME";
  2862.       ndata.m = data.realname;
  2863.     } else if(type == "server") {
  2864.       mtype = "SERVER";
  2865.       ndata.x = data.server;
  2866.       ndata.m = data.serverdesc;
  2867.     } else if(type == "oper") {
  2868.       mtype = "OPER";
  2869.     } else if(type == "idle") {
  2870.       mtype = "IDLE";
  2871.       ndata.x = qwebirc.util.longtoduration(data.idle);
  2872.       ndata.m = qwebirc.irc.IRCDate(new Date(data.connected * 1000));
  2873.     } else if(type == "channels") {
  2874.       mtype = "CHANNELS";
  2875.       ndata.m = data.channels;
  2876.     } else if(type == "account") {
  2877.       mtype = "ACCOUNT";
  2878.       ndata.m = data.account;
  2879.     } else if(type == "away") {
  2880.       mtype = "AWAY";
  2881.       ndata.m = data.away;
  2882.     } else if(type == "opername") {
  2883.       mtype = "OPERNAME";
  2884.       ndata.m = data.opername;
  2885.     } else if(type == "actually") {
  2886.       mtype = "ACTUALLY";
  2887.       ndata.m = data.hostname;
  2888.       ndata.x = data.ip;
  2889.     } else if(type == "availhelp") {
  2890.       mtype = "AVAILHELP";
  2891.     } else if(type == "regged") {
  2892.       mtype = "REGGED";
  2893.     } else if(type == "modes") {
  2894.       mtype = "MODES";
  2895.       ndata.m = data.modes;
  2896.     } else if(type == "realhost") {
  2897.       mtype = "REALHOST";
  2898.       ndata.m = data.hostname;
  2899.       ndata.x = data.ip;
  2900.     } else if(type == "generictext") {
  2901.       mtype = "GENERICTEXT";
  2902.       ndata.m = data.text;
  2903.     } else if(type == "end") {
  2904.       mtype = "END";
  2905.     } else {
  2906.       return false;
  2907.     }
  2908.  
  2909.     xsend();
  2910.     return true;
  2911.   },
  2912.   genericError: function(target, message) {
  2913.     this.newTargetOrActiveLine(target, "GENERICERROR", {m: message, t: target});
  2914.   },
  2915.   genericQueryError: function(target, message) {
  2916.     this.newQueryOrActiveLine(target, "GENERICERROR", {m: message, t: target}, true);
  2917.   },
  2918.   awayStatus: function(state, message) {
  2919.     this.newActiveLine("GENERICMESSAGE", {m: message});
  2920.   },
  2921.   pushLastNick: function(nick) {
  2922.     var i = this.lastNicks.indexOf(nick);
  2923.     if(i != -1) {
  2924.       this.lastNicks.splice(i, 1);
  2925.     } else {
  2926.       if(this.lastNicks.length == 10)
  2927.         this.lastNicks.pop();
  2928.     }
  2929.     this.lastNicks.unshift(nick);
  2930.   },
  2931.   wallops: function(user, text) {
  2932.     var nick = user.hostToNick();
  2933.     var host = user.hostToHost();
  2934.  
  2935.     this.newServerLine("WALLOPS", {t: text, n: nick, h: host});
  2936.   },
  2937.   channelModeIs: function(channel, modes) {
  2938.     this.newTargetOrActiveLine(channel, "CHANNELMODEIS", {c: channel, m: modes.join(" ")});
  2939.   },
  2940.   channelCreationTime: function(channel, time) {
  2941.     this.newTargetOrActiveLine(channel, "CHANNELCREATIONTIME", {c: channel, m: qwebirc.irc.IRCDate(new Date(time * 1000))});
  2942.   }
  2943. });
  2944.  
  2945. /* This could do with a rewrite from scratch. */
  2946.  
  2947. qwebirc.irc.IRCConnection = new Class({
  2948.   Implements: [Events, Options],
  2949.   session: null,
  2950.   options: {
  2951.     initialNickname: "ircconnX",
  2952.     timeout: 45000,
  2953.     floodInterval: 200,
  2954.     floodMax: 10,
  2955.     floodReset: 5000,
  2956.     errorAlert: true,
  2957.     maxRetries: 5,
  2958.     serverPassword: null,
  2959.     authUser: null,
  2960.     authSecret: null
  2961.   },
  2962.   initialize: function(session, options) {
  2963.     this.session = session;
  2964.     this.setOptions(options);
  2965.  
  2966.     this.initialNickname = this.options.initialNickname;
  2967.  
  2968.     this.counter = 0;
  2969.     this.disconnected = false;
  2970.  
  2971.     this.__floodLastRequest = 0;
  2972.     this.__floodCounter = 0;
  2973.     this.__floodLastFlood = 0;
  2974.  
  2975.     this.__retryAttempts = 0;
  2976.  
  2977.     this.__timeoutId = null;
  2978.     this.__lastActiveRequest = null;
  2979.     this.__activeRequest = null;
  2980.  
  2981.     this.__sendQueue = [];
  2982.     this.__sendQueueActive = false;
  2983.   },
  2984.   __error: function(text) {
  2985.     this.fireEvent("error", text);
  2986.     if(this.options.errorAlert)
  2987.       alert(text);
  2988.   },
  2989.   newRequest: function(url, floodProtection, synchronous) {
  2990.     if(this.disconnected)
  2991.       return null;
  2992.  
  2993.     if(floodProtection && !this.disconnected && this.__isFlooding()) {
  2994.       this.disconnect();
  2995.       this.__error("BUG: uncontrolled flood detected -- disconnected.");
  2996.     }
  2997.  
  2998.     var asynchronous = true;
  2999.     if(synchronous)
  3000.       asynchronous = false;
  3001.  
  3002.     var r = new Request.JSON({
  3003.       url: conf.frontend.dynamic_base_url + "e/" + url + "?r=" + this.cacheAvoidance + "&t=" + this.counter++,
  3004.       async: asynchronous
  3005.     });
  3006.  
  3007.     /* try to minimise the amount of headers */
  3008.     r.headers = new Hash;
  3009.     r.addEvent("request", function() {
  3010.       var setHeader = function(key, value) {
  3011.         try {
  3012.           this.setRequestHeader(key, value);
  3013.         } catch(e) {
  3014.         }
  3015.       }.bind(this);
  3016.  
  3017.       setHeader("User-Agent", null);
  3018.       setHeader("Accept", null);
  3019.       setHeader("Accept-Language", null);
  3020.     }.bind(r.xhr));
  3021.  
  3022.     if(Browser.Engine.trident)
  3023.       r.setHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
  3024.  
  3025.     return r;
  3026.   },
  3027.   __isFlooding: function() {
  3028.     var t = new Date().getTime();
  3029.  
  3030.     if(t - this.__floodLastRequest < this.options.floodInterval) {
  3031.       if(this.__floodLastFlood != 0 && (t - this.__floodLastFlood > this.options.floodReset))
  3032.         this.__floodCounter = 0;
  3033.  
  3034.       this.__floodLastFlood = t;
  3035.       if(this.__floodCounter++ >= this.options.floodMax)
  3036.         return true;
  3037.     }
  3038.  
  3039.     this.__floodLastRequest = t;
  3040.     return false;
  3041.   },
  3042.   send: function(data, synchronous) {
  3043.     if(this.disconnected)
  3044.       return false;
  3045.  
  3046.     if(synchronous) {
  3047.       this.__send(data, false);
  3048.     } else {
  3049.       this.__sendQueue.push(data);
  3050.       this.__processSendQueue();
  3051.     }
  3052.  
  3053.     return true;
  3054.   },
  3055.   __processSendQueue: function() {
  3056.     if(this.__sendQueueActive || this.__sendQueue.length == 0)
  3057.       return;
  3058.  
  3059.     this.sendQueueActive = true;
  3060.     this.__send(this.__sendQueue.shift(), true);
  3061.   },
  3062.   __send: function(data, queued) {
  3063.     var r = this.newRequest("p", false, !queued); /* !queued == synchronous */
  3064.     if(r === null)
  3065.       return;
  3066.  
  3067.     r.addEvent("complete", function(o) {
  3068.       if(queued)
  3069.         this.__sendQueueActive = false;
  3070.  
  3071.       if(!o || (o[0] == false)) {
  3072.         this.__sendQueue = [];
  3073.  
  3074.         if(!this.disconnected) {
  3075.           this.disconnected = true;
  3076.           this.__error("An error occured: " + o[1]);
  3077.         }
  3078.         return false;
  3079.       }
  3080.  
  3081.       this.__processSendQueue();
  3082.     }.bind(this));
  3083.  
  3084.     r.send("s=" + this.sessionid + "&c=" + encodeURIComponent(data));
  3085.   },
  3086.   __processData: function(o) {
  3087.     if(o[0] == false) {
  3088.       if(!this.disconnected) {
  3089.         this.disconnected = true;
  3090.         this.__error("An error occured: " + o[1]);
  3091.       }
  3092.       return false;
  3093.     }
  3094.  
  3095.     this.__retryAttempts = 0;
  3096.     o.each(function(x) {
  3097.       this.fireEvent("recv", [x]);
  3098.     }, this);
  3099.  
  3100.     return true;
  3101.   },
  3102.   __scheduleTimeout: function() {
  3103.     if(this.options.timeout)
  3104.       this.__timeoutId = this.__timeoutEvent.delay(this.options.timeout, this);
  3105.   },
  3106.   __cancelTimeout: function() {
  3107.     if($defined(this.__timeoutId)) {
  3108.       $clear(this.__timeoutId);
  3109.       this.__timeoutId = null;
  3110.     }
  3111.   },
  3112.   __timeoutEvent: function() {
  3113.     this.__timeoutId = null;
  3114.  
  3115.     if(!$defined(this.__activeRequest))
  3116.       return;
  3117.  
  3118.     if(this.__checkRetries()) {
  3119.       if(this.__lastActiveRequest)
  3120.         this.__lastActiveRequest.cancel();
  3121.  
  3122.       this.__activeRequest.__replaced = true;
  3123.       this.__lastActiveRequest = this.__activeRequest;
  3124.       this.recv();
  3125.     } else {
  3126.       this.__cancelRequests();
  3127.     }
  3128.   },
  3129.   __checkRetries: function() {
  3130.     /* hmm, something went wrong! */
  3131.     if(this.__retryAttempts++ >= this.options.maxRetries && !this.disconnected) {
  3132.       this.disconnect();
  3133.  
  3134.       this.__error("Error: connection closed after several requests failed.");
  3135.       return false;
  3136.     }
  3137.  
  3138.     return true;
  3139.   },
  3140.   recv: function() {
  3141.     var r = this.newRequest("s", true);
  3142.     if(!$defined(r))
  3143.       return;
  3144.  
  3145.     this.__activeRequest = r;
  3146.     r.__replaced = false;
  3147.  
  3148.     var onComplete = function(o) {
  3149.       /* if we're a replaced requests... */
  3150.       if(r.__replaced) {
  3151.         this.__lastActiveRequest = null;
  3152.  
  3153.         if(o)
  3154.           this.__processData(o);
  3155.         return;
  3156.       }
  3157.  
  3158.       /* ok, we're the main request */
  3159.       this.__activeRequest = null;
  3160.       this.__cancelTimeout();
  3161.  
  3162.       if(!o) {
  3163.         if(this.disconnected)
  3164.           return;
  3165.  
  3166.         if(this.__checkRetries())
  3167.           this.recv();
  3168.         return;
  3169.       }
  3170.  
  3171.       if(this.__processData(o))
  3172.         this.recv();
  3173.     };
  3174.  
  3175.     r.addEvent("complete", onComplete.bind(this));
  3176.  
  3177.     this.__scheduleTimeout();
  3178.     r.send("s=" + this.sessionid);
  3179.   },
  3180.   connect: function() {
  3181.     this.cacheAvoidance = qwebirc.util.randHexString(16);
  3182.  
  3183.     var r = this.newRequest("n");
  3184.     r.addEvent("complete", function(o) {
  3185.       if(!o) {
  3186.         this.disconnected = true;
  3187.         this.__error("Couldn't connect to remote server.");
  3188.         return;
  3189.       }
  3190.       if(o[0] == false) {
  3191.         this.disconnect();
  3192.         this.__error("An error occured: " + o[1]);
  3193.         return;
  3194.       }
  3195.       this.sessionid = o[1];
  3196.  
  3197.       this.recv();
  3198.     }.bind(this));
  3199.  
  3200.     var postdata = "nick=" + encodeURIComponent(this.initialNickname);
  3201.     if($defined(this.options.serverPassword))
  3202.       postdata+="&password=" + encodeURIComponent(this.options.serverPassword);
  3203.     if($defined(this.options.authUser) && $defined(this.options.authSecret)) {
  3204.       postdata+="&authUser=" + encodeURIComponent(this.options.authUser);
  3205.       postdata+="&authSecret=" + encodeURIComponent(this.options.authSecret);
  3206.     }
  3207.  
  3208.     r.send(postdata);
  3209.   },
  3210.   __cancelRequests: function() {
  3211.     if($defined(this.__lastActiveRequest)) {
  3212.       this.__lastActiveRequest.cancel();
  3213.       this.__lastActiveRequest = null;
  3214.     }
  3215.     if($defined(this.__activeRequest)) {
  3216.       this.__activeRequest.cancel();
  3217.       this.__activeRequest = null;
  3218.     }
  3219.   },
  3220.   disconnect: function() {
  3221.     this.disconnected = true;
  3222.     this.__cancelTimeout();
  3223.     this.__cancelRequests();
  3224.   }
  3225. });
  3226.  
  3227. qwebirc.irc.IRCLowerTable = [
  3228. /* x00-x07 */ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
  3229. /* x08-x0f */ '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
  3230. /* x10-x17 */ '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
  3231. /* x18-x1f */ '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
  3232. /* ' '-x27 */    ' ',    '!',    '"',    '#',    '$',    '%',    '&', '\x27',
  3233. /* '('-'/' */    '(',    ')',    '*',    '+',    ',',    '-',    '.',    '/',
  3234. /* '0'-'7' */    '0',    '1',    '2',    '3',    '4',    '5',    '6',    '7',
  3235. /* '8'-'?' */    '8',    '9',    ':',    ';',    '<',    '=',    '>',    '?',
  3236. /* '@'-'G' */    '@',    'a',    'b',    'c',    'd',    'e',    'f',    'g',
  3237. /* 'H'-'O' */    'h',    'i',    'j',    'k',    'l',    'm',    'n',    'o',
  3238. /* 'P'-'W' */    'p',    'q',    'r',    's',    't',    'u',    'v',    'w',
  3239. /* 'X'-'_' */    'x',    'y',    'z',    '{',    '|',    '}',    '~',    '_',
  3240. /* '`'-'g' */    '`',    'a',    'b',    'c',    'd',    'e',    'f',    'g',
  3241. /* 'h'-'o' */    'h',    'i',    'j',    'k',    'l',    'm',    'n',    'o',
  3242. /* 'p'-'w' */    'p',    'q',    'r',    's',    't',    'u',    'v',    'w',
  3243. /* 'x'-x7f */    'x',    'y',    'z',    '{',    '|',    '}',    '~', '\x7f',
  3244. /* x80-x87 */ '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87',
  3245. /* x88-x8f */ '\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f',
  3246. /* x90-x97 */ '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97',
  3247. /* x98-x9f */ '\x98', '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f',
  3248. /* xa0-xa7 */ '\xa0', '\xa1', '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7',
  3249. /* xa8-xaf */ '\xa8', '\xa9', '\xaa', '\xab', '\xac', '\xad', '\xae', '\xaf',
  3250. /* xb0-xb7 */ '\xb0', '\xb1', '\xb2', '\xb3', '\xb4', '\xb5', '\xb6', '\xb7',
  3251. /* xb8-xbf */ '\xb8', '\xb9', '\xba', '\xbb', '\xbc', '\xbd', '\xbe', '\xbf',
  3252. /* xc0-xc7 */ '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7',
  3253. /* xc8-xcf */ '\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef',
  3254. /* xd0-xd7 */ '\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xd7',
  3255. /* xd8-xdf */ '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xdf',
  3256. /* xe0-xe7 */ '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7',
  3257. /* xe8-xef */ '\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef',
  3258. /* xf0-xf7 */ '\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xf7',
  3259. /* xf8-xff */ '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff'
  3260. ];
  3261.  
  3262. qwebirc.irc.RFC1459toIRCLower = function(x) {
  3263.   var p = [];
  3264.   for(var i=0;i<x.length;i++) {
  3265.     var l = x.charCodeAt(i);
  3266.  
  3267.     p.push(qwebirc.irc.IRCLowerTable[l]);
  3268.   }
  3269.  
  3270.   return p.join("");
  3271. }
  3272.  
  3273. qwebirc.irc.ASCIItoIRCLower = function(x) {
  3274.   return x.toLowerCase(); /* TODO: does unicode too.... */
  3275. }
  3276.  
  3277. String.prototype.hostToNick = function() {
  3278.   return this.split("!", 1)[0];
  3279. }
  3280.  
  3281. String.prototype.hostToHost = function() {
  3282.   return this.split("!", 2)[1];
  3283. }
  3284.  
  3285. qwebirc.irc.IRCTimestamp = function(d) {
  3286.   return "[" + qwebirc.util.pad(d.getHours()) + ":" + qwebirc.util.pad(d.getMinutes()) + "]";
  3287. }
  3288.  
  3289. qwebirc.irc.IRCDate = function(d) {
  3290.   var pad = qwebirc.util.pad;
  3291.  
  3292.   return qwebirc.util.DaysOfWeek[d.getDay()] + " " + qwebirc.util.MonthsOfYear[d.getMonth()] + " " + pad(d.getDate()) + " "  + pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()) + " " + d.getFullYear();
  3293. }
  3294.  
  3295. qwebirc.irc.toIRCCompletion = function(session, data) {
  3296.   return session.irc.toIRCLower(data).replace(/[^\w]+/g, "");
  3297. }
  3298.  
  3299. qwebirc.irc.NickChanEntry = function() {
  3300.   this.prefixes = "";
  3301.   this.lastSpoke = 0;
  3302. }
  3303.  
  3304. qwebirc.irc.IRCTracker = new Class({
  3305.   initialize: function(owner) {
  3306.     this.channels = {};
  3307.     this.nicknames = {};
  3308.     this.owner = owner;
  3309.   },
  3310.   toIRCLower: function(value) {
  3311.     /* proxied because the method can change after we connect */
  3312.  
  3313.     return this.owner.toIRCLower(value);
  3314.   },
  3315.   isEmpty: function(hash) {
  3316.     for(var x in hash)
  3317.       return false;
  3318.     return true;
  3319.   },
  3320.   getNick: function(nick) {
  3321.     return this.nicknames[nick];
  3322.   },
  3323.   getOrCreateNick: function(nick) {
  3324.     var n = this.getNick(nick);
  3325.     if(!n)
  3326.       n = this.nicknames[nick] = {};
  3327.     return n;
  3328.   },
  3329.   getChannel: function(channel) {
  3330.     return this.channels[this.toIRCLower(channel)];
  3331.   },
  3332.   getOrCreateChannel: function(channel) {
  3333.     var c = this.getChannel(channel);
  3334.     if(!c)
  3335.       c = this.channels[this.toIRCLower(channel)] = {};
  3336.     return c;
  3337.   },
  3338.   getOrCreateNickOnChannel: function(nick, channel) {
  3339.     var n = this.getOrCreateNick(nick);
  3340.  
  3341.     var nc = n[this.toIRCLower(channel)];
  3342.     if(!nc)
  3343.       return this.addNickToChannel(nick, channel);
  3344.  
  3345.     return nc;
  3346.   },
  3347.   getNickOnChannel: function(nick, channel) {
  3348.     var n = this.getNick(nick);
  3349.     if(!n)
  3350.       return;
  3351.  
  3352.     return n[this.toIRCLower(channel)];
  3353.   },
  3354.   addNickToChannel: function(nick, channel) {
  3355.     var nc = new qwebirc.irc.NickChanEntry();
  3356.  
  3357.     var n = this.getOrCreateNick(nick);
  3358.     n[this.toIRCLower(channel)] = nc;
  3359.  
  3360.     var c = this.getOrCreateChannel(channel);
  3361.     c[nick] = nc;
  3362.  
  3363.     return nc;
  3364.   },
  3365.   removeNick: function(nick) {
  3366.     var n = this.getNick(nick);
  3367.     if(!n)
  3368.       return;
  3369.  
  3370.     for(var channel in n) {
  3371.       var lchannel = this.toIRCLower(channel);
  3372.       var c = this.channels[lchannel];
  3373.  
  3374.       delete c[nick];
  3375.       if(this.isEmpty(c))
  3376.         delete this.channels[lchannel];
  3377.     }
  3378.     delete this.nicknames[nick];
  3379.   },
  3380.   removeChannel: function(channel) {
  3381.     var c = this.getChannel(channel);
  3382.     if(!c)
  3383.       return;
  3384.  
  3385.     var lchannel = this.toIRCLower(channel);
  3386.  
  3387.     for(var nick in c) {
  3388.       var n = this.nicknames[nick];
  3389.  
  3390.       delete n[lchannel];
  3391.       if(this.isEmpty(n))
  3392.         delete this.nicknames[nick];
  3393.     }
  3394.     delete this.channels[lchannel];
  3395.   },
  3396.   removeNickFromChannel: function(nick, channel) {
  3397.     var lchannel = this.toIRCLower(channel);
  3398.  
  3399.     var n = this.getNick(nick);
  3400.     var c = this.getChannel(lchannel);
  3401.     if(!n || !c)
  3402.       return;
  3403.  
  3404.     delete n[lchannel];
  3405.     delete c[nick];
  3406.  
  3407.     if(this.isEmpty(n))
  3408.       delete this.nicknames[nick];
  3409.     if(this.isEmpty(c))
  3410.       delete this.channels[lchannel];
  3411.   },
  3412.   renameNick: function(oldnick, newnick) {
  3413.     var n = this.getNick(oldnick);
  3414.     if(!n)
  3415.       return;
  3416.  
  3417.     for(var channel in n) {
  3418.       var lchannel = this.toIRCLower(channel);
  3419.       this.channels[lchannel][newnick] = this.channels[lchannel][oldnick];
  3420.       delete this.channels[lchannel][oldnick];
  3421.     }
  3422.  
  3423.     this.nicknames[newnick] = this.nicknames[oldnick];
  3424.     delete this.nicknames[oldnick];
  3425.   },
  3426.   updateLastSpoke: function(nick, channel, time) {
  3427.     var nc = this.getNickOnChannel(nick, channel);
  3428.     if($defined(nc))
  3429.       nc.lastSpoke = time;
  3430.   },
  3431.   getSortedByLastSpoke: function(channel) {
  3432.     var sorter = function(a, b) {
  3433.       return b[1].lastSpoke - a[1].lastSpoke;
  3434.     };
  3435.  
  3436.     var c = this.getChannel(channel);
  3437.     if(!c)
  3438.       return;
  3439.  
  3440.     var n = [];
  3441.     for(var k in c)
  3442.       n.push([k, c[k]]);
  3443.  
  3444.     n.sort(sorter);
  3445.  
  3446.     var n2 = [];
  3447.     for(var i=0;i<n.length;i++)
  3448.       n2.push(n[i][0]);
  3449.  
  3450.     return n2;
  3451.   }
  3452. });
  3453.  
  3454.  
  3455. qwebirc.irc.Numerics = {
  3456.   "001": "RPL_WELCOME",
  3457.   "433": "ERR_NICKNAMEINUSE",
  3458.   "437": "ERR_UNAVAILRESOURCE",
  3459.   "004": "RPL_MYINFO",
  3460.   "005": "RPL_ISUPPORT",
  3461.   "353": "RPL_NAMREPLY",
  3462.   "366": "RPL_ENDOFNAMES",
  3463.   "331": "RPL_NOTOPIC",
  3464.   "332": "RPL_TOPIC",
  3465.   "333": "RPL_TOPICWHOTIME",
  3466.   "311": "RPL_WHOISUSER",
  3467.   "312": "RPL_WHOISSERVER",
  3468.   "313": "RPL_WHOISOPERATOR",
  3469.   "317": "RPL_WHOISIDLE",
  3470.   "671": "RPL_WHOISSECURE",
  3471.   "318": "RPL_ENDOFWHOIS",
  3472.   "319": "RPL_WHOISCHANNELS",
  3473.   "330": "RPL_WHOISACCOUNT",
  3474.   "338": "RPL_WHOISACTUALLY",
  3475.   "343": "RPL_WHOISOPERNAME",
  3476.   "320": "RPL_WHOISGENERICTEXT",
  3477.   "310": "RPL_WHOISAVAILHELP",
  3478.   "307": "RPL_WHOISREGGED",
  3479.   "378": "RPL_WHOISREALHOST",
  3480.   "379": "RPL_WHOISMODES",
  3481.   "325": "RPL_WHOISWEBIRC",
  3482.   "301": "RPL_AWAY",
  3483.   "401": "ERR_NOSUCHNICK",
  3484.   "404": "ERR_CANNOTSENDTOCHAN",
  3485.   "482": "ERR_CHANOPPRIVSNEEDED",
  3486.   "305": "RPL_UNAWAY",
  3487.   "306": "RPL_NOWAWAY",
  3488.   "324": "RPL_CHANNELMODEIS",
  3489.   "329": "RPL_CREATIONTIME"
  3490. };
  3491.  
  3492. /**
  3493.  * Provides handling of Atheme login state in the client interface.
  3494.  * Configures the client when the user changes between logged in and out, and
  3495.  * provides checks to determine whether which it is in.
  3496.  */
  3497.  
  3498. qwebirc.ui.Atheme = {};
  3499.  
  3500. /**
  3501.  * Handles an Atheme login.
  3502.  *
  3503.  * \param user The provided username.
  3504.  * \param token The user's given token.
  3505.  */
  3506. qwebirc.ui.Atheme.handleLogin = function(session, user, token) {
  3507.  
  3508.     /* Update state. */
  3509.     session.atheme.state = true;
  3510.     session.atheme.user = user;
  3511.     session.atheme.secret = token;
  3512.  
  3513.     /* Save cookie. */
  3514.     cookie = new Hash.Cookie("iris-auth");
  3515.     cookie.set("user", session.atheme.user);
  3516.     cookie.set("token", session.atheme.secret);
  3517.     cookie.save();
  3518. }
  3519.  
  3520. /**
  3521.  * Handle an Atheme logout.
  3522.  */
  3523. qwebirc.ui.Atheme.handleLogout = function() {
  3524.  
  3525.     /* Update state. */
  3526.     this.state = false;
  3527. }
  3528.  
  3529. /**
  3530.  * Check whether the user is currently logged in, and set the client up
  3531.  * accordingly.
  3532.  */
  3533. qwebirc.ui.Atheme.check = function(session) {
  3534.  
  3535.     /* If we have a user and token, check them for validity. Otherwise,
  3536.      * we're definitely logged out. */
  3537.     if ($defined(session.atheme.user) && $defined(session.atheme.secret) &&
  3538.             conf.atheme.sasl_type == "AUTHCOOKIE") {
  3539.         qwebirc.irc.AthemeQuery.checkLogin(function(valid) {
  3540.         if (valid == null)
  3541.             session.atheme.state = null;
  3542.         else if (valid)
  3543.             this.handleLogin(session, session.atheme.user, session.atheme.secret);
  3544.         else
  3545.             this.handleLogout();
  3546.         }.bind(this), session.atheme.user, session.atheme.secret);
  3547.     }
  3548.     else
  3549.         this.handleLogout(session);
  3550. }
  3551.  
  3552. qwebirc.ui.WINDOW_STATUS =   0x01;
  3553. qwebirc.ui.WINDOW_QUERY =    0x02;
  3554. qwebirc.ui.WINDOW_CHANNEL =  0x04;
  3555. qwebirc.ui.WINDOW_MESSAGES = 0x08;
  3556. qwebirc.ui.WINDOW_CUSTOM =   0x10;
  3557.  
  3558. qwebirc.ui.BaseUI = new Class({
  3559.   initialize: function(session, parentElement, windowClass, uiName) {
  3560.     this.session = session;
  3561.     this.parentElement = parentElement;
  3562.     this.windowClass = windowClass;
  3563.  
  3564.     this.windowArray = [];
  3565.     this.parentElement.addClass("qwebirc");
  3566.     this.parentElement.addClass("qwebirc-" + uiName);
  3567.     this.firstClient = false;
  3568.     this.commandhistory = new qwebirc.irc.CommandHistory();
  3569.  
  3570.     this.windowFocused = true;
  3571.  
  3572.     if(Browser.Engine.trident) {
  3573.       var checkFocus = function() {
  3574.         var hasFocus = document.hasFocus();
  3575.         if(hasFocus != this.windowFocused) {
  3576.           this.windowFocused = hasFocus;
  3577.           this.focusChange(hasFocus);
  3578.         }
  3579.       }
  3580.  
  3581.       checkFocus.periodical(100, this);
  3582.     } else {
  3583.       var blur = function() { if(this.windowFocused) { this.windowFocused = false; this.focusChange(false); } }.bind(this);
  3584.       var focus = function() { if(!this.windowFocused) { this.windowFocused = true; this.focusChange(true); } }.bind(this);
  3585.  
  3586.       /* firefox requires both */
  3587.  
  3588.       document.addEvent("blur", blur);
  3589.       window.addEvent("blur", blur);
  3590.       document.addEvent("focus", focus);
  3591.       window.addEvent("focus", focus);
  3592.     }
  3593.  
  3594.     this.postInitialize();
  3595.   },
  3596.   newClient: function() {
  3597.     var w = this.newWindow(qwebirc.ui.WINDOW_STATUS, "Status");
  3598.     this.selectWindow(w);
  3599.     if(!this.firstClient) {
  3600.       this.firstClient = true;
  3601.       w.addLine("", "qwebirc v" + qwebirc.VERSION);
  3602.       w.addLine("", "Copyright (C) 2008-2010 Chris Porter and the qwebirc project.");
  3603.       w.addLine("", "http://www.qwebirc.org");
  3604.       w.addLine("", "Licensed under the GNU General Public License, Version 2.");
  3605.     }
  3606.     return w;
  3607.   },
  3608.   getWindowIdentifier: function(type, name) {
  3609.     if(type == qwebirc.ui.WINDOW_MESSAGES)
  3610.       return "-M";
  3611.     if(type == qwebirc.ui.WINDOW_STATUS)
  3612.       return "";
  3613.     if(type == qwebirc.ui.WINDOW_CUSTOM)
  3614.       return "internal_" + qwebirc.irc.ASCIItoIRCLower(name);
  3615.  
  3616.     return "_" + this.session.irc.toIRCLower(name);
  3617.   },
  3618.   newWindow: function(type, name) {
  3619.     var w = this.getWindow(type, name);
  3620.     if($defined(w))
  3621.       return w;
  3622.  
  3623.     var wId = this.getWindowIdentifier(type, name);
  3624.     var w = this.session.windows[wId] = new this.windowClass(this.session, type, name, wId);
  3625.  
  3626.     this.windowArray.push(w);
  3627.  
  3628.     return w;
  3629.   },
  3630.   getWindow: function(type, name) {
  3631.     return this.session.windows[this.getWindowIdentifier(type, name)];
  3632.   },
  3633.   getActiveWindow: function() {
  3634.     return this.active;
  3635.   },
  3636.   getActiveIRCWindow: function() {
  3637.     if(!this.active || this.active.type == qwebirc.ui.WINDOW_CUSTOM) {
  3638.       return this.session.windows[this.getWindowIdentifier(qwebirc.ui.WINDOW_STATUS)];
  3639.     } else {
  3640.       return this.active;
  3641.     }
  3642.   },
  3643.   __setActiveWindow: function(window) {
  3644.     this.active = window;
  3645.   },
  3646.   selectWindow: function(window) {
  3647.     if(this.active)
  3648.       this.active.deselect();
  3649.     window.select();  /* calls setActiveWindow */
  3650.     this.updateTitle(window.name + " - " + conf.frontend.app_title);
  3651.   },
  3652.   updateTitle: function(text) {
  3653.     document.title = text;
  3654.   },
  3655.   nextWindow: function(direction) {
  3656.     if(this.windowArray.length == 0 || !this.active)
  3657.       return;
  3658.  
  3659.     if(!direction)
  3660.       direction = 1;
  3661.  
  3662.     var index = this.windowArray.indexOf(this.active);
  3663.     if(index == -1)
  3664.       return;
  3665.  
  3666.     index = index + direction;
  3667.     if(index < 0) {
  3668.       index = this.windowArray.length - 1;
  3669.     } else if(index >= this.windowArray.length) {
  3670.       index = 0;
  3671.     }
  3672.  
  3673.     this.selectWindow(this.windowArray[index]);
  3674.   },
  3675.   prevWindow: function() {
  3676.     this.nextWindow(-1);
  3677.   },
  3678.  
  3679.   /* Cleans up for an ALREADY REMOVED window. */
  3680.   closeWindow: function(window) {
  3681.  
  3682.     /* Window cleanup. */
  3683.     window.closed = true;
  3684.     if($defined(window.scrolltimer)) {
  3685.       $clear(window.scrolltimer);
  3686.       window.scrolltimer = null;
  3687.     }
  3688.  
  3689.     /* Transfer active window. */
  3690.     if(window.active) {
  3691.       this.active = undefined;
  3692.       if(this.windowArray.length == 1) {
  3693.         this.windowArray = [];
  3694.       } else {
  3695.         var index = this.windowArray.indexOf(window);
  3696.         if(index == -1) {
  3697.           return;
  3698.         } else if(index == 0) {
  3699.           this.selectWindow(this.windowArray[1]);
  3700.         } else {
  3701.           this.selectWindow(this.windowArray[index - 1]);
  3702.         }
  3703.       }
  3704.     }
  3705.  
  3706.     /* Delete records of the window. */
  3707.     delete window.session.windows[window.identifier];
  3708.     this.windowArray = this.windowArray.erase(window);
  3709.   },
  3710.  
  3711.   focusChange: function(newValue) {
  3712.     var window_ = this.getActiveWindow();
  3713.     if($defined(window_))
  3714.       window_.focusChange(newValue);
  3715.   }
  3716. });
  3717.  
  3718. qwebirc.ui.StandardUI = new Class({
  3719.   Extends: qwebirc.ui.BaseUI,
  3720.   initialize: function(session, parentElement, windowClass, uiName) {
  3721.     this.parent(session, parentElement, windowClass, uiName);
  3722.  
  3723.     this.tabCompleter = new qwebirc.ui.TabCompleterFactory(this);
  3724.  
  3725.     var ev;
  3726.     if(Browser.Engine.trident) {
  3727.       ev = "keydown";
  3728.     } else {
  3729.       ev = "keypress";
  3730.     }
  3731.     document.addEvent(ev, this.__handleHotkey.bind(this));
  3732.   },
  3733.   __handleHotkey: function(x) {
  3734.     if(!x.alt || x.control) {
  3735.       if(x.key == "backspace" || x.key == "/")
  3736.         if(!this.getInputFocused(x))
  3737.           new Event(x).stop();
  3738.       return;
  3739.     }
  3740.     var success = false;
  3741.     if(x.key == "a" || x.key == "A") {
  3742.       var highestNum = 0;
  3743.       var highestIndex = -1;
  3744.       success = true;
  3745.  
  3746.       new Event(x).stop();
  3747.       for(var i=0;i<this.windowArray.length;i++) {
  3748.         var h = this.windowArray[i].hilighted;
  3749.         if(h > highestNum) {
  3750.           highestIndex = i;
  3751.           highestNum = h;
  3752.         }
  3753.       }
  3754.       if(highestIndex > -1)
  3755.         this.selectWindow(this.windowArray[highestIndex]);
  3756.     } else if(x.key >= '0' && x.key <= '9') {
  3757.       success = true;
  3758.  
  3759.       number = x.key - '0';
  3760.       if(number == 0)
  3761.         number = 10
  3762.  
  3763.       number = number - 1;
  3764.  
  3765.       if(number >= this.windowArray.length)
  3766.         return;
  3767.  
  3768.       this.selectWindow(this.windowArray[number]);
  3769.     } else if(x.key == "left") {
  3770.       this.prevWindow();
  3771.       success = true;
  3772.     } else if(x.key == "right") {
  3773.       this.nextWindow();
  3774.       success = true;
  3775.     }
  3776.     if(success)
  3777.       new Event(x).stop();
  3778.   },
  3779.   getInputFocused: function(x) {
  3780.     if($$("input").indexOf(x.target) == -1 && $$("textarea").indexOf(x.target) == -1)
  3781.       return false;
  3782.     return true;
  3783.   },
  3784.   newCustomWindow: function(name, select, type) {
  3785.     if(!type)
  3786.       type = qwebirc.ui.WINDOW_CUSTOM;
  3787.  
  3788.     var w = this.newWindow(type, name);
  3789.  
  3790.     if(select)
  3791.       this.selectWindow(w);
  3792.  
  3793.     return w;
  3794.   },
  3795.   addPane: function(name) {
  3796.  
  3797.     var title = qwebirc.ui.Panes[name].title;
  3798.  
  3799.     var id = this.getWindowIdentifier(qwebirc.ui.WINDOW_CUSTOM, title)
  3800.     if(this.session.windows[id]) {
  3801.       this.selectWindow(this.session.windows[id]);
  3802.       return;
  3803.     }
  3804.  
  3805.     var d = this.newCustomWindow(title, true);
  3806.     d.lines.addClass("qwebirc-pane" + name);
  3807.  
  3808.     var ew = new qwebirc.ui.Panes[name].pclass(this.session, d);
  3809.  
  3810.     d.setSubWindow(ew);
  3811.     return d;
  3812.   },
  3813.   connectWindow: function(callbackfn) {
  3814.     var pane = qwebirc.ui.Panes.Connect;
  3815.     var w = this.addPane("Connect");
  3816.     w.subWindow.connectCallback = function(args) {
  3817.       ui.closeWindow(w);
  3818.       callbackfn(args);
  3819.     };
  3820.   },
  3821.   urlDispatcher: function(name) {
  3822.     if(name == "embedded")
  3823.       return ["a", this.embeddedWindow.bind(this)];
  3824.  
  3825.     if(name == "options")
  3826.       return ["a", this.optionsWindow.bind(this)];
  3827.  
  3828.     /* doesn't really belong here */
  3829.     if(name == "whois") {
  3830.       return ["span", function(nick) {
  3831.         if (conf.ui.nick_click_query)
  3832.           this.session.irc.exec("/QUERY " + nick);
  3833.         else
  3834.           this.session.irc.exec("/WHOIS " + nick);
  3835.       }.bind(this)];
  3836.     }
  3837.     if(name == "accinfo") {
  3838.       return ["span", function(account) {
  3839.         this.session.irc.exec("/MSG NickServ INFO " + account);
  3840.       }.bind(this)];
  3841.     }
  3842.  
  3843.     return null;
  3844.   },
  3845.   tabComplete: function(element) {
  3846.     this.tabCompleter.tabComplete(element);
  3847.   },
  3848.   resetTabComplete: function() {
  3849.     this.tabCompleter.reset();
  3850.   },
  3851.   setModifiableStylesheet: function(name) {
  3852.     this.__styleSheet = new qwebirc.ui.style.ModifiableStylesheet(conf.frontend.static_base_url + "css/" + name + qwebirc.FILE_SUFFIX + ".mcss");
  3853.  
  3854.     this.setModifiableStylesheetValues(conf.ui.fg_color, conf.ui.fg_sec_color, conf.ui.bg_color);
  3855.   },
  3856.   setModifiableStylesheetValues: function(fg_color, fg_sec_color, bg_color) {
  3857.  
  3858.     var fg = new Color(fg_color);
  3859.     var fg_sec = new Color(fg_sec_color);
  3860.     var bg = new Color(bg_color);
  3861.  
  3862.     var multiplier = 1;
  3863.     if (fg.hsb[2] > bg.hsb[2])
  3864.       multiplier = -1;
  3865.  
  3866.     if(!$defined(this.__styleSheet))
  3867.       return;
  3868.     this.__styleSheet.set(
  3869.         function(x) {
  3870.           return x.setHue(-180 + x.hsb[0] + fg.hsb[0]).setSaturation(fg.hsb[1] + (x.hsb[1]-50)*multiplier).setBrightness(fg.hsb[2] + (x.hsb[2]-50)*multiplier);
  3871.         },
  3872.         function(x) {
  3873.           return x.setHue(-180 + x.hsb[0] + fg_sec.hsb[0]).setSaturation(fg_sec.hsb[1] + (x.hsb[1]-50)*multiplier).setBrightness(fg_sec.hsb[2] + (x.hsb[2]-50)*multiplier);
  3874.         },
  3875.         function(x) {
  3876.           return x.setHue(-180 + x.hsb[0] + bg.hsb[0]).setSaturation(bg.hsb[1] + (x.hsb[1]-50)*multiplier).setBrightness(bg.hsb[2] + (x.hsb[2]-50)*multiplier);
  3877.         }
  3878.     );
  3879.   }
  3880. });
  3881.  
  3882. qwebirc.ui.NotificationUI = new Class({
  3883.   Extends: qwebirc.ui.StandardUI,
  3884.   initialize: function(session, parentElement, windowClass, uiName) {
  3885.     this.parent(session, parentElement, windowClass, uiName);
  3886.  
  3887.     this.__beeper = new qwebirc.ui.Beeper(session);
  3888.     this.__flasher = new qwebirc.ui.Flasher(session);
  3889.  
  3890.     this.beep = this.__beeper.beep.bind(this.__beeper);
  3891.  
  3892.     this.flash = this.__flasher.flash.bind(this.__flasher);
  3893.     this.cancelFlash = this.__flasher.cancelFlash.bind(this.__flasher);
  3894.   },
  3895.   setBeepOnMention: function(value) {
  3896.     if(value)
  3897.       this.__beeper.soundInit();
  3898.   },
  3899.   updateTitle: function(text) {
  3900.     if(this.__flasher.updateTitle(text))
  3901.       this.parent(text);
  3902.   },
  3903.   focusChange: function(value) {
  3904.     this.parent(value);
  3905.     this.__flasher.focusChange(value);
  3906.   }
  3907. });
  3908.  
  3909. qwebirc.ui.RootUI = qwebirc.ui.NotificationUI;
  3910.  
  3911. qwebirc.ui.RequestTransformHTML = function(session, options) {
  3912.   var HREF_ELEMENTS = {
  3913.     "IMG": 1
  3914.   };
  3915.  
  3916.   var update = options.update;
  3917.   var onSuccess = options.onSuccess;
  3918.  
  3919.   var fixUp = function(node) {
  3920.     if(node.nodeType != 1)
  3921.       return;
  3922.  
  3923.     var tagName = node.nodeName.toUpperCase();
  3924.     if(HREF_ELEMENTS[tagName]) {
  3925.       var attr = node.getAttribute("transform_attr");
  3926.       var value = node.getAttribute("transform_value");
  3927.       if($defined(attr) && $defined(value)) {
  3928.         node.removeAttribute("transform_attr");
  3929.         node.removeAttribute("transform_value");
  3930.         node.setAttribute(attr, conf.frontend.static_base_url + value);
  3931.       }
  3932.     }
  3933.  
  3934.     for(var i=0;i<node.childNodes.length;i++)
  3935.       fixUp(node.childNodes[i]);
  3936.   };
  3937.  
  3938.   delete options["update"];
  3939.   options.onSuccess = function(tree, elements, html, js) {
  3940.     var container = new Element("div");
  3941.     container.set("html", html);
  3942.     fixUp(container);
  3943.     update.empty();
  3944.  
  3945.     while(container.childNodes.length > 0) {
  3946.       var x = container.firstChild;
  3947.       container.removeChild(x);
  3948.       update.appendChild(x);
  3949.     }
  3950.     onSuccess();
  3951.   };
  3952.  
  3953.   return new Request.HTML(options);
  3954. };
  3955.  
  3956.  
  3957. qwebirc.ui.HILIGHT_NONE = 0;
  3958. qwebirc.ui.HILIGHT_ACTIVITY = 1;
  3959. qwebirc.ui.HILIGHT_SPEECH = 2;
  3960. qwebirc.ui.HILIGHT_US = 3;
  3961.  
  3962. qwebirc.ui.WINDOW_LASTLINE = qwebirc.ui.WINDOW_QUERY | qwebirc.ui.WINDOW_MESSAGES | qwebirc.ui.WINDOW_CHANNEL | qwebirc.ui.WINDOW_STATUS;
  3963.  
  3964. qwebirc.ui.Window = new Class({
  3965.   Implements: [Events],
  3966.   initialize: function(session, type, name, identifier) {
  3967.     this.session = session;
  3968.     this.type = type;
  3969.     this.name = name;
  3970.     this.active = false;
  3971.     this.identifier = identifier;
  3972.     this.hilighted = qwebirc.ui.HILIGHT_NONE;
  3973.     this.scrolltimer = null;
  3974.     this.commandhistory = ui.commandhistory;
  3975.     this.scrolleddown = true;
  3976.     this.scrollpos = null;
  3977.     this.lastNickHash = {};
  3978.     this.lastSelected = null;
  3979.     this.subWindow = null;
  3980.     this.closed = false;
  3981.  
  3982.     if(this.type & qwebirc.ui.WINDOW_LASTLINE) {
  3983.       this.lastPositionLine = new Element("hr");
  3984.       this.lastPositionLine.addClass("lastpos");
  3985.       this.lastPositionLineInserted = false;
  3986.     }
  3987.   },
  3988.   updateTopic: function(topic, element)  {
  3989.     qwebirc.ui.Colourise(this.session, "[" + topic + "]", element);
  3990.   },
  3991.   subEvent: function(event) {
  3992.     if($defined(this.subWindow))
  3993.       this.subWindow.fireEvent(event);
  3994.   },
  3995.   setSubWindow: function(window) {
  3996.     this.subWindow = window;
  3997.   },
  3998.   select: function() {
  3999.     if(this.lastPositionLineInserted && !conf.ui.lastpos_line) {
  4000.       this.lines.removeChild(this.lastPositionLine);
  4001.       this.lastPositionLineInserted = false;
  4002.     }
  4003.  
  4004.     this.active = true;
  4005.     ui.__setActiveWindow(this);
  4006.     if(this.hilighted)
  4007.       this.setHilighted(qwebirc.ui.HILIGHT_NONE);
  4008.  
  4009.     this.subEvent("select");
  4010.     this.resetScrollPos();
  4011.     this.lastSelected = new Date();
  4012.   },
  4013.   deselect: function() {
  4014.     this.subEvent("deselect");
  4015.  
  4016.     this.setScrollPos();
  4017.     if($defined(this.scrolltimer)) {
  4018.       $clear(this.scrolltimer);
  4019.       this.scrolltimer = null;
  4020.     }
  4021.  
  4022.     if(this.type & qwebirc.ui.WINDOW_LASTLINE)
  4023.       this.replaceLastPositionLine();
  4024.  
  4025.     this.active = false;
  4026.   },
  4027.   resetScrollPos: function() {
  4028.     if(this.scrolleddown) {
  4029.       this.scrollToBottom();
  4030.     } else if($defined(this.scrollpos)) {
  4031.       this.getScrollParent().scrollTo(this.scrollpos.x, this.scrollpos.y);
  4032.     }
  4033.   },
  4034.   setScrollPos: function() {
  4035.     if(!ui.singleWindow) {
  4036.       this.scrolleddown = this.scrolledDown();
  4037.       this.scrollpos = this.lines.getScroll();
  4038.     }
  4039.   },
  4040.   addLine: function(type, line, colour, element) {
  4041.     var hilight = qwebirc.ui.HILIGHT_NONE;
  4042.     var lhilight = false;
  4043.  
  4044.     if(type) {
  4045.       hilight = qwebirc.ui.HILIGHT_ACTIVITY;
  4046.  
  4047.       if(type.match(/(NOTICE|ACTION|MSG)$/)) {
  4048.         if(this.type == qwebirc.ui.WINDOW_QUERY || this.type == qwebirc.ui.WINDOW_MESSAGES) {
  4049.           if(type.match(/^OUR/) || type.match(/NOTICE$/)) {
  4050.             hilight = qwebirc.ui.HILIGHT_ACTIVITY;
  4051.           } else {
  4052.             hilight = qwebirc.ui.HILIGHT_US;
  4053.             ui.beep();
  4054.             ui.flash();
  4055.           }
  4056.         }
  4057.         if(!type.match(/^OUR/) && this.session.irc.hilightController.match(line["m"])) {
  4058.           lhilight = true;
  4059.           hilight = qwebirc.ui.HILIGHT_US;
  4060.           ui.beep();
  4061.           ui.flash();
  4062.         } else if(hilight != qwebirc.ui.HILIGHT_US) {
  4063.           hilight = qwebirc.ui.HILIGHT_SPEECH;
  4064.         }
  4065.       }
  4066.     }
  4067.  
  4068.     if(!this.active && (hilight != qwebirc.ui.HILIGHT_NONE))
  4069.       this.setHilighted(hilight);
  4070.  
  4071.     if(type)
  4072.       line = ui.theme.message(type, line, lhilight);
  4073.  
  4074.     qwebirc.ui.Colourise(this.session, qwebirc.irc.IRCTimestamp(new Date()) + " " + line, element);
  4075.     this.scrollAdd(element);
  4076.   },
  4077.   errorMessage: function(message) {
  4078.     this.addLine("", message, "warncolour");
  4079.   },
  4080.   infoMessage: function(message) {
  4081.     this.addLine("", message, "infocolour");
  4082.   },
  4083.   setHilighted: function(state) {
  4084.     if(state == qwebirc.ui.HILIGHT_NONE || state >= this.hilighted)
  4085.       this.hilighted = state;
  4086.   },
  4087.   scrolledDown: function() {
  4088.     if(this.scrolltimer)
  4089.       return true;
  4090.  
  4091.     var parent = this.lines;
  4092.  
  4093.     var prev = parent.getScroll();
  4094.     var prevbottom = parent.getScrollSize().y;
  4095.     var prevheight = parent.clientHeight;
  4096.  
  4097.     /*
  4098.      * fixes an IE bug: the scrollheight is less than the actual height
  4099.      * when the div isn't full
  4100.      */
  4101.     if(prevbottom < prevheight)
  4102.       prevbottom = prevheight;
  4103.  
  4104.     return prev.y + prevheight == prevbottom;
  4105.   },
  4106.   getScrollParent: function() {
  4107.     var scrollparent = this.lines;
  4108.  
  4109.     if($defined(this.scroller))
  4110.       scrollparent = this.scroller;
  4111.     return scrollparent;
  4112.   },
  4113.   scrollToBottom: function() {
  4114.     if(this.type == qwebirc.ui.WINDOW_CUSTOM)
  4115.       return;
  4116.  
  4117.     var parent = this.lines;
  4118.     var scrollparent = this.getScrollParent();
  4119.  
  4120.     scrollparent.scrollTo(parent.getScroll().x, parent.getScrollSize().y);
  4121.   },
  4122.   scrollAdd: function(element) {
  4123.     var parent = this.lines;
  4124.  
  4125.     /* scroll in bursts, else the browser gets really slow */
  4126.     if($defined(element)) {
  4127.       var sd = this.scrolledDown();
  4128.       parent.appendChild(element);
  4129.       if(sd) {
  4130.         if(this.scrolltimer)
  4131.           $clear(this.scrolltimer);
  4132.         this.scrolltimer = this.scrollAdd.delay(50, this, [null]);
  4133.       }
  4134.     } else {
  4135.       this.scrollToBottom();
  4136.       this.scrolltimer = null;
  4137.     }
  4138.   },
  4139.   updateNickList: function(nicks) {
  4140.     var nickHash = {}, present = {};
  4141.     var added = [];
  4142.     var lnh = this.lastNickHash;
  4143.  
  4144.     for(var i=0;i<nicks.length;i++)
  4145.       present[nicks[i]] = 1;
  4146.  
  4147.     for(var k in lnh)
  4148.       if(!present[k])
  4149.         this.nickListRemove(k, lnh[k]);
  4150.  
  4151.     for(var i=0;i<nicks.length;i++) {
  4152.       var n = nicks[i];
  4153.       var l = lnh[n];
  4154.       if(!l) {
  4155.         l = this.nickListAdd(n, i);
  4156.         if(!l)
  4157.           l = 1;
  4158.       }
  4159.       nickHash[n] = l;
  4160.     }
  4161.  
  4162.     this.lastNickHash = nickHash;
  4163.   },
  4164.   nickListAdd: function(nick, position) {
  4165.   },
  4166.   nickListRemove: function(nick, stored) {
  4167.   },
  4168.   historyExec: function(line) {
  4169.     this.commandhistory.addLine(line);
  4170.     this.session.irc.exec(line);
  4171.   },
  4172.   focusChange: function(newValue) {
  4173.     if(newValue == true || !(this.type & qwebirc.ui.WINDOW_LASTLINE))
  4174.       return;
  4175.  
  4176.     this.replaceLastPositionLine();
  4177.   },
  4178.   replaceLastPositionLine: function() {
  4179.     if(conf.ui.lastpos_line) {
  4180.       if(!this.lastPositionLineInserted) {
  4181.         this.scrollAdd(this.lastPositionLine);
  4182.       } else if(this.lines.lastChild != this.lastPositionLine) {
  4183.         try {
  4184.           this.lines.removeChild(this.lastPositionLine);
  4185.         } catch(e) {
  4186.           /* IGNORE, /clear removes lastPositionLine from the dom without resetting it. */
  4187.         }
  4188.         this.scrollAdd(this.lastPositionLine);
  4189.       }
  4190.     } else {
  4191.       if(this.lastPositionLineInserted)
  4192.         this.lines.removeChild(this.lastPositionLine);
  4193.     }
  4194.  
  4195.     this.lastPositionLineInserted = conf.ui.lastpos_line;
  4196.   }
  4197. });
  4198.  
  4199. qwebirc.ui.Colourise = function(session, line, entity) {
  4200.   var fg;
  4201.   var bg;
  4202.   var underline = false;
  4203.   var bold = false;
  4204.   var autoNickColour = false;
  4205.  
  4206.   var out = [];
  4207.   var xline = line.split("");
  4208.   var element = document.createElement("span");
  4209.  
  4210.   entity.addClass("colourline");
  4211.  
  4212.   function isNum(x) {
  4213.     return x >= '0' && x <= '9';
  4214.   }
  4215.  
  4216.   function parseColours(xline, i) {
  4217.     if(!isNum(xline[i + 1])) {
  4218.       fg = undefined;
  4219.       bg = undefined;
  4220.       return i;
  4221.     }
  4222.     i++;
  4223.     if(isNum(xline[i + 1])) {
  4224.       fg = parseInt(xline[i] + xline[i + 1]);
  4225.       i++;
  4226.     } else {
  4227.       fg = parseInt(xline[i]);
  4228.     }
  4229.     if(xline[i + 1] != ",")
  4230.       return i;
  4231.     if(!isNum(xline[i + 2]))
  4232.       return i;
  4233.     i+=2;
  4234.  
  4235.     if(isNum(xline[i + 1])) {
  4236.       bg = parseInt(xline[i] + xline[i + 1]);
  4237.       i++;
  4238.     } else {
  4239.       bg = parseInt(xline[i]);
  4240.     }
  4241.     return i;
  4242.   }
  4243.  
  4244.   function emitEndToken() {
  4245.     var data = "";
  4246.     if(out.length > 0) {
  4247.       var data = qwebirc.ui.urlificate(session, element, out.join(""));
  4248.       entity.appendChild(element);
  4249.       out = [];
  4250.     }
  4251.     element = document.createElement("span");
  4252.     return data;
  4253.   }
  4254.  
  4255.   function emitStartToken() {
  4256.     if(autoNickColour)
  4257.       return element;
  4258.  
  4259.     var classes = []
  4260.     if(fg != undefined)
  4261.       classes.push("Xc" + fg);
  4262.     if(bg != undefined)
  4263.       classes.push("Xbc" + bg);
  4264.     if(bold)
  4265.       classes.push("Xb");
  4266.     if(underline)
  4267.       classes.push("Xu");
  4268.     element.className = classes.join(" ");
  4269.   }
  4270.  
  4271.   var nickColouring = conf.ui.nick_colors;
  4272.   var capturingNick = false;
  4273.   for(var i=0;i<xline.length;i++) {
  4274.     var lc = xline[i];
  4275.  
  4276.     if(nickColouring) {
  4277.       if(!capturingNick) {
  4278.         if(lc == "\x00") {
  4279.           capturingNick = true;
  4280.           emitEndToken();
  4281.           continue;
  4282.         }
  4283.       } else {
  4284.         if(lc != "\x00") {
  4285.           out.push(lc);
  4286.         } else {
  4287.           autoNickColour = true;
  4288.           var e = emitStartToken();
  4289.           var text = emitEndToken();
  4290.  
  4291.           var c = text.toHSBColour(session);
  4292.           if($defined(c))
  4293.             e.style.color = c.rgbToHex();
  4294.           capturingNick = autoNickColour = false;
  4295.         }
  4296.         continue;
  4297.       }
  4298.     } else if(lc == "\x00") {
  4299.       continue;
  4300.     }
  4301.  
  4302.     if(lc == "\x02") {
  4303.       emitEndToken();
  4304.  
  4305.       bold = !bold;
  4306.  
  4307.       emitStartToken();
  4308.     } else if(lc == "\x1F") {
  4309.       emitEndToken();
  4310.  
  4311.       underline = !underline;
  4312.  
  4313.       emitStartToken();
  4314.     } else if(lc == "\x0F") {
  4315.       emitEndToken();
  4316.  
  4317.       fg = undefined;
  4318.       bg = undefined;
  4319.       underline = false;
  4320.       bold = false;
  4321.     } else if(lc == "\x03") {
  4322.       emitEndToken();
  4323.  
  4324.       i = parseColours(xline, i);
  4325.       if(bg > 15)
  4326.         bg = undefined;
  4327.       if(fg > 15)
  4328.         fg = undefined;
  4329.  
  4330.       emitStartToken();
  4331.     } else {
  4332.       out.push(lc);
  4333.     }
  4334.   }
  4335.  
  4336.   emitEndToken();
  4337. }
  4338.  
  4339. String.prototype.toHSBColour = function(session) {
  4340.   var lower = session.irc.toIRCLower(session.irc.stripPrefix(this));
  4341.   if(lower == session.irc.lowerNickname)
  4342.     return null;
  4343.  
  4344.   var hash = 0;
  4345.   for(var i=0;i<lower.length;i++)
  4346.     hash = 31 * hash + lower.charCodeAt(i);
  4347.  
  4348.   var hue = Math.abs(hash) % 360;
  4349.  
  4350.   return new Color([hue, 70, 60], "hsb");
  4351. }
  4352.  
  4353. /* Create the client UI. */
  4354. qwebirc.ui.create = function(element, uiclass) {
  4355.  
  4356.   /* Initialise our Atheme login and single session. */
  4357.   session = new qwebirc.session();
  4358.  
  4359.   /* Now wait until all the JS is loaded. */
  4360.   window.addEvent("domready", function() {
  4361.  
  4362.     /* Define login function. */
  4363.     var callback = function(connOptions) {
  4364.       session.irc = new qwebirc.irc.IRCClient(session, connOptions);
  4365.       session.irc.connect();
  4366.       window.onbeforeunload = qwebirc.ui.onbeforeunload;
  4367.       window.addEvent("unload", function() {
  4368.         session.irc.quit("Web client closed");
  4369.       });
  4370.     };
  4371.  
  4372.     /* Create UI. */
  4373.     ui = new uiclass(this.session, $(element));
  4374.  
  4375.     /* Create login window. */
  4376.     ui.connectWindow(callback);
  4377.  
  4378.     /* If enabled, open channel list. */
  4379.     if (conf.atheme.chan_list_on_start) {
  4380.       if (qwebirc.ui.Panes.List)
  4381.         ui.addPane("List");
  4382.     }
  4383.   });
  4384. };
  4385.  
  4386.  
  4387. /* Displays a warning if the user tries to close their browser. */
  4388. qwebirc.ui.onbeforeunload = function(e) { /* IE sucks */
  4389.   if (qwebirc.connected) {
  4390.     var message = "This action will close all active IRC connections.";
  4391.     var e = e || window.event;
  4392.     if(e)
  4393.       e.returnValue = message;
  4394.     return message;
  4395.   }
  4396. };
  4397.  
  4398. qwebirc.ui.MENU_ITEMS = function() {
  4399.   var maybeOpped = function(nick) {
  4400.     var channel = this.name; /* window name */
  4401.     var myNick = this.session.irc.nickname;
  4402.  
  4403.     return this.session.irc.nickOnChanHasAtLeastPrefix(myNick, channel, "+", true);
  4404.   };
  4405.  
  4406.   var isOpped = function(nick) {
  4407.     var channel = this.name; /* window name */
  4408.     var myNick = this.session.irc.nickname;
  4409.  
  4410.     return this.session.irc.nickOnChanHasAtLeastPrefix(myNick, channel, "@", false);
  4411.   };
  4412.  
  4413.   var isVoiced = function(nick) {
  4414.     var channel = this.name;
  4415.     var myNick = this.session.irc.nickname;
  4416.  
  4417.     return this.session.irc.nickOnChanHasPrefix(myNick, channel, "+");
  4418.   };
  4419.  
  4420.   var targetOpped = function(nick) {
  4421.     var channel = this.name;
  4422.     return this.session.irc.nickOnChanHasPrefix(nick, channel, "@");
  4423.   };
  4424.  
  4425.   var targetVoiced = function(nick) {
  4426.     var channel = this.name;
  4427.     return this.session.irc.nickOnChanHasPrefix(nick, channel, "+");
  4428.   };
  4429.  
  4430.   var invert = qwebirc.util.invertFn, compose = qwebirc.util.composeAnd;
  4431.  
  4432.   var command = function(cmd) {
  4433.     return function(nick) { this.session.irc.exec("/" + cmd + " " + nick); };
  4434.   };
  4435.  
  4436.   return [
  4437.     {
  4438.       text: "PM",
  4439.       fn: command("query"),
  4440.       predicate: true
  4441.     },
  4442.     {
  4443.       text: "whois",
  4444.       fn: command("whois"),
  4445.       predicate: true
  4446.     },
  4447.     {
  4448.       text: "kick", /* TODO: disappear when we're deopped */
  4449.       fn: function(nick) { this.session.irc.exec("/KICK " + nick + " wibble"); },
  4450.       predicate: maybeOpped
  4451.     },
  4452.     {
  4453.       text: "op",
  4454.       fn: command("op"),
  4455.       predicate: compose(isOpped, invert(targetOpped))
  4456.     },
  4457.     {
  4458.       text: "deop",
  4459.       fn: command("deop"),
  4460.       predicate: compose(isOpped, targetOpped)
  4461.     },
  4462.     {
  4463.       text: "voice",
  4464.       fn: command("voice"),
  4465.       predicate: compose(maybeOpped, invert(targetVoiced))
  4466.     },
  4467.     {
  4468.       text: "devoice",
  4469.       fn: command("devoice"),
  4470.       predicate: compose(maybeOpped, targetVoiced)
  4471.     }
  4472.   ];
  4473. };
  4474.  
  4475. qwebirc.ui.HilightController = new Class({
  4476.   initialize: function(session) {
  4477.     this.session = session;
  4478.     this.regex = null;
  4479.     this.prevnick = null;
  4480.   },
  4481.   match: function(text) {
  4482.     var nick = this.session.irc.nickname;
  4483.     if(nick != this.prevnick) {
  4484.       var classes = '[\\s\\.,;:]';
  4485.       this.regex = new RegExp('(^|' + classes + ')' + RegExp.escape(nick) + '(' + classes + '|$)', "i");
  4486.     }
  4487.     if(text.match(this.regex))
  4488.       return true;
  4489.     return false;
  4490.   }
  4491. });
  4492.  
  4493. qwebirc.ui.Beeper = new Class({
  4494.   initialize: function(session) {
  4495.     this.session = session;
  4496.  
  4497.     this.soundInited = false;
  4498.     this.soundReady = false;
  4499.  
  4500.     if(conf.ui.beep_on_mention)
  4501.       this.soundInit();
  4502.   },
  4503.   soundInit: function() {
  4504.     if(this.soundInited)
  4505.       return;
  4506.     if(!$defined(Browser.Plugins.Flash) || Browser.Plugins.Flash.version < 8)
  4507.       return;
  4508.     this.soundInited = true;
  4509.  
  4510.     this.soundPlayer = new qwebirc.sound.SoundPlayer(this.session);
  4511.     this.soundPlayer.addEvent("ready", function() {
  4512.       this.soundReady = true;
  4513.     }.bind(this));
  4514.  
  4515.     this.soundPlayer.go();
  4516.   },
  4517.   beep: function() {
  4518.     if(!this.soundReady || !conf.ui.beep_on_mention)
  4519.       return;
  4520.  
  4521.     this.soundPlayer.beep();
  4522.   }
  4523. });
  4524.  
  4525. qwebirc.ui.Flasher = new Class({
  4526.   initialize: function(session) {
  4527.     this.session = session;
  4528.  
  4529.     this.windowFocused = false;
  4530.     this.canUpdateTitle = true;
  4531.     this.titleText = document.title;
  4532.  
  4533.     var favIcon = this._getFavIcon();
  4534.     if($defined(favIcon)) {
  4535.       this.favIcon = favIcon;
  4536.       this.favIconParent = favIcon.parentNode;
  4537.       this.favIconVisible = true;
  4538.       this.emptyFavIcon = new Element("link");
  4539.       this.emptyFavIcon.rel = "shortcut icon";
  4540.       this.emptyFavIcon.href = conf.frontend.static_base_url + "images/empty_favicon.ico";
  4541.       this.emptyFavIcon.type = "image/x-icon";
  4542.       this.flashing = false;
  4543.  
  4544.       this.canFlash = true;
  4545.       document.addEvent("mousedown", this.cancelFlash.bind(this));
  4546.       document.addEvent("keydown", this.cancelFlash.bind(this));
  4547.     } else {
  4548.       this.canFlash = false;
  4549.     }
  4550.   },
  4551.   _getFavIcon: function() {
  4552.     var favIcons = $$("head link");
  4553.     for(var i=0;i<favIcons.length;i++)
  4554.       if(favIcons[i].getAttribute("rel") == "shortcut icon")
  4555.         return favIcons[i];
  4556.   },
  4557.   flash: function() {
  4558.     if(!conf.ui.flash_on_mention || this.windowFocused || !this.canFlash || this.flashing)
  4559.       return;
  4560.  
  4561.     this.titleText = document.title; /* just in case */
  4562.     var flashA = function() {
  4563.       this.hideFavIcon();
  4564.       this.canUpdateTitle = false;
  4565.       document.title = "Activity!";
  4566.  
  4567.       this.flasher = flashB.delay(500);
  4568.     }.bind(this);
  4569.  
  4570.     var flashB = function() {
  4571.       this.showFavIcon();
  4572.       this.canUpdateTitle = true;
  4573.       document.title = this.titleText;
  4574.  
  4575.       this.flasher = flashA.delay(500);
  4576.     }.bind(this);
  4577.  
  4578.     this.flashing = true;
  4579.     flashA();
  4580.   },
  4581.   cancelFlash: function() {
  4582.     if(!this.canFlash || !$defined(this.flasher))
  4583.       return;
  4584.  
  4585.     this.flashing = false;
  4586.  
  4587.     $clear(this.flasher);
  4588.     this.flasher = null;
  4589.  
  4590.     this.showFavIcon();
  4591.     document.title = this.titleText;
  4592.     this.canUpdateTitle = true;
  4593.   },
  4594.   hideFavIcon: function() {
  4595.     if(this.favIconVisible) {
  4596.       /* only seems to work in firefox */
  4597.       this.favIconVisible = false;
  4598.       this.favIconParent.removeChild(this.favIcon);
  4599.       this.favIconParent.appendChild(this.emptyFavIcon);
  4600.     }
  4601.   },
  4602.   showFavIcon: function() {
  4603.     if(!this.favIconVisible) {
  4604.       this.favIconVisible = true;
  4605.       this.favIconParent.removeChild(this.emptyFavIcon);
  4606.       this.favIconParent.appendChild(this.favIcon);
  4607.     }
  4608.   },
  4609.   updateTitle: function(text) {
  4610.     this.titleText = text;
  4611.     return this.canUpdateTitle;
  4612.   },
  4613.   focusChange: function(value) {
  4614.     this.windowFocused = value;
  4615.  
  4616.     if(value)
  4617.       this.cancelFlash();
  4618.   }
  4619. });
  4620.  
  4621. qwebirc.ui.supportsFocus = function(session) {
  4622.   var ua = navigator.userAgent;
  4623.   if(!$defined(ua))
  4624.     return true;
  4625.  
  4626.   if(Browser.Engine.ipod || ua.indexOf("Konqueror") != -1)
  4627.     return false;
  4628.  
  4629.   return true;
  4630. }
  4631.  
  4632. qwebirc.options.Input = new Class({
  4633.   initialize: function(session, options, parent, option, position) {
  4634.     this.session = session;
  4635.     this.options = options;
  4636.     this.option = option;
  4637.     this.value = conf[option.category][option.option];
  4638.     this.enabled = true;
  4639.     this.position = position;
  4640.     this.parentElement = parent;
  4641.     this.id = qwebirc.util.generateID();
  4642.  
  4643.     if ($defined(this.option.isEnabled))
  4644.       this.enabled = this.option.isEnabled(session);
  4645.  
  4646.     this.render();
  4647.   },
  4648.   createInput: function(type, parent, name, selected) {
  4649.     if(!$defined(parent))
  4650.       parent = this.parentElement;
  4651.  
  4652.     return qwebirc.util.createInput(type, parent, name, selected, this.id);
  4653.   },
  4654.   FE: function(element, parent) {
  4655.     var n = new Element(element);
  4656.     if(!$defined(parent))
  4657.       parent = this.parentElement;
  4658.  
  4659.     parent.appendChild(n);
  4660.     return n;
  4661.   },
  4662.   focus: function() {
  4663.     this.mainElement.focus();
  4664.   },
  4665.   onChange: function() {
  4666.     if ($defined(this.option.onChange))
  4667.       this.option.onChange(this.session, this.options, this.get());
  4668.   }
  4669. });
  4670.  
  4671. qwebirc.options.TextInput = new Class({
  4672.   Extends: qwebirc.options.Input,
  4673.   render: function() {
  4674.     var i = this.createInput("text");
  4675.     i.addEvent("change", function(value) {
  4676.       this.value = value;
  4677.       this.onChange();
  4678.     }.bind(this));
  4679.     this.mainElement = i;
  4680.  
  4681.     i.value = this.value;
  4682.     i.disabled = !this.enabled;
  4683.   },
  4684.   get: function() {
  4685.     return this.mainElement.value;
  4686.   }
  4687. });
  4688.  
  4689. qwebirc.options.ColorInput = new Class({
  4690.   Extends: qwebirc.options.Input,
  4691.   render: function() {
  4692.     this.hexbox = null;
  4693.  
  4694.     if (this.value[0] != "#")
  4695.       this.value = "#" + this.value;
  4696.  
  4697.     var hue = new Element("div");
  4698.     hue.addClass("qwebirc-optionspane");
  4699.     hue.addClass("hue-slider");
  4700.     this.parentElement.appendChild(hue);
  4701.  
  4702.     var sat = new Element("div");
  4703.     sat.addClass("qwebirc-optionspane");
  4704.     sat.addClass("sat-slider");
  4705.  
  4706.     var light = new Element("div");
  4707.     light.addClass("qwebirc-optionspane");
  4708.     light.addClass("light-slider");
  4709.  
  4710.     var hexform = new Element("form", {"class": "hexform"});
  4711.     this.hexbox = new Element("input", {value: this.value});
  4712.     hexform.appendChild(this.hexbox);
  4713.  
  4714.     var reset = new Element("input", {type: "button", value: "Reset to Default"});
  4715.  
  4716.     if (!conf.ui.simple_color) {
  4717.       this.parentElement.appendChild(sat);
  4718.       this.parentElement.appendChild(light);
  4719.       this.parentElement.appendChild(hexform);
  4720.       this.parentElement.appendChild(reset);
  4721.     }
  4722.  
  4723.     var color = new Color(this.value);
  4724.  
  4725.     var k = new Element("div");
  4726.     k.addClass("knob");
  4727.     if(Browser.Engine.trident) {
  4728.       k.setStyle("top", "0px");
  4729.       k.setStyle("background-color", "black");
  4730.     }
  4731.     hue.appendChild(k);
  4732.     var hue_slider = new Slider(hue, k, {steps: 36, range: [0, 369], wheel: true});
  4733.     hue_slider.set(color.hsb[0]);
  4734.  
  4735.     k = new Element("div");
  4736.     k.addClass("knob");
  4737.     if(Browser.Engine.trident) {
  4738.       k.setStyle("top", "0px");
  4739.       k.setStyle("background-color", "black");
  4740.     }
  4741.     sat.appendChild(k);
  4742.     var sat_slider = new Slider(sat, k, {steps: 50, range: [0, 100], wheel: true});
  4743.     sat_slider.set(color.hsb[1]);
  4744.  
  4745.     k = new Element("div");
  4746.     k.addClass("knob");
  4747.     if(Browser.Engine.trident) {
  4748.       k.setStyle("top", "0px");
  4749.       k.setStyle("background-color", "black");
  4750.     }
  4751.     light.appendChild(k);
  4752.     var light_slider = new Slider(light, k, {steps: 50, range: [0, 100], wheel: true});
  4753.     light_slider.set(color.hsb[2]);
  4754.  
  4755.     var change_func = function(step) {
  4756.       var newcolor = $HSB(hue_slider.step, sat_slider.step, light_slider.step);
  4757.       this.value = newcolor.rgb.rgbToHex();
  4758.       this.onChange();
  4759.     }.bind(this);
  4760.  
  4761.     if (this.enabled) {
  4762.       hue_slider.addEvent("change", change_func);
  4763.       sat_slider.addEvent("change", change_func);
  4764.       light_slider.addEvent("change", change_func);
  4765.  
  4766.       hexform.addEvent("submit", function(e) {
  4767.         (new Event(e)).stop();
  4768.         var color = new Color(this.hexbox.value)
  4769.        hue_slider.set(color.hsb[0]);
  4770.         sat_slider.set(color.hsb[1]);
  4771.         light_slider.set(color.hsb[2]);
  4772.       }.bind(this));
  4773.  
  4774.       reset.addEvent("click", function(e) {
  4775.         (new Event(e)).stop();
  4776.         this.value = conf[this.option.category][this.option.option + "_default"];
  4777.        var color = new Color(this.value)
  4778.        hue_slider.set(color.hsb[0]);
  4779.        sat_slider.set(color.hsb[1]);
  4780.         light_slider.set(color.hsb[2]);
  4781.       }.bind(this));
  4782.     }
  4783.  
  4784.     this.mainElement = hue;
  4785.     this.startValue = this.value;
  4786.   },
  4787.   onChange: function() {
  4788.     this.hexbox.value = this.get();
  4789.     if ($defined(this.option.onChange))
  4790.       this.option.onChange(this.session, this.options, this.get());
  4791.   },
  4792.   get: function() {
  4793.     return this.value;
  4794.   },
  4795.   cancel: function() {
  4796.     this.value = this.startValue;
  4797.     this.get();
  4798.   }
  4799. });
  4800.  
  4801. qwebirc.options.CheckInput = new Class({
  4802.   Extends: qwebirc.options.Input,
  4803.   render: function() {
  4804.     var i = this.createInput("checkbox");
  4805.     i.addEvent("change", function(value) {
  4806.       this.value = value;
  4807.       this.onChange();
  4808.     }.bind(this));
  4809.     this.mainElement = i;
  4810.  
  4811.     i.checked = this.value;
  4812.     i.disabled = !this.enabled;
  4813.   },
  4814.   get: function() {
  4815.     return this.mainElement.checked;
  4816.   }
  4817. });
  4818.  
  4819. qwebirc.options.RadioInput = new Class({
  4820.   Extends: qwebirc.options.Input,
  4821.   render: function() {
  4822.     var value = this.option.options;
  4823.  
  4824.     this.elements = [];
  4825.  
  4826.     for(var i=0;i<value.length;i++) {
  4827.       var d = this.FE("div");
  4828.       var e = this.createInput("radio", d, "options_radio" + this.position, i == this.option.position);
  4829.       this.elements.push(e);
  4830.       e.disabled = !this.enabled;
  4831.  
  4832.       var ePosition = i;
  4833.       e.addEvent("change", function(value) {
  4834.         if (value) {
  4835.           this.option.position = ePosition;
  4836.           this.onChange();
  4837.         }
  4838.       }.bind(this));
  4839.  
  4840.       if(i == 0)
  4841.         this.mainElement = e;
  4842.  
  4843.       d.appendChild(document.createTextNode(value[i][0]));
  4844.     };
  4845.   },
  4846.   get: function() {
  4847.     for(var i=0;i<this.elements.length;i++) {
  4848.       var x = this.elements[i];
  4849.       if(x.checked) {
  4850.         this.option.position = i;
  4851.         return this.option.options[i][1];
  4852.       }
  4853.     }
  4854.   }
  4855. });
  4856.  
  4857. qwebirc.options.Options = [
  4858.   {
  4859.     category: "ui",
  4860.     option: "beep_on_mention",
  4861.     label: "Beep when nick mentioned or on query activity (requires Flash)",
  4862.     isEnabled: function (session) {
  4863.       if(!$defined(Browser.Plugins.Flash) || Browser.Plugins.Flash.version < 8)
  4864.        return false;
  4865.       return true;
  4866.     },
  4867.     onSave: function(session) {
  4868.       if (ui.updateBeepOnMention)
  4869.         ui.updateBeepOnMention();
  4870.     }
  4871.   },
  4872.   {
  4873.     category: "ui",
  4874.     option: "dedicated_msg_window",
  4875.     label: "Send privmsgs to dedicated messages window"
  4876.   },
  4877.   {
  4878.     category: "ui",
  4879.     option: "dedicated_notice_window",
  4880.     label: "Send notices to dedicated message window"
  4881.   },
  4882.   {
  4883.     category: "ui",
  4884.     option: "flash_on_mention",
  4885.     label: "Flash titlebar when nick mentioned or on query activity",
  4886.     isEnabled: qwebirc.ui.supportsFocus
  4887.   },
  4888.   {
  4889.     category: "ui",
  4890.     option: "lastpos_line",
  4891.     label: "Show a last position indicator for each window",
  4892.     isEnabled: qwebirc.ui.supportsFocus
  4893.   },
  4894.   {
  4895.     category: "ui",
  4896.     option: "nick_colors",
  4897.     label: "Automatically colour nicknames"
  4898.   },
  4899.   {
  4900.     category: "ui",
  4901.     option: "nick_status",
  4902.     label: "Show status symbol before nicknames in channel lines"
  4903.   },
  4904.   {
  4905.     category: "ui",
  4906.     option: "nick_click_query",
  4907.     label: "Open a PM window on clicking a nickname in channel"
  4908.   },
  4909.   {
  4910.     category: "ui",
  4911.     option: "hide_joinparts",
  4912.     label: "Hide JOINS/PARTS/QUITS"
  4913.   },
  4914.   {
  4915.     category: "ui",
  4916.     option: "fg_color",
  4917.     type: qwebirc.options.ColorInput,
  4918.     label: "Adjust main foreground color",
  4919.     onChange: function (session, options, value) {
  4920.       ui.setModifiableStylesheetValues(value, options["ui.fg_sec_color"].get(), options["ui.bg_color"].get());
  4921.     },
  4922.     isEnabled: function (session) {
  4923.       return !conf.ui.simple_color;
  4924.     }
  4925.   },
  4926.   {
  4927.     category: "ui",
  4928.     option: "fg_sec_color",
  4929.     type: qwebirc.options.ColorInput,
  4930.     label: "Adjust title/link foreground color",
  4931.     onChange: function (session, options, value) {
  4932.       ui.setModifiableStylesheetValues(options["ui.fg_color"].get(), value, options["ui.bg_color"].get());
  4933.     },
  4934.     isEnabled: function (session) {
  4935.       return !conf.ui.simple_color;
  4936.     }
  4937.   },
  4938.   {
  4939.     category: "ui",
  4940.     option: "bg_color",
  4941.     type: qwebirc.options.ColorInput,
  4942.     label: "Adjust background color",
  4943.     onChange: function (session, options, value) {
  4944.       if ($defined(options["ui.fg_color"]))
  4945.         ui.setModifiableStylesheetValues(options["ui.fg_color"].get(), options["ui.fg_sec_color"].get(), value);
  4946.       else
  4947.         ui.setModifiableStylesheetValues(conf.ui.fg_color, conf.ui.fg_sec_color, value);
  4948.     },
  4949.     onSave: function (session) {
  4950.       ui.setModifiableStylesheetValues(conf.ui.fg_color, conf.ui.fg_sec_color, conf.ui.bg_color);
  4951.     },
  4952.     onCancel: function (session) {
  4953.       ui.setModifiableStylesheetValues(conf.ui.fg_color, conf.ui.fg_sec_color, conf.ui.bg_color);
  4954.     }
  4955.   }
  4956. ];
  4957.  
  4958. // Panes add themselves to this.
  4959. qwebirc.ui.Panes = {}
  4960.  
  4961. qwebirc.ui.style.ModifiableStylesheet = new Class({
  4962.   initialize: function(url) {
  4963.     var n = this.__parseStylesheet(this.__getStylesheet(url));
  4964.  
  4965.     this.__cssText = n.cssText;
  4966.     this.rules = n.rules;
  4967.  
  4968.     this.__tag = this.__createTag();
  4969.   },
  4970.   __createTag: function() {
  4971.     var tag = document.createElement("style");
  4972.     tag.type = "text/css";
  4973.     tag.media = "all";
  4974.  
  4975.     document.getElementsByTagName("head")[0].appendChild(tag);
  4976.  
  4977.     return tag;
  4978.   },
  4979.   __getStylesheet: function(url) {
  4980.     var r = new Request({url: url, async: false});
  4981.     var result;
  4982.     r.addEvent("complete", function(x) {
  4983.       result = x;
  4984.     });
  4985.     r.get();
  4986.     return result;
  4987.   },
  4988.   __setStylesheet: function(stylesheet) {
  4989.     var node = this.__tag;
  4990.  
  4991.     if(node.styleSheet) { /* IE */
  4992.       node.styleSheet.cssText = stylesheet;
  4993.     } else {
  4994.       var d = document.createTextNode(stylesheet);
  4995.       node.appendChild(d);
  4996.       while(node.childNodes.length > 1)
  4997.         node.removeChild(node.firstChild);
  4998.     }
  4999.   },
  5000.   __parseStylesheet: function(data) {
  5001.     var lines = data.replace("\r\n", "\n").split("\n");
  5002.  
  5003.     var rules = {};
  5004.     var i;
  5005.     for(i=0;i<lines.length;i++) {
  5006.       var line = lines[i];
  5007.       if(line.trim() === "")
  5008.         break;
  5009.  
  5010.       var tokens = line.splitMax("=", 2);
  5011.       if(tokens.length != 2)
  5012.         continue;
  5013.  
  5014.       rules[tokens[0]] = tokens[1];
  5015.     }
  5016.  
  5017.     var cssLines = []
  5018.     for(;i<lines.length;i++)
  5019.       cssLines.push(lines[i]);
  5020.  
  5021.     return {cssText: cssLines.join("\n"), rules: rules};
  5022.   },
  5023.   set: function(fg_mutator, fg_sec_mutator, bg_mutator) {
  5024.     if(!fg_mutator)
  5025.       fg_mutator = function(x) { return x; };
  5026.     if(!fg_sec_mutator)
  5027.       fg_sec_mutator = fg_mutator;
  5028.     if(!bg_mutator)
  5029.       bg_mutator = function(x) { return x; };
  5030.  
  5031.     var text = this.__cssText;
  5032.     for(var key in this.rules) {
  5033.       var value;
  5034.       if (key.substring(7, 0) == "fg_sec_")
  5035.         value = fg_sec_mutator(new Color(this.rules[key]));
  5036.       else if (key.substring(3, 0) == "fg_")
  5037.         value = fg_mutator(new Color(this.rules[key]));
  5038.       else
  5039.         value = bg_mutator(new Color(this.rules[key]));
  5040.  
  5041.       if(value == "255,255,255") /* IE confuses white with transparent... */
  5042.         value = "255,255,254";
  5043.  
  5044.       text = text.replaceAll("$(" + key + ")", "rgb(" + value + ")");
  5045.     }
  5046.  
  5047.     this.__setStylesheet(text);
  5048.   }
  5049. });
  5050.  
  5051. qwebirc.ui.TabCompleterFactory = new Class({
  5052.   initialize: function(session) {
  5053.     this.session = session;
  5054.     this.reset();
  5055.   },
  5056.   tabComplete: function(textBox) {
  5057.     var text = textBox.value;
  5058.  
  5059.     if(!$defined(this.obj)) {
  5060.       this.incr = 1;
  5061.  
  5062.       var w = ui.getActiveWindow();
  5063.       if(!w)
  5064.         return;
  5065.  
  5066.       var startingWord = qwebirc.util.getEnclosedWord(text, qwebirc.util.getCaretPos(textBox));
  5067.       var preword = "", word = "", postword = "";
  5068.       if($defined(startingWord)) {
  5069.         var preword = text.substring(0, startingWord[0]);
  5070.         var word = startingWord[1];
  5071.         var postword = text.substring(startingWord[0] + word.length);
  5072.       }
  5073.  
  5074.       var ltext = text.toLowerCase();
  5075.       if(text == "") {
  5076.         preword = "/msg ";
  5077.         obj = qwebirc.ui.QueryTabCompleter;
  5078.       } else if(session.irc.isChannel(word)) {
  5079.         obj = qwebirc.ui.ChannelNameTabCompleter;
  5080.       } else if(ltext.match(/^\/(q|query|msg) /i)) {
  5081.         obj = qwebirc.ui.QueryTabCompleter;
  5082.       } else if(w.type == qwebirc.ui.WINDOW_QUERY) {
  5083.         obj = qwebirc.ui.QueryNickTabCompleter;
  5084.       } else if(w.type == qwebirc.ui.WINDOW_CHANNEL) {
  5085.         /* "slug[TAB]" == "slug: " */
  5086.         if(preword == "") {
  5087.           if((postword != "") && postword.charAt(0) == " ") {
  5088.             postword = ":" + postword;
  5089.           } else {
  5090.             postword = ": " + postword;
  5091.           }
  5092.           this.incr++;
  5093.         }
  5094.         obj = qwebirc.ui.ChannelUsersTabCompleter;
  5095.       } else {
  5096.         return;
  5097.       }
  5098.  
  5099.       if(postword == "")
  5100.         postword = " ";
  5101.  
  5102.       this.obj = new obj(session, preword, word, postword, w);
  5103.       if(!$defined(this.obj))
  5104.         return;
  5105.     }
  5106.  
  5107.     var r = this.obj.get();
  5108.     if(!$defined(r))
  5109.       return;
  5110.  
  5111.     textBox.value = r[1];
  5112.     qwebirc.util.setCaretPos(textBox, r[0] + this.incr);
  5113.   },
  5114.   reset: function() {
  5115.     this.obj = null;
  5116.   }
  5117. });
  5118.  
  5119. qwebirc.ui.TabIterator = new Class({
  5120.   initialize: function(session, prefix, list) {
  5121.     this.prefix = prefix;
  5122.     if(!$defined(list) || list.length == 0) {
  5123.       this.list = null;
  5124.     } else {
  5125.       var l = [];
  5126.  
  5127.       var prefixl = qwebirc.irc.toIRCCompletion(session, prefix);
  5128.  
  5129.       /* convert the nick list to IRC lower case, stripping all non letters
  5130.        * before comparisions */
  5131.       for(var i=0;i<list.length;i++) {
  5132.         var l2 = qwebirc.irc.toIRCCompletion(session, list[i]);
  5133.  
  5134.         if(l2.startsWith(prefixl))
  5135.           l.push(list[i]);
  5136.       }
  5137.       this.list = l;
  5138.     }
  5139.  
  5140.     this.pos = -1;
  5141.   },
  5142.   next: function() {
  5143.     /*
  5144.      * ideally next would do the list gubbins recursively, but no JS engine currently
  5145.      * support tail recursion :(
  5146.      */
  5147.     if(!$defined(this.list))
  5148.       return null;
  5149.  
  5150.     this.pos = this.pos + 1;
  5151.     if(this.pos >= this.list.length)
  5152.       this.pos = 0;
  5153.  
  5154.     return this.list[this.pos];
  5155.   }
  5156. });
  5157.  
  5158. qwebirc.ui.BaseTabCompleter = new Class({
  5159.   initialize: function(session, prefix, existingNick, suffix, list) {
  5160.     this.existingNick = existingNick;
  5161.     this.prefix = prefix;
  5162.     this.suffix = suffix;
  5163.     this.iterator = new qwebirc.ui.TabIterator(session, existingNick, list);
  5164.   },
  5165.   get: function() {
  5166.     var n = this.iterator.next();
  5167.     if(!$defined(n))
  5168.       return null;
  5169.  
  5170.     var p = this.prefix + n;
  5171.     return [p.length, p + this.suffix];
  5172.   }
  5173. });
  5174.  
  5175. qwebirc.ui.QueryTabCompleter = new Class({
  5176.   Extends: qwebirc.ui.BaseTabCompleter,
  5177.   initialize: function(session, prefix, existingNick, suffix) {
  5178.     this.parent(session, prefix, existingNick, suffix, session.irc.lastNicks);
  5179.   }
  5180. });
  5181.  
  5182. qwebirc.ui.QueryNickTabCompleter = new Class({
  5183.   Extends: qwebirc.ui.BaseTabCompleter,
  5184.   initialize: function(session, prefix, existingText, suffix) {
  5185.     var nick = window.name
  5186.     this.parent(session, prefix, existingText, suffix, [nick]);
  5187.   }
  5188. });
  5189.  
  5190. qwebirc.ui.ChannelNameTabCompleter = new Class({
  5191.   Extends: qwebirc.ui.BaseTabCompleter,
  5192.   initialize: function(session, prefix, existingText, suffix) {
  5193.  
  5194.     /* WTB map */
  5195.     var l = [];
  5196.  
  5197.     for(var c in session.irc.channels) {
  5198.       var w = session.windows[c];
  5199.  
  5200.       /* redundant? */
  5201.       if($defined(w))
  5202.         w = w.lastSelected;
  5203.  
  5204.       l.push([w, c]);
  5205.     }
  5206.  
  5207.     l.sort(function(a, b) {
  5208.       return b[0] - a[0];
  5209.     });
  5210.  
  5211.     var l2 = [];
  5212.     for(var i=0;i<l.length;i++)
  5213.       l2.push(l[i][1]);
  5214.     this.parent(session, prefix, existingText, suffix, l2);
  5215.   }
  5216. });
  5217.  
  5218. qwebirc.ui.ChannelUsersTabCompleter = new Class({
  5219.   Extends: qwebirc.ui.BaseTabCompleter,
  5220.   initialize: function(session, prefix, existingText, suffix, nc) {
  5221.     var nc = session.irc.tracker.getSortedByLastSpoke(ui.getActiveWindow().name);
  5222.  
  5223.     this.parent(session, prefix, existingText, suffix, nc);
  5224.   }
  5225. });
  5226.  
  5227. qwebirc.ui.themes.ThemeControlCodeMap = {
  5228.   "C": "\x03",
  5229.   "B": "\x02",
  5230.   "U": "\x1F",
  5231.   "O": "\x0F",
  5232.   "{": "\x00",
  5233.   "}": "\x00",
  5234.   "[": "qwebirc://whois/",
  5235.   "]": "/",
  5236.   "$": "$"
  5237. };
  5238.  
  5239. qwebirc.ui.themes.Default = {
  5240.   "PREFIX": ["$C4==$O "],
  5241.   "SIGNON": ["Signed on!", true],
  5242.   "CONNECT": ["Connected to server.", true],
  5243.   "RAW": ["$m", true],
  5244.   "DISCONNECT": ["Disconnected from server: $m", true],
  5245.   "ERROR": ["ERROR: $m", true],
  5246.   "SERVERNOTICE": ["$m", true],
  5247.   "JOIN": ["${$N$} [$h] has joined $c", true],
  5248.   "OURJOIN": ["${$N$} [$h] has joined $c", true],
  5249.   "PART": ["${$N$} [$h] has left $c [$m]", true],
  5250.   "KICK": ["${$v$} was kicked from $c by ${$N$} [$m]", true],
  5251.   "MODE": ["mode/$c [$m] by ${$N$}", true],
  5252.   "QUIT": ["${$N$} [$h] has quit [$m]", true],
  5253.   "NICK": ["${$n$} has changed nick to ${$[$w$]$}", true],
  5254.   "TOPIC": ["${$N$} changed the topic of $c to: $m", true],
  5255.   "UMODE": ["Usermode change: $m", true],
  5256.   "INVITE": ["$N invites you to join $c", true],
  5257.   "HILIGHT": ["$C4"],
  5258.   "HILIGHTEND": ["$O"],
  5259.   "CHANMSG": ["<${$@$($N$)$}> $m"],
  5260.   "PRIVMSG": ["<$($N$)> $m"],
  5261.   "CHANNOTICE": ["-${$($N$)$}:$c- $m"],
  5262.   "PRIVNOTICE": ["-$($N$)- $m"],
  5263.   "OURCHANMSG": ["<$@$N> $m"],
  5264.   "OURPRIVMSG": ["<$N> $m"],
  5265.   "OURTARGETEDMSG": ["*$[$t$]* $m"],
  5266.   "OURTARGETEDNOTICE": ["[notice($[$t$])] $m"],
  5267.   "OURCHANNOTICE": ["-$N:$t- $m"],
  5268.   "OURPRIVNOTICE": ["-$N- $m"],
  5269.   "OURCHANACTION": [" * $N $m"],
  5270.   "OURPRIVACTION": [" * $N $m"],
  5271.   "CHANACTION": [" * ${$($N$)$} $m"],
  5272.   "PRIVACTION": [" * $($N$) $m"],
  5273.   "CHANCTCP": ["$N [$h] requested CTCP $x from $c: $m"],
  5274.   "PRIVCTCP": ["$N [$h] requested CTCP $x from $-: $m"],
  5275.   "CTCPREPLY": ["CTCP $x reply from $N: $m"],
  5276.   "OURCHANCTCP": ["[ctcp($t)] $x $m"],
  5277.   "OURPRIVCTCP": ["[ctcp($t)] $x $m"],
  5278.   "OURTARGETEDCTCP": ["[ctcp($t)] $x $m"],
  5279.   "WHOISUSER": ["$B$N$B [$h]", true],
  5280.   "WHOISREALNAME": [" realname : $m", true],
  5281.   "WHOISCHANNELS": [" channels : $m", true],
  5282.   "WHOISSERVER": [" server   : $x [$m]", true],
  5283.   "WHOISACCOUNT": [" account  : qwebirc://accinfo/$m", true],
  5284.   "WHOISIDLE": [" idle     : $x [connected: $m]", true],
  5285.   "WHOISAWAY": [" away     : $m", true],
  5286.   "WHOISOPER": [" oper     : $BIRC Operator$B", true],
  5287.   "WHOISOPERNAME": [" operedas : $m", true],
  5288.   "WHOISACTUALLY": [" realhost : $m [ip: $x]", true],
  5289.   "WHOISAVAILHELP": [" oper     : is available for help.", true],
  5290.   "WHOISREGGED": [" regged   : has a registered nick.", true],
  5291.   "WHOISMODES": [" modes    : $m", true],
  5292.   "WHOISREALHOST": [" realhost : $m ($x)", true],
  5293.   "WHOISGENERICTEXT": [" info     : $m", true],
  5294.   "WHOISEND": ["End of WHOIS", true],
  5295.   "AWAY": ["$N is away: $m", true],
  5296.   "GENERICERROR": ["$m: $t", true],
  5297.   "GENERICMESSAGE": ["$m", true],
  5298.   "WALLOPS": ["WALLOP $n: $t", true],
  5299.   "CHANNELCREATIONTIME": ["Channel $c was created at: $m", true],
  5300.   "CHANNELMODEIS": ["Channel modes on $c are: $m", true]
  5301. };
  5302.  
  5303. qwebirc.ui.Theme = new Class({
  5304.   initialize: function(themeDict) {
  5305.     this.__theme = qwebirc.util.dictCopy(qwebirc.ui.themes.Default);
  5306.  
  5307.     if(themeDict)
  5308.       for(var k in themeDict)
  5309.         this.__theme[k] = themeDict[k];
  5310.  
  5311.     for(var k in this.__theme) {
  5312.       if(k == "PREFIX")
  5313.         continue;
  5314.  
  5315.       var data = this.__theme[k];
  5316.       if(data[1]) {
  5317.         this.__theme[k] = this.__theme["PREFIX"] + data[0];
  5318.       } else {
  5319.         this.__theme[k] = data[0];
  5320.       }
  5321.     }
  5322.  
  5323.     this.__ccmap = qwebirc.util.dictCopy(qwebirc.ui.themes.ThemeControlCodeMap);
  5324.     this.__ccmaph = qwebirc.util.dictCopy(this.__ccmap);
  5325.  
  5326.     this.__ccmaph["("] = this.message("HILIGHT", {}, this.__ccmap);
  5327.     this.__ccmaph[")"] = this.message("HILIGHTEND", {}, this.__ccmap);
  5328.     this.__ccmaph["{"] = this.__ccmaph["}"] = "";
  5329.   },
  5330.   __dollarSubstitute: function(x, h, mapper) {
  5331.     var msg = [];
  5332.  
  5333.     var n = x.split("");
  5334.     for(var i=0;i<n.length;i++) {
  5335.       var c = n[i];
  5336.       if(c == "$" && (i <= n.length - 1)) {
  5337.         var c2 = n[++i];
  5338.  
  5339.         var o = mapper[c2];
  5340.         if(!o)
  5341.           o = h[c2];
  5342.         if(o)
  5343.           msg.push(o);
  5344.       } else {
  5345.         msg.push(c);
  5346.       }
  5347.     }
  5348.  
  5349.     return msg.join("");
  5350.   },
  5351.   message: function(type, data, hilight) {
  5352.     var map;
  5353.     if(hilight) {
  5354.       map = this.__ccmaph;
  5355.     } else {
  5356.       map = this.__ccmap;
  5357.     }
  5358.  
  5359.     if(data && data["n"])
  5360.       data["N"] = "qwebirc://whois/" + data.n + "/";
  5361.     return this.__dollarSubstitute(this.__theme[type], data, map);
  5362.   }
  5363. });
  5364.  
  5365. qwebirc.ui.urlificate = function(session, element, text) {
  5366.   var punct_re = /[[\)|\]]?(\.*|[\,;])$/;
  5367.   var addedText = [];
  5368.  
  5369.   var txtprocess = function(text, regex, appendfn, matchfn) {
  5370.     for(;;) {
  5371.       var index = text.search(regex);
  5372.       if(index == -1) {
  5373.        appendfn(text);
  5374.        break;
  5375.       }
  5376.       var match = text.match(regex);
  5377.  
  5378.       var before = text.substring(0, index);
  5379.       var matched = match[0];
  5380.       var after = text.substring(index + matched.length);
  5381.  
  5382.       appendfn(before);
  5383.       var more = matchfn(matched, appendfn);
  5384.       if(!more)
  5385.         more = "";
  5386.       text = more + after;
  5387.     }
  5388.   };
  5389.  
  5390.   var appendText = function(text) {
  5391.     addedText.push(text);
  5392.     qwebirc.util.NBSPCreate(text, element);
  5393.   };
  5394.  
  5395.   var appendChan = function(text) {
  5396.     var newtext = text.replace(punct_re, "");
  5397.     addedText.push(newtext);
  5398.     var punct = text.substring(newtext.length);
  5399.  
  5400.     var a = new Element("span");
  5401.     a.href = "#";
  5402.     a.addClass("hyperlink-channel");
  5403.     a.addEvent("click", function(e) {
  5404.       new Event(e).stop();
  5405.       if (session.irc)
  5406.         session.irc.exec("/JOIN " + newtext);
  5407.       else {
  5408.         var connect = ui.getWindow(qwebirc.ui.WINDOW_CUSTOM, "Connect");
  5409.         if (connect) {
  5410.           var connected = connect.subWindow.connectChannel(newtext);
  5411.           if (!connected) {
  5412.             ui.selectWindow(connect);
  5413.             connect.subWindow.nickBox.focus();
  5414.           }
  5415.         }
  5416.       }
  5417.     });
  5418.     a.appendChild(document.createTextNode(newtext));
  5419.     element.appendChild(a);
  5420.  
  5421.     return punct;
  5422.   };
  5423.  
  5424.   var appendURL = function(text, appendfn) {
  5425.     var url = text.replace(punct_re, "");
  5426.     var punct = text.substring(url.length);
  5427.  
  5428.     var href = "";
  5429.     var fn = null;
  5430.     var target = "_blank";
  5431.     var disptext = url;
  5432.     var elementType = "a";
  5433.     var addClass;
  5434.  
  5435.     var ma = url.match(/^qwebirc:\/\/(.*)$/);
  5436.     if(ma) {
  5437.       var m = ma[1].match(/^([^\/]+)\/([^\/]+)\/?(.*)$/);
  5438.       if(!m) {
  5439.         appendfn(text);
  5440.         return;
  5441.       }
  5442.  
  5443.       var cmd = ui.urlDispatcher(m[1]);
  5444.       if(cmd) {
  5445.         addClass = m[1];
  5446.         elementType = cmd[0];
  5447.         if(cmd[0] != "a") {
  5448.           url = null;
  5449.         } else {
  5450.           url = "#";
  5451.         }
  5452.         fn = cmd[1];
  5453.         disptext = unescape(m[2]);
  5454.         target = null;
  5455.       } else {
  5456.         appendfn(text);
  5457.         return;
  5458.       }
  5459.       if(m[3])
  5460.         punct = m[3] + punct;
  5461.     } else {
  5462.       if(url.match(/^www\./))
  5463.         url = "http://" + url;
  5464.     }
  5465.  
  5466.     var a = new Element(elementType);
  5467.     if(addClass)
  5468.       a.addClass("hyperlink-" + addClass);
  5469.  
  5470.     if(url) {
  5471.       a.href = url;
  5472.  
  5473.       if(target)
  5474.         a.target = target;
  5475.     }
  5476.     addedText.push(disptext);
  5477.     a.appendChild(document.createTextNode(disptext));
  5478.  
  5479.     element.appendChild(a);
  5480.     if($defined(fn))
  5481.       a.addEvent("click", function(e) { new Event(e).stop(); fn(disptext); });
  5482.     else
  5483.       a.addEvent("click", function(e) { new Event(e).stopPropagation(); });
  5484.  
  5485.     return punct;
  5486.   };
  5487.  
  5488.   txtprocess(text, /\b((https?|ftp|qwebirc):\/\/|www\.)[^ ]+/, function(text) {
  5489.     txtprocess(text, /\B#[^ ,]+/, appendText, appendChan);
  5490.   }, appendURL);
  5491.  
  5492.   return addedText.join("");
  5493. }
  5494.  
  5495. qwebirc.ui.Panes.Connect = {
  5496.   title: "Connect",
  5497.   command: function(session) { return null; },
  5498.   menuitem: function(session) { return null; }
  5499. };
  5500.  
  5501. qwebirc.ui.Panes.Connect.pclass = new Class({
  5502.   Implements: [Events],
  5503.   session: null,
  5504.   parent: null,
  5505.   connectCallback: null,
  5506.   nickBox: null,
  5507.   chanBox: null,
  5508.  
  5509.   initialize: function(session, w) {
  5510.     this.session = session;
  5511.     this.parent = w.lines;
  5512.  
  5513.     if (!conf.frontend.prompt
  5514.           && conf.frontend.initial_nick
  5515.           && conf.frontend.initial_chans)
  5516.       this.createConfirmBox();
  5517.     else
  5518.       this.createLoginBox(null);
  5519.   },
  5520.  
  5521.   connectChannel: function(channel) {
  5522.     if (conf.frontend.chan_autoconnect
  5523.         && (conf.frontend.initial_nick
  5524.          || (this.nickBox && this.nickBox.value))) {
  5525.       this.connect(channel);
  5526.       return true;
  5527.     }
  5528.     else {
  5529.       if (this.chanBox != null)
  5530.         this.chanBox.set("value", channel);
  5531.       else
  5532.         this.createLoginBox(channel);
  5533.  
  5534.       return false;
  5535.     }
  5536.   },
  5537.  
  5538.   connect: function(channel) {
  5539.     while(this.parent.childNodes.length > 0)
  5540.       this.parent.removeChild(this.parent.firstChild);
  5541.  
  5542.     var data = {};
  5543.  
  5544.     if (this.nickBox != null)
  5545.       data["nickname"] = this.nickBox.value;
  5546.     else
  5547.       data["nickname"] = conf.frontend.initial_nick;
  5548.  
  5549.     if (channel != null)
  5550.       data["autojoin"] = channel;
  5551.     else if (this.chanBox != null)
  5552.       data["autojoin"] = this.chanBox.value;
  5553.     else
  5554.       data["autojoin"] = conf.frontend.initial_chans;
  5555.  
  5556.     if (this.session.atheme.state) {
  5557.       data["authUser"] = this.session.atheme.user;
  5558.       data["authSecret"] = this.session.atheme.secret;
  5559.     }
  5560.  
  5561.     this.connectCallback(data);
  5562.   },
  5563.  
  5564.   /* Focus elem if we're not embedded in an iframe. This prevents stealing focus. */
  5565.   autoFocus: function(elem) {
  5566.     /* Accessing window.top might raise if we are iframed... */
  5567.     try {
  5568.       if (window.self === window.top) {
  5569.         elem.focus();
  5570.       }
  5571.     } catch (e) {}
  5572.   },
  5573.  
  5574.   createConfirmBox: function() {
  5575.     while(this.parent.childNodes.length > 0)
  5576.       this.parent.removeChild(this.parent.firstChild);
  5577.  
  5578.     var outerbox = new Element("table");
  5579.     outerbox.addClass("qwebirc-centrebox");
  5580.     this.parent.appendChild(outerbox);
  5581.     var tbody = new Element("tbody");
  5582.     outerbox.appendChild(tbody);
  5583.  
  5584.     var tr = new Element("tr");
  5585.     tbody.appendChild(tr);
  5586.     var td = new Element("td");
  5587.     tr.appendChild(td);
  5588.  
  5589.     var box = new Element("table");
  5590.     box.addClass("qwebirc-confirmbox");
  5591.     td.appendChild(box);
  5592.  
  5593.     var tbody = new Element("tbody");
  5594.     box.appendChild(tbody);
  5595.  
  5596.     var tr = new Element("tr");
  5597.     tbody.appendChild(tr);
  5598.     tr.addClass("tr1");
  5599.  
  5600.     var text = new Element("td");
  5601.     tr.appendChild(text);
  5602.  
  5603.     var nick = new Element("b");
  5604.     nick.set("text", conf.frontend.initial_nick);
  5605.  
  5606.     var c = conf.frontend.initial_chans.split(" ")[0].split(",");
  5607.  
  5608.     text.appendChild(document.createTextNode("To connect to " + conf.frontend.network_name + " IRC and join channel" + ((c.length>1)?"s":"") + " "));
  5609.  
  5610.     for(var i=0;i<c.length;i++) {
  5611.       if((c.length > 1) && (i == c.length - 1)) {
  5612.         text.appendChild(document.createTextNode(" and "));
  5613.       } else if(i > 0) {
  5614.         text.appendChild(document.createTextNode(", "));
  5615.       }
  5616.       text.appendChild(new Element("b").set("text", c[i]));
  5617.     }
  5618.  
  5619.     if(!conf.frontend.initial_nick_rand) {
  5620.       text.appendChild(document.createTextNode(" as "));
  5621.       text.appendChild(nick);
  5622.     }
  5623.  
  5624.     text.appendChild(document.createTextNode(" click 'Connect'."));
  5625.     text.appendChild(new Element("br"));
  5626.  
  5627.     var tr = new Element("tr");
  5628.     tbody.appendChild(tr);
  5629.     tr.addClass("tr2");
  5630.  
  5631.     var td = new Element("td");
  5632.     tr.appendChild(td);
  5633.  
  5634.     var form = new Element("form");
  5635.     td.appendChild(form);
  5636.  
  5637.     var yes = new Element("input", {"type": "submit", "value": "Connect"});
  5638.     form.appendChild(yes);
  5639.     this.autoFocus(yes);
  5640.  
  5641.     form.addEvent("submit", function(e) {
  5642.       new Event(e).stop();
  5643.       this.connect(null);
  5644.     }.bind(this));
  5645.  
  5646.   },
  5647.  
  5648.   createLoginBox: function(channel) {
  5649.     while(this.parent.childNodes.length > 0)
  5650.       this.parent.removeChild(this.parent.firstChild);
  5651.  
  5652.     var outerbox = new Element("table");
  5653.     outerbox.addClass("qwebirc-centrebox");
  5654.     this.parent.appendChild(outerbox);
  5655.     var tbody = new Element("tbody");
  5656.     outerbox.appendChild(tbody);
  5657.  
  5658.     var tr = new Element("tr");
  5659.     tbody.appendChild(tr);
  5660.     var td = new Element("td");
  5661.     tr.appendChild(td);
  5662.  
  5663.     var box = new Element("table");
  5664.     box.addClass("qwebirc-loginbox");
  5665.     td.appendChild(box);
  5666.  
  5667.     var tbody = new Element("tbody");
  5668.     box.appendChild(tbody);
  5669.  
  5670.     var tr = new Element("tr");
  5671.     tbody.appendChild(tr);
  5672.     tr.addClass("tr1");
  5673.  
  5674.     var td = new Element("td");
  5675.     tr.appendChild(td);
  5676.     td.set("html", "<h1>Connect to " + conf.frontend.network_name + " IRC</h1>");
  5677.  
  5678.     var tr = new Element("tr");
  5679.     tbody.appendChild(tr);
  5680.     tr.addClass("tr2");
  5681.  
  5682.     var td = new Element("td");
  5683.     tr.appendChild(td);
  5684.  
  5685.     var form = new Element("form");
  5686.     td.appendChild(form);
  5687.  
  5688.     var boxtable = new Element("table");
  5689.     form.appendChild(boxtable);
  5690.  
  5691.     var tbody = new Element("tbody");
  5692.     boxtable.appendChild(tbody); /* stupid IE */
  5693.  
  5694.     function createRow(label, e2, style) {
  5695.       var r = new Element("tr");
  5696.       tbody.appendChild(r);
  5697.  
  5698.       var d1 = new Element("td");
  5699.       if(label)
  5700.         d1.set("text", label);
  5701.       r.appendChild(d1);
  5702.  
  5703.       var d2 = new Element("td");
  5704.       r.appendChild(d2);
  5705.  
  5706.       if($defined(e2))
  5707.         d2.appendChild(e2);
  5708.       if($defined(style)) {
  5709.         r.setStyles(style);
  5710.         return [r, d2];
  5711.       }
  5712.  
  5713.       return d2;
  5714.     }
  5715.  
  5716.     this.nickBox = new Element("input");
  5717.     createRow("Nickname:", this.nickBox);
  5718.  
  5719.     if (conf.atheme.nickserv_login) {
  5720.       var srvbutton = new Element("input");
  5721.       srvbutton.set("type", "checkbox");
  5722.       srvbutton.set("checked", false);
  5723.       createRow("Login to Services:", srvbutton);
  5724.  
  5725.       var user = new Element("input");
  5726.       var userRow = createRow("Username:", user, {})[0];
  5727.       userRow.setStyle("display", "none");
  5728.  
  5729.       var pass = new Element("input");
  5730.       pass.set("type", "password");
  5731.       var passRow = createRow("Password:", pass, {})[0];
  5732.       passRow.setStyle("display", "none");
  5733.  
  5734.       var syncInput = function (e) {
  5735.         user.value = this.nickBox.value;
  5736.       }.bind(this);
  5737.  
  5738.       /* the 'input' event is buggy in IE9, but this isn't a very
  5739.        * important feature.
  5740.        */
  5741.       user.addEvent("input", function (e) {
  5742.         this.nickBox.removeEvent("input", syncInput, false);
  5743.       }.bind(this), false);
  5744.  
  5745.       srvbutton.addEvent("click", function(e) {
  5746.         var visible = srvbutton.checked;
  5747.         var display = visible ? null : "none";
  5748.         userRow.setStyle("display", display);
  5749.         passRow.setStyle("display", display);
  5750.         if (visible) {
  5751.           this.nickBox.addEvent("input", syncInput, false);
  5752.           user.focus();
  5753.           /* setting the value after calling focus() will place the cursor at
  5754.            * the end of the text.
  5755.            */
  5756.           user.value = this.nickBox.value;
  5757.         } else {
  5758.           this.nickBox.removeEvent("input", syncInput, false);
  5759.           this.nickBox.focus();
  5760.         }
  5761.       }.bind(this));
  5762.  
  5763.     }
  5764.  
  5765.     //if (channel || conf.frontend.chan_prompt ||
  5766.     //    !conf.frontend.initial_chans) {
  5767.     //  this.chanBox = new Element("input");
  5768.     //  createRow("Channels:", this.chanBox);
  5769.     //}
  5770.  
  5771.     var connbutton = new Element("input", {"type": "submit"});
  5772.     connbutton.set("value", "Connect");
  5773.     var r = createRow(undefined, connbutton);
  5774.  
  5775.     form.addEvent("submit", function(e) {
  5776.       new Event(e).stop();
  5777.  
  5778.       if(!this.nickBox.value) {
  5779.         alert("You must supply a nickname.");
  5780.         this.nickBox.focus();
  5781.         return;
  5782.       }
  5783.  
  5784.       if(!this.nickBox.value.match(/^[a-zA-Z0-9`^\-_\[\]{}|\\]+$/)) {
  5785.         alert("Invalid nickname entered; only characters in the list \"A-Z a-z 0-9 ` ^ - \\ [ ] { } |\" are allowed.");
  5786.         this.nickBox.focus();
  5787.         return;
  5788.       }
  5789.       if(this.nickBox.value.match(/^\d/) || this.nickBox.value[0] == '-') {
  5790.         alert("Invalid nickname entered; nicknames may not start with - or a number.");
  5791.         this.nickBox.focus();
  5792.         return;
  5793.       }
  5794.       Cookie.write("iris-nick", this.nickBox.value, {"duration": 3650});
  5795.  
  5796.       if (conf.atheme.nickserv_login) {
  5797.         if (srvbutton.checked) {
  5798.           if (!user.value) {
  5799.             alert("You must supply a username.");
  5800.             user.focus();
  5801.             return;
  5802.           }
  5803.           if (!pass.value) {
  5804.             alert("You must supply a password.");
  5805.             pass.focus();
  5806.             return;
  5807.           }
  5808.         }
  5809.  
  5810.         if (srvbutton.checked && conf.atheme.sasl_type == "AUTHCOOKIE") {
  5811.           qwebirc.irc.AthemeQuery.login(function(token) {
  5812.             if (token == null)
  5813.               alert("Authentication failed");
  5814.             else
  5815.               qwebirc.ui.Atheme.handleLogin(this.session, user.value, token);
  5816.             this.connect(null);
  5817.           }.bind(this), user.value, pass.value);
  5818.         }
  5819.         else if (srvbutton.checked && conf.atheme.sasl_type == "PLAIN") {
  5820.           this.session.atheme.state = true;
  5821.           this.session.atheme.user = user.value;
  5822.           this.session.atheme.secret = pass.value;
  5823.           this.connect(null);
  5824.         }
  5825.         else {
  5826.           qwebirc.ui.Atheme.handleLogout(this.session);
  5827.           this.connect(null);
  5828.         }
  5829.       }
  5830.       else {
  5831.         this.connect(null);
  5832.       }
  5833.  
  5834.     }.bind(this));
  5835.  
  5836.     if (Cookie.read("iris-nick") != null)
  5837.       this.nickBox.set("value", Cookie.read("iris-nick"));
  5838.     else if (conf.frontend.initial_nick)
  5839.       this.nickBox.set("value", conf.frontend.initial_nick);
  5840.  
  5841.     if (this.chanBox != null && channel)
  5842.       this.chanBox.set("value", channel);
  5843.     else if (this.chanBox != null)
  5844.       this.chanBox.set("value", conf.frontend.initial_chans);
  5845.  
  5846.     this.autoFocus(this.nickBox);
  5847.   }
  5848. });
  5849.  
  5850.  
  5851.  
  5852. /**
  5853.  * Display a list of channels on the network.
  5854.  */
  5855. qwebirc.ui.Panes.List = {
  5856.   title: "Channels",
  5857.   command: function(session) {
  5858.     if (conf.atheme.chan_list)
  5859.       return "LIST";
  5860.   },
  5861.   menuitem: function(session) {
  5862.     if (conf.atheme.chan_list)
  5863.       return "Channel list";
  5864.   },
  5865.   menupos: 100
  5866. };
  5867.  
  5868. qwebirc.ui.Panes.List.pclass = new Class({
  5869.   Implements: [Events],
  5870.   session: null,
  5871.   parent: null,
  5872.  
  5873.   /* Store the list's current state. */
  5874.   cloud: true,
  5875.   loading: null,
  5876.   timestamp: 0,
  5877.   page: 1,
  5878.   namefilter: "",
  5879.   topicfilter: "",
  5880.   filterBox: null,
  5881.   viewBox: null,
  5882.   chanBox: null,
  5883.   pageBox: null,
  5884.   viewChange: null,
  5885.   pageText: null,
  5886.   prev: null,
  5887.   next: null,
  5888.  
  5889.  
  5890.   initialize: function(session, w) {
  5891.     this.session = session;
  5892.     this.parent = w.lines;
  5893.     this.cloud = conf.atheme.chan_list_cloud_view;
  5894.  
  5895.     /* Make header table. */
  5896.     var headerTable = new Element("table");
  5897.     var header = new Element("tbody");
  5898.     headerTable.appendChild(header);
  5899.  
  5900.  
  5901.     /* Make and add view change link. */
  5902.     this.viewChange = new Element("a", { "class": "", "href": "#" });
  5903.     this.viewChange.addEvent("click", function(e) {
  5904.       if (this.cloud) {
  5905.         this.createListView();
  5906.       } else {
  5907.         this.createCloudView();
  5908.       }
  5909.     }.bind(this));
  5910.     var cell = new Element("td", { "class": "viewchange" });
  5911.     cell.appendChild(this.viewChange);
  5912.     header.appendChild(cell);
  5913.  
  5914.  
  5915.     /* Make filter box. */
  5916.     var filterBox = new Element("form", { "class": "filterbox" });
  5917.  
  5918.     /* Add name filter text box. */
  5919.     var filterSubBox = new Element("span", { "class": "inputbox" });
  5920.     filterSubBox.appendText("Filter by Name:");
  5921.     var nameinput = new Element("input");
  5922.     filterSubBox.appendChild(nameinput);
  5923.     filterBox.appendChild(filterSubBox);
  5924.  
  5925.     /* Add topic filter box. */
  5926.     var filterSubBox = new Element("span", { "class": "inputbox" });
  5927.     filterSubBox.appendText("Filter by Topic:");
  5928.     var topicinput = new Element("input");
  5929.     filterSubBox.appendChild(topicinput);
  5930.     filterBox.appendChild(filterSubBox);
  5931.  
  5932.     /* Add refresh and update filters button. */
  5933.     var refresh = new Element("input", {"type": "submit", "value": "Refresh and Apply Filters"});
  5934.     refresh.addEvent("click", function(e) {
  5935.       (new Event(e)).stop();
  5936.       if (nameinput.value != this.namefilter || topicinput.value != this.topicfilter) {
  5937.         this.namefilter = nameinput.value;
  5938.         this.topicfilter = topicinput.value;
  5939.         this.page = 1;
  5940.       }
  5941.       this.timestamp = 0;
  5942.       this.update();
  5943.     }.bind(this));
  5944.     filterBox.appendChild(refresh);
  5945.  
  5946.     /* Add filter box and completed header. */
  5947.     var cell = new Element("td"); cell.appendChild(filterBox);
  5948.     header.appendChild(cell);
  5949.     this.parent.appendChild(headerTable);
  5950.  
  5951.  
  5952.     /* Make and add view box. */
  5953.     this.viewBox = new Element("div");
  5954.     this.parent.appendChild(this.viewBox);
  5955.  
  5956.     if (this.cloud) {
  5957.       this.createCloudView();
  5958.     } else {
  5959.       this.createListView();
  5960.     }
  5961.   },
  5962.  
  5963.   /**
  5964.    * Setup the "list view" list of channels.
  5965.   */
  5966.   createListView: function() {
  5967.     this.cloud = false;
  5968.     while(this.viewBox.childNodes.length > 0)
  5969.       this.viewBox.removeChild(this.viewBox.firstChild);
  5970.  
  5971.     /* Update view change text. */
  5972.     this.viewChange.set("text", "Switch to cloud view...");
  5973.  
  5974.     /* Create the channel table. */
  5975.     var table = new Element("table", { "class": "listbox", "cellspacing": "0"});
  5976.     this.chanBox = new Element("tbody");
  5977.     table.appendChild(this.chanBox);
  5978.     this.viewBox.appendChild(table);
  5979.  
  5980.     /* Make page box and components. */
  5981.     this.pageBox = new Element("div", { "class": "pagebox" });
  5982.     this.pageBox.style.display = "none";
  5983.     this.prev = new Element("span");
  5984.     this.pageText = new Element("span");
  5985.     this.next = new Element("span");
  5986.     this.pageBox.appendChild(this.prev);
  5987.     this.pageBox.appendText(" - ");
  5988.     this.pageBox.appendChild(this.pageText);
  5989.     this.pageBox.appendText(" - ");
  5990.     this.pageBox.appendChild(this.next);
  5991.  
  5992.     /* Add page box. */
  5993.     this.viewBox.appendChild(this.pageBox);
  5994.  
  5995.     /* Start the loading display timer. */
  5996.     var delayfn = function() { this.chanBox.set("html", "<tr><td class=\"loading\">Loading. . .</td></tr>"); }.bind(this);
  5997.     this.loading = delayfn.delay(500);
  5998.  
  5999.     /* Get a channel list. */
  6000.     this.update();
  6001.   },
  6002.  
  6003.   /**
  6004.    * Setup the "cloud view" list of channels.
  6005.   */
  6006.   createCloudView: function() {
  6007.     this.cloud = true;
  6008.     this.page = 1;
  6009.     while(this.viewBox.childNodes.length > 0)
  6010.       this.viewBox.removeChild(this.viewBox.firstChild);
  6011.  
  6012.     /* Update view change text. */
  6013.     this.viewChange.set("text", "Switch to list view...");
  6014.  
  6015.     /* Add hint text. */
  6016.     var hint = new Element("div", { "class": "hoverhint"});
  6017.     hint.appendText("Hover over a channel to view its topic!");
  6018.     this.viewBox.appendChild(hint);
  6019.  
  6020.     /* Add channel box. */
  6021.     this.chanBox = new Element("div", { "class": "tagbox" });
  6022.     this.viewBox.appendChild(this.chanBox);
  6023.  
  6024.     /* Get a channel list. */
  6025.     this.update();
  6026.   },
  6027.  
  6028.   /**
  6029.    * Update the channel list.
  6030.    */
  6031.   update: function() {
  6032.     qwebirc.irc.AthemeQuery.channelList(function(channels, timestamp, total) {
  6033.  
  6034.       /* Update our timestamp to the timestamp of this list. */
  6035.       this.timestamp = timestamp;
  6036.  
  6037.       /* Cancel any timeout. */
  6038.       if (this.loading != null) {
  6039.         clearTimeout(this.loading);
  6040.         this.loading = null;
  6041.       }
  6042.  
  6043.       /* Remove any previous content from the channel box. */
  6044.       if (this.chanBox.hasChildNodes()) {
  6045.         while (this.chanBox.childNodes.length >= 1)
  6046.           this.chanBox.removeChild(this.chanBox.firstChild);
  6047.       }
  6048.  
  6049.       /* If the connection failed, display that and return. */
  6050.       if (channels == null) {
  6051.         if (this.cloud) {
  6052.           this.chanBox.set("html", "<span class=\"loading\">Unable to load channel list, please try reopening the channel list later.</span>");
  6053.         } else {
  6054.           this.chanBox.set("html", "<tr><td class=\"loading\">Unable to load channel list, please try refreshing again later.</td></tr>");
  6055.         }
  6056.         return;
  6057.       }
  6058.  
  6059.       /* If we got no channels, display that and return. */
  6060.       if (channels.length == 0) {
  6061.         if (this.cloud) {
  6062.           this.chanBox.set("html", "<span class=\"loading\">No channels currently exist, please try refreshing again later.</span>");
  6063.         } else {
  6064.           this.chanBox.set("html", "<tr><td class=\"loading\">No channels currently exist, please try refreshing again later.</td></tr>");
  6065.         }
  6066.         return;
  6067.       }
  6068.  
  6069.       /* Calculate the range of the channel sizes. */
  6070.       var minUsers = channels[channels.length-1].users
  6071.       var userScale = channels[0].users - minUsers
  6072.  
  6073.       /* Print the table headings, for list view. */
  6074.       if (!this.cloud) {
  6075.         var headers = new Element("tr");
  6076.         var name = new Element("th", { "class": "name" });
  6077.         name.appendText("Channel");
  6078.         headers.appendChild(name);
  6079.  
  6080.         var users = new Element("th", { "class": "users" });
  6081.         users.appendText("Users");
  6082.         headers.appendChild(users);
  6083.  
  6084.         var topic = new Element("th", { "class": "chantopic" });
  6085.         topic.appendText("Topic");
  6086.         headers.appendChild(topic);
  6087.         this.chanBox.appendChild(headers);
  6088.       }
  6089.  
  6090.       /* Sort the channels into alphabetical order for cloud view. */
  6091.       if (this.cloud) {
  6092.         channels.sort(function (a, b) {
  6093.           an = a.name.toLowerCase(); bn = b.name.toLowerCase();
  6094.           if (an < bn) return -1;
  6095.           if (an > bn) return 1;
  6096.           return 0;
  6097.         });
  6098.       }
  6099.  
  6100.       /* Finally, add the channels. */
  6101.       for (var i = 0; i < channels.length; i++) {
  6102.         var channel;
  6103.         if (this.cloud) {
  6104.           channel = new Element("span", { "class": "chantag", style: "font-size: " + (1 + 2 * (channels[i].users - minUsers) / (userScale+1)) + "em;" });
  6105.         } else {
  6106.           channel = new Element("tr");
  6107.         }
  6108.  
  6109.         this.makeChannel(channel, channels[i], i%2 + 1);
  6110.  
  6111.     this.chanBox.appendChild(channel);
  6112.         if (this.cloud) {
  6113.           this.chanBox.appendText(" ");
  6114.         }
  6115.       }
  6116.     }.bind(this), this.timestamp, "100", this.page, this.namefilter, this.topicfilter);
  6117.   },
  6118.  
  6119.   /**
  6120.    * Update page numbers and paging boxes.
  6121.    */
  6122.   updatePaging: function(total) {
  6123.  
  6124.     /* Update the page number, if there is one. */
  6125.     if (this.pageText) {
  6126.       var pages = Math.ceil(total/100);
  6127.       while (this.pageText.childNodes.length >= 1)
  6128.         this.pageText.removeChild(this.pageText.firstChild);
  6129.       this.pageText.appendText("Page " + (this.page) + " of " + pages);
  6130.     }
  6131.  
  6132.     /* Update the page change buttons, if there are any. */
  6133.     if (this.prev && this.next) {
  6134.  
  6135.       /* If we have a previous page, enable prev button. */
  6136.       while (this.prev.childNodes.length >= 1)
  6137.        this.prev.removeChild(this.prev.firstChild);
  6138.       if (this.page > 1) {
  6139.         var prevLink = new Element("a", {"href": "#"});
  6140.         prevLink.appendText("Prev Page");
  6141.         prevLink.addEvent("click", function(e) {
  6142.           this.page--;
  6143.           prevLink.removeEvents();
  6144.           this.update();
  6145.         }.bind(this));
  6146.         this.prev.appendChild(prevLink);
  6147.       } else {
  6148.         this.prev.appendText("Prev Page");
  6149.       }
  6150.  
  6151.       /* If we have a next page, enable next button. */
  6152.       while (this.next.childNodes.length >= 1)
  6153.         this.next.removeChild(this.next.firstChild);
  6154.       if (this.page < pages) {
  6155.         var nextLink = new Element("a", {"href": "#"});
  6156.         nextLink.appendText("Next Page");
  6157.         nextLink.addEvent("click", function(e) {
  6158.           this.page++;
  6159.           nextLink.removeEvents();
  6160.           this.update();
  6161.         }.bind(this));
  6162.         this.next.appendChild(nextLink);
  6163.       }
  6164.       else {
  6165.         this.next.appendText("Next Page");
  6166.       }
  6167.     }
  6168.  
  6169.     /* Show the page sorting box only if it has any contents. */
  6170.     if (this.PageBox) {
  6171.       if (this.page != 1 || pages > 1)
  6172.         this.pageBox.style.display = "block";
  6173.       else
  6174.         this.pageBox.style.display = "none";
  6175.     }
  6176.   },
  6177.  
  6178.   /**
  6179.    * Make channel item for the list, as appropriate for the current list mode.
  6180.    */
  6181.   makeChannel: function(chanitem, channel, oddeven) {
  6182.  
  6183.     var name;
  6184.     if (this.cloud) {
  6185.       name = chanitem
  6186.     } else {
  6187.       name = new Element("td", { "class": "name chan" + oddeven });
  6188.     }
  6189.  
  6190.     /* This closure is a trick so each event handler gets a unique
  6191.      * channame recording the channel it should open. */
  6192.     var closure = function() {
  6193.       var channame = channel.name;
  6194.       chanitem.addEvent("click", function(e) {
  6195.         new Event(e).stop();
  6196.         if (this.session.irc)
  6197.           this.session.irc.exec("/JOIN " + channame);
  6198.         else {
  6199.           var connect = ui.getWindow(qwebirc.ui.WINDOW_CUSTOM, "Connect");
  6200.           if (connect) {
  6201.             var connected = connect.subWindow.connectChannel(channame);
  6202.             if (!connected) {
  6203.               ui.selectWindow(connect);
  6204.               connect.subWindow.nickBox.focus();
  6205.             }
  6206.           }
  6207.         }
  6208.       }.bind(this));
  6209.     }.bind(this); closure();
  6210.     name.appendText(channel.name);
  6211.  
  6212.     /* Add all other information shown for the channel in this view. */
  6213.     if (this.cloud) {
  6214.       chanitem.setProperty("title", channel.topic);
  6215.     } else {
  6216.       chanitem.appendChild(name);
  6217.  
  6218.       var users = new Element("td", { "class": "users chan" + oddeven });
  6219.       users.appendText(channel.users);
  6220.       chanitem.appendChild(users);
  6221.  
  6222.       var topic = new Element("td", { "class": "chantopic chan" + oddeven });
  6223.       qwebirc.ui.Colourise(this.session, channel.topic, topic);
  6224.       chanitem.appendChild(topic);
  6225.     }
  6226.   }
  6227. });
  6228.  
  6229. qwebirc.ui.Panes.Options = {
  6230.   title: "Options",
  6231.   command: function(session) { return "OPTIONS"; },
  6232.   menuitem: function(session) { return "Options"; },
  6233.   menupos: 300
  6234. };
  6235.  
  6236. qwebirc.ui.Panes.Options.pclass = new Class({
  6237.   Implements: [Events],
  6238.   session: null,
  6239.   initialize: function(session, w) {
  6240.     this.session = session;
  6241.     this.window = w;
  6242.     this.parent = w.lines;
  6243.  
  6244.     this.createElements();
  6245.   },
  6246.   createElements: function() {
  6247.     var FE = function(element, parent) {
  6248.       var n = new Element(element);
  6249.       parent.appendChild(n);
  6250.       return n;
  6251.     };
  6252.  
  6253.     var t = FE("table", this.parent);
  6254.     var tb = FE("tbody", t);
  6255.  
  6256.     this.boxList = [];
  6257.     this.options = {};
  6258.  
  6259.     for(var i=0;i<qwebirc.options.Options.length;i++) {
  6260.       var x = qwebirc.options.Options[i];
  6261.  
  6262.       var type = qwebirc.options.CheckInput;
  6263.       if ($defined(x.type))
  6264.         type = x.type;
  6265.       if (type == qwebirc.options.ColorInput && $defined(x.isEnabled)) {
  6266.         if (!x.isEnabled(this.session))
  6267.           continue
  6268.       }
  6269.  
  6270.       var row = FE("tr", tb);
  6271.       var cella = FE("td", row);
  6272.       var cellb = FE("td", row);
  6273.  
  6274.       var input = new type(this.session, this.options, cellb, x, i);
  6275.  
  6276.       var label = new Element("label", {"for": input.id});
  6277.       label.set("text", x.label + ":");
  6278.       cella.appendChild(label);
  6279.  
  6280.       this.boxList.push([x, input]);
  6281.       this.options[x.category + "." + x.option] = input;
  6282.     }
  6283.  
  6284.     var r = FE("tr", tb);
  6285.     var cella = FE("td", r);
  6286.     var cellb = FE("td", r);
  6287.     var save = qwebirc.util.createInput("submit", cellb);
  6288.     save.value = "Save";
  6289.  
  6290.     save.addEvent("click", function() {
  6291.       this.save();
  6292.       ui.closeWindow(this.window);
  6293.     }.bind(this));
  6294.  
  6295.     var cancel = qwebirc.util.createInput("submit", cellb);
  6296.     cancel.value = "Cancel";
  6297.     cancel.addEvent("click", function() {
  6298.       this.cancel();
  6299.       ui.closeWindow(this.window);
  6300.     }.bind(this));
  6301.   },
  6302.   save: function() {
  6303.     this.boxList.forEach(function(x) {
  6304.       var option = x[0];
  6305.       var box = x[1];
  6306.  
  6307.       conf[option.category][option.option] = box.get();
  6308.       if (option.onSave)
  6309.         option.onSave(this.session);
  6310.     }.bind(this));
  6311.  
  6312.     qwebirc.config.saveUserSettings(conf);
  6313.   },
  6314.   cancel: function() {
  6315.     this.boxList.forEach(function(x) {
  6316.       if (x[0].onCancel)
  6317.         x[0].onCancel(this.session);
  6318.     }.bind(this));
  6319.   }
  6320. });
  6321.  
  6322. qwebirc.ui.Panes.PrivacyPolicy = {
  6323.   title: "Privacy Policy",
  6324.   command: function(session) {
  6325.     if (conf.ui.privacy) {
  6326.       return "PRIVACYPOLICY";
  6327.     } else {
  6328.       return null;
  6329.     }
  6330.   },
  6331.   menuitem: function(session) {
  6332.     if (conf.ui.privacy) {
  6333.       return "Privacy policy";
  6334.     } else {
  6335.       return null;
  6336.     }
  6337.   },
  6338.   menupos: 700
  6339. };
  6340.  
  6341. qwebirc.ui.Panes.PrivacyPolicy.pclass = new Class({
  6342.   Implements: [Events],
  6343.   session: null,
  6344.   initialize: function(session, w) {
  6345.     this.session = session;
  6346.     var delayfn = function() { w.lines.set("html", "<div class=\"loading\">Loading. . .</div>"); };
  6347.     var cb = delayfn.delay(500);
  6348.  
  6349.     var r = qwebirc.ui.RequestTransformHTML(session, {url: conf.frontend.static_base_url + "panes/privacypolicy.html", update: w.lines, onSuccess: function() {
  6350.       $clear(cb);
  6351.  
  6352.       w.lines.getElement("input[class=close]").addEvent("click", function() {
  6353.         ui.closeWindow(w);
  6354.       }.bind(this));
  6355.     }.bind(this)});
  6356.     r.get();
  6357.   }
  6358. });
  6359.  
  6360. qwebirc.ui.QUI = new Class({
  6361.   Extends: qwebirc.ui.RootUI,
  6362.   initialize: function(session, parentElement) {
  6363.     this.parent(session, parentElement, qwebirc.ui.QUI.Window, "qui");
  6364.     this.theme = new qwebirc.ui.Theme(null);
  6365.     this.setModifiableStylesheet("qui");
  6366.   },
  6367.   postInitialize: function() {
  6368.     this.qjsui = new qwebirc.ui.QUI.JSUI("qwebirc-qui", this.parentElement);
  6369.     this.qjsui.addEvent("reflow", function() {
  6370.       var w = this.getActiveWindow();
  6371.       if($defined(w))
  6372.         w.onResize();
  6373.     }.bind(this));
  6374.     this.qjsui.top.addClass("outertabbar");
  6375.  
  6376.     this.qjsui.bottom.addClass("input");
  6377.     this.qjsui.right.addClass("nicklist");
  6378.     this.qjsui.topic.addClass("topic");
  6379.     this.qjsui.middle.addClass("lines");
  6380.  
  6381.     this.outerTabs = this.qjsui.top;
  6382.  
  6383.     this.tabs = new Element("div", { "class":"tabbar" });
  6384.  
  6385.     this.__createDropdownMenu();
  6386.  
  6387.     this.outerTabs.appendChild(this.tabs);
  6388.     this.origtopic = this.topic = this.qjsui.topic;
  6389.     this.origlines = this.lines = this.qjsui.middle;
  6390.     this.orignicklist = this.nicklist = this.qjsui.right;
  6391.  
  6392.     this.input = this.qjsui.bottom;
  6393.     this.reflow = this.qjsui.reflow.bind(this.qjsui);
  6394.  
  6395.     this.tabs.addEvent("mousewheel", function(x) {
  6396.       var event = new Event(x);
  6397.  
  6398.       /* up */
  6399.       if(event.wheel > 0) {
  6400.         this.nextWindow();
  6401.       } else if(event.wheel < 0) {
  6402.         /* down */
  6403.         this.prevWindow();
  6404.       }
  6405.       event.stop();
  6406.     }.bind(this));
  6407.  
  6408.     this.createInput();
  6409.     this.reflow();
  6410.     this.reflow.delay(100); /* Konqueror fix */
  6411.   },
  6412.   __createDropdownMenu: function() {
  6413.     var dropdownMenu = new Element("span");
  6414.     dropdownMenu.addClass("dropdownmenu");
  6415.  
  6416.     dropdownMenu.hide = function() {
  6417.       dropdownMenu.setStyle("display", "none");
  6418.       dropdownMenu.visible = false;
  6419.       document.removeEvent("mousedown", hideEvent);
  6420.     }.bind(this);
  6421.     var hideEvent = function() { dropdownMenu.hide(); };
  6422.  
  6423.     dropdownMenu.hide();
  6424.     this.parentElement.appendChild(dropdownMenu);
  6425.  
  6426.     var menuitems = [];
  6427.     var i = 0;
  6428.     $each(qwebirc.ui.Panes, function(pane, name, object) {
  6429.       var text = pane.menuitem(this.session);
  6430.       if (text) {
  6431.         menuitems[i] = {};
  6432.         menuitems[i].text = text;
  6433.         menuitems[i].name = name;
  6434.         menuitems[i].pos = pane.menupos;
  6435.         i++;
  6436.       }
  6437.     }.bind(this));
  6438.     menuitems.sort(function(a, b) { return a.pos - b.pos });
  6439.     menuitems.forEach(function(x) {
  6440.       var e = new Element("a");
  6441.       e.addEvent("mousedown", function(e) { new Event(e).stop(); });
  6442.       e.addEvent("click", function() {
  6443.         dropdownMenu.hide();
  6444.         ui.addPane(x.name);
  6445.       }.bind(this));
  6446.       e.set("text", x.text);
  6447.       dropdownMenu.appendChild(e);
  6448.     }.bind(this));
  6449.  
  6450.     var dropdown = new Element("div");
  6451.     dropdown.addClass("dropdown-tab");
  6452.     dropdown.appendChild(new Element("img", {src: conf.frontend.static_base_url + "images/menu.png", title: "menu", alt: "menu"}));
  6453.  
  6454.     this.outerTabs.appendChild(dropdown);
  6455.     dropdownMenu.show = function(x) {
  6456.       new Event(x).stop();
  6457.  
  6458.       if(dropdownMenu.visible) {
  6459.         dropdownMenu.hide();
  6460.         return;
  6461.       }
  6462.       var top = this.outerTabs.getSize().y;
  6463.  
  6464.       dropdownMenu.setStyle("left", 0);
  6465.       dropdownMenu.setStyle("top", top-1); /* -1 == top border */
  6466.       dropdownMenu.setStyle("display", "inline-block");
  6467.       dropdownMenu.visible = true;
  6468.  
  6469.       document.addEvent("mousedown", hideEvent);
  6470.     }.bind(this);
  6471.     dropdown.addEvent("mousedown", function(e) { new Event(e).stop(); });
  6472.     dropdown.addEvent("click", dropdownMenu.show);
  6473.   },
  6474.   createInput: function() {
  6475.     var form = new Element("form");
  6476.     this.input.appendChild(form);
  6477.  
  6478.     form.addClass("input");
  6479.  
  6480.     var inputbox = new Element("input");
  6481.     form.appendChild(inputbox);
  6482.     this.inputbox = inputbox;
  6483.     this.inputbox.maxLength = 512;
  6484.  
  6485.     var sendInput = function() {
  6486.       if(inputbox.value == "")
  6487.         return;
  6488.  
  6489.       this.resetTabComplete();
  6490.       this.getActiveWindow().historyExec(inputbox.value);
  6491.       inputbox.value = "";
  6492.     }.bind(this);
  6493.  
  6494.     if(!qwebirc.util.deviceHasKeyboard()) {
  6495.       inputbox.addClass("mobile-input");
  6496.       var inputButton = new Element("input", {type: "button"});
  6497.       inputButton.addClass("mobile-button");
  6498.       inputButton.addEvent("click", function() {
  6499.         sendInput();
  6500.         inputbox.focus();
  6501.       });
  6502.       inputButton.value = ">";
  6503.       this.input.appendChild(inputButton);
  6504.       var reflowButton = function() {
  6505.         var containerSize = this.input.getSize();
  6506.         var buttonSize = inputButton.getSize();
  6507.  
  6508.         var buttonLeft = containerSize.x - buttonSize.x - 5; /* lovely 5 */
  6509.  
  6510.         inputButton.setStyle("left", buttonLeft);
  6511.         inputbox.setStyle("width", buttonLeft - 5);
  6512.         inputButton.setStyle("height", containerSize.y);
  6513.       }.bind(this);
  6514.       this.qjsui.addEvent("reflow", reflowButton);
  6515.     } else {
  6516.       inputbox.addClass("keyboard-input");
  6517.     }
  6518.  
  6519.     form.addEvent("submit", function(e) {
  6520.       new Event(e).stop();
  6521.       sendInput();
  6522.     });
  6523.  
  6524.     inputbox.addEvent("focus", this.resetTabComplete.bind(this));
  6525.     inputbox.addEvent("mousedown", this.resetTabComplete.bind(this));
  6526.  
  6527.     inputbox.addEvent("keydown", function(e) {
  6528.       var resultfn;
  6529.       var cvalue = inputbox.value;
  6530.  
  6531.       if(e.key == "up") {
  6532.         resultfn = this.commandhistory.upLine;
  6533.       } else if(e.key == "down") {
  6534.         resultfn = this.commandhistory.downLine;
  6535.       } else if(e.key == "tab") {
  6536.         new Event(e).stop();
  6537.         this.tabComplete(inputbox);
  6538.         return;
  6539.       } else {
  6540.         /* ideally alt and other keys wouldn't break this */
  6541.         this.resetTabComplete();
  6542.         return;
  6543.       }
  6544.  
  6545.       this.resetTabComplete();
  6546.       if((cvalue != "") && (this.lastcvalue != cvalue))
  6547.         this.commandhistory.addLine(cvalue, true);
  6548.  
  6549.       var result = resultfn.bind(this.commandhistory)();
  6550.  
  6551.       new Event(e).stop();
  6552.       if(!result)
  6553.         result = "";
  6554.       this.lastcvalue = result;
  6555.  
  6556.       inputbox.value = result;
  6557.       qwebirc.util.setAtEnd(inputbox);
  6558.     }.bind(this));
  6559.   },
  6560.   setLines: function(lines) {
  6561.     this.lines.parentNode.replaceChild(lines, this.lines);
  6562.     this.qjsui.middle = this.lines = lines;
  6563.   },
  6564.   setChannelItems: function(nicklist, topic) {
  6565.     if(!$defined(nicklist)) {
  6566.       nicklist = this.orignicklist;
  6567.       topic = this.origtopic;
  6568.     }
  6569.     this.nicklist.parentNode.replaceChild(nicklist, this.nicklist);
  6570.     this.qjsui.right = this.nicklist = nicklist;
  6571.  
  6572.     this.topic.parentNode.replaceChild(topic, this.topic);
  6573.     this.qjsui.topic = this.topic = topic;
  6574.   },
  6575.   closeWindow: function(window) {
  6576.     this.parent(window);
  6577.  
  6578.     this.tabs.removeChild(window.tab);
  6579.     this.tabs.removeChild(window.spaceNode);
  6580.     this.reflow();
  6581.   }
  6582. });
  6583.  
  6584. qwebirc.ui.QUI.JSUI = new Class({
  6585.   Implements: [Events],
  6586.   initialize: function(class_, parent, sizer) {
  6587.     this.parent = parent;
  6588.     this.sizer = $defined(sizer)?sizer:parent;
  6589.  
  6590.     this.class_ = class_;
  6591.     this.create();
  6592.  
  6593.     this.reflowevent = null;
  6594.  
  6595.     window.addEvent("resize", function() {
  6596.       this.reflow(100);
  6597.     }.bind(this));
  6598.   },
  6599.   applyClasses: function(pos, l) {
  6600.     l.addClass("dynamicpanel");
  6601.     l.addClass(this.class_);
  6602.  
  6603.     if(pos == "middle") {
  6604.       l.addClass("leftboundpanel");
  6605.     } else if(pos == "top") {
  6606.       l.addClass("topboundpanel");
  6607.       l.addClass("widepanel");
  6608.     } else if(pos == "topic") {
  6609.       l.addClass("widepanel");
  6610.     } else if(pos == "right") {
  6611.       l.addClass("rightboundpanel");
  6612.     } else if(pos == "bottom") {
  6613.       l.addClass("bottomboundpanel");
  6614.       l.addClass("widepanel");
  6615.     }
  6616.   },
  6617.   create: function() {
  6618.     var XE = function(pos) {
  6619.       var element = new Element("div");
  6620.       this.applyClasses(pos, element);
  6621.  
  6622.       this.parent.appendChild(element);
  6623.       return element;
  6624.     }.bind(this);
  6625.  
  6626.     this.top = XE("top");
  6627.     this.topic = XE("topic");
  6628.     this.middle = XE("middle");
  6629.     this.right = XE("right");
  6630.     this.bottom = XE("bottom");
  6631.   },
  6632.   reflow: function(delay) {
  6633.     if(!delay)
  6634.       delay = 1;
  6635.  
  6636.     if(this.reflowevent)
  6637.       $clear(this.reflowevent);
  6638.     this.__reflow();
  6639.     this.reflowevent = this.__reflow.delay(delay, this);
  6640.   },
  6641.   __reflow: function() {
  6642.     var bottom = this.bottom;
  6643.     var middle = this.middle;
  6644.     var right = this.right;
  6645.     var topic = this.topic;
  6646.     var top = this.top;
  6647.  
  6648.     var topicsize = topic.getSize();
  6649.     var topsize = top.getSize();
  6650.     var rightsize = right.getSize();
  6651.     var bottomsize = bottom.getSize();
  6652.     var docsize = this.sizer.getSize();
  6653.  
  6654.     var mheight = (docsize.y - topsize.y - bottomsize.y - topicsize.y);
  6655.     var mwidth = (docsize.x - rightsize.x);
  6656.  
  6657.     topic.setStyle("top", topsize.y);
  6658.  
  6659.     middle.setStyle("top", (topsize.y + topicsize.y));
  6660.     if(mheight > 0) {
  6661.       middle.setStyle("height", mheight);
  6662.       right.setStyle("height", mheight);
  6663.     }
  6664.  
  6665.     if(mwidth > 0)
  6666.       middle.setStyle("width", mwidth);
  6667.     right.setStyle("top", (topsize.y + topicsize.y));
  6668.     right.setStyle("left", mwidth);
  6669.  
  6670.     bottom.setStyle("top", (docsize.y - bottomsize.y));
  6671.     this.fireEvent("reflow");
  6672.   },
  6673.   showChannel: function(state) {
  6674.     var display = "none";
  6675.     if(state)
  6676.       display = "block";
  6677.  
  6678.     this.right.setStyle("display", display);
  6679.     this.topic.setStyle("display", display);
  6680.   },
  6681.   showInput: function(state) {
  6682.     this.bottom.isVisible = state;
  6683.     this.bottom.setStyle("display", state?"block":"none");
  6684.   }
  6685. });
  6686.  
  6687. qwebirc.ui.QUI.Window = new Class({
  6688.   Extends: qwebirc.ui.Window,
  6689.  
  6690.   initialize: function(session, type, name, identifier) {
  6691.     this.parent(session, type, name, identifier);
  6692.  
  6693.     this.tab = new Element("a", {"href": "#"});
  6694.     this.tab.addClass("tab");
  6695.     this.tab.addEvent("focus", function() { this.blur() }.bind(this.tab));;
  6696.     this.spaceNode = document.createTextNode(" ");
  6697.  
  6698.     /* Always put the connect/status windows in front. */
  6699.     if (ui.tabs.hasChildNodes()) {
  6700.       if (type == qwebirc.ui.WINDOW_STATUS) {
  6701.         ui.tabs.insertBefore(this.tab, ui.tabs.firstChild);
  6702.         ui.tabs.insertBefore(this.spaceNode, this.tab.nextSibling);
  6703.       }
  6704.       else if (type == qwebirc.ui.WINDOW_CUSTOM && name == "Connect") {
  6705.         ui.tabs.insertBefore(this.tab, ui.tabs.firstChild);
  6706.         ui.tabs.insertBefore(this.spaceNode, this.tab.nextSibling);
  6707.       }
  6708.       else {
  6709.         ui.tabs.appendChild(this.tab);
  6710.         ui.tabs.appendChild(this.spaceNode);
  6711.       }
  6712.     }
  6713.     else {
  6714.       ui.tabs.appendChild(this.tab);
  6715.       ui.tabs.appendChild(this.spaceNode);
  6716.     }
  6717.  
  6718.     this.tab.appendText(name);
  6719.     this.tab.addEvent("click", function(e) {
  6720.       new Event(e).stop();
  6721.  
  6722.       if(this.closed)
  6723.         return;
  6724.  
  6725.       ui.selectWindow(this);
  6726.     }.bind(this));
  6727.  
  6728.     if(type != qwebirc.ui.WINDOW_STATUS && (type != qwebirc.ui.WINDOW_CUSTOM || name != "Connect")) {
  6729.       var tabclose = new Element("span");
  6730.       tabclose.set("text", "X");
  6731.       tabclose.addClass("tabclose");
  6732.       var close = function(e) {
  6733.         new Event(e).stop();
  6734.  
  6735.         if(this.closed)
  6736.           return;
  6737.  
  6738.         if(type == qwebirc.ui.WINDOW_CHANNEL)
  6739.           this.session.irc.exec("/PART " + name);
  6740.  
  6741.         ui.closeWindow(this);
  6742.  
  6743.         //ui.inputbox.focus();
  6744.       }.bind(this);
  6745.  
  6746.       tabclose.addEvent("click", close);
  6747.       this.tab.addEvent("mouseup", function(e) {
  6748.         var button = 1;
  6749.  
  6750.         if(Browser.Engine.trident)
  6751.           button = 4;
  6752.  
  6753.         if(e.event.button == button)
  6754.           close();
  6755.       }.bind(this));
  6756.  
  6757.       this.tab.appendChild(tabclose);
  6758.     }
  6759.  
  6760.     this.lines = new Element("div");
  6761.     ui.qjsui.applyClasses("middle", this.lines);
  6762.     this.lines.addClass("lines");
  6763.     if(type != qwebirc.ui.WINDOW_CUSTOM)
  6764.       this.lines.addClass("ircwindow");
  6765.  
  6766.     this.lines.addEvent("scroll", function() {
  6767.       this.scrolleddown = this.scrolledDown();
  6768.       this.scrollpos = this.getScrollParent().getScroll();
  6769.     }.bind(this));
  6770.  
  6771.     if(type == qwebirc.ui.WINDOW_CHANNEL) {
  6772.       this.topic = new Element("div");
  6773.       this.topic.addClass("topic");
  6774.       this.topic.addClass("tab-invisible");
  6775.       this.topic.set("html", "&nbsp;");
  6776.       this.topic.addEvent("dblclick", this.editTopic.bind(this));
  6777.       ui.qjsui.applyClasses("topic", this.topic);
  6778.  
  6779.       this.prevNick = null;
  6780.       this.nicklist = new Element("div");
  6781.       this.nicklist.addClass("nicklist");
  6782.       this.nicklist.addClass("tab-invisible");
  6783.       this.nicklist.addEvent("click", this.removePrevMenu.bind(this));
  6784.       ui.qjsui.applyClasses("nicklist", this.nicklist);
  6785.     }
  6786.  
  6787.     if(type == qwebirc.ui.WINDOW_CHANNEL)
  6788.       this.updateTopic("");
  6789.  
  6790.     this.nicksColoured = conf.ui.nick_colors;
  6791.     this.reflow();
  6792.   },
  6793.   editTopic: function() {
  6794.     if(!this.session.irc.nickOnChanHasAtLeastPrefix(this.session.irc.nickname, this.name, "+", true)) {
  6795. /*      var cmodes = this.session.irc.getChannelModes(channel);
  6796.       if(cmodes.indexOf("t")) {*/
  6797.         alert("Sorry, you need to be a channel operator to change the topic!");
  6798.         return;
  6799.       /*}*/
  6800.     }
  6801.     var newTopic = prompt("Change topic of " + this.name + " to:", this.topic.topicText);
  6802.     if(newTopic === null)
  6803.       return;
  6804.  
  6805.     this.session.irc.exec("/TOPIC " + newTopic);
  6806.   },
  6807.   reflow: function() {
  6808.     ui.reflow();
  6809.   },
  6810.   onResize: function() {
  6811.     if(this.scrolleddown) {
  6812.       if(Browser.Engine.trident) {
  6813.         this.scrollToBottom.delay(5, this);
  6814.       } else {
  6815.         this.scrollToBottom();
  6816.       }
  6817.     } else if($defined(this.scrollpos)) {
  6818.       if(Browser.Engine.trident) {
  6819.         this.getScrollParent().scrollTo(this.scrollpos.x, this.scrollpos.y);
  6820.       } else {
  6821.         this.getScrollParent().scrollTo.delay(5, this, [this.scrollpos.x, this.scrollpos.y]);
  6822.       }
  6823.     }
  6824.   },
  6825.   createMenu: function(nick, parent) {
  6826.     var e = new Element("div");
  6827.     parent.appendChild(e);
  6828.     e.addClass("menu");
  6829.  
  6830.     var nickArray = [nick];
  6831.     qwebirc.ui.MENU_ITEMS().forEach(function(x) {
  6832.       if(!x.predicate || x.predicate !== true && !x.predicate.apply(this, nickArray))
  6833.         return;
  6834.  
  6835.       var e2 = new Element("a");
  6836.       e.appendChild(e2);
  6837.  
  6838.       e2.href = "#";
  6839.       e2.set("text", "- " + x.text);
  6840.  
  6841.       e2.addEvent("focus", function() { this.blur() }.bind(e2));
  6842.       e2.addEvent("click", function(ev) { new Event(ev.stop()); this.menuClick(x.fn); }.bind(this));
  6843.     }.bind(this));
  6844.     return e;
  6845.   },
  6846.   menuClick: function(fn) {
  6847.     /*
  6848.     this.prevNick.removeChild(this.prevNick.menu);
  6849.     this.prevNick.menu = null;
  6850.     */
  6851.     fn.bind(this)(this.prevNick.realNick);
  6852.     this.removePrevMenu();
  6853.   },
  6854.   moveMenuClass: function() {
  6855.     if(!this.prevNick)
  6856.       return;
  6857.     if(this.nicklist.firstChild == this.prevNick) {
  6858.       this.prevNick.removeClass("selected-middle");
  6859.     } else {
  6860.       this.prevNick.addClass("selected-middle");
  6861.     }
  6862.   },
  6863.   removePrevMenu: function() {
  6864.     if(!this.prevNick)
  6865.       return;
  6866.  
  6867.     this.prevNick.removeClass("selected");
  6868.     this.prevNick.removeClass("selected-middle");
  6869.     if(this.prevNick.menu)
  6870.       this.prevNick.removeChild(this.prevNick.menu);
  6871.     this.prevNick = null;
  6872.   },
  6873.   nickListAdd: function(nick, position) {
  6874.     var realNick = this.session.irc.stripPrefix(nick);
  6875.  
  6876.     var e = new Element("a");
  6877.     qwebirc.ui.insertAt(position, this.nicklist, e);
  6878.  
  6879.     e.href = "#";
  6880.     var span = new Element("span");
  6881.     if(conf.ui.nick_colors) {
  6882.       var colour = realNick.toHSBColour(this.session);
  6883.       if($defined(colour))
  6884.         span.setStyle("color", colour.rgbToHex());
  6885.     }
  6886.     span.set("text", nick);
  6887.     e.appendChild(span);
  6888.  
  6889.     e.realNick = realNick;
  6890.  
  6891.     e.addEvent("click", function(x) {
  6892.       if(this.prevNick == e) {
  6893.         this.removePrevMenu();
  6894.         return;
  6895.       }
  6896.  
  6897.       this.removePrevMenu();
  6898.       this.prevNick = e;
  6899.       e.addClass("selected");
  6900.       this.moveMenuClass();
  6901.       e.menu = this.createMenu(e.realNick, e);
  6902.       new Event(x).stop();
  6903.     }.bind(this));
  6904.  
  6905.     e.addEvent("focus", function() { this.blur() }.bind(e));
  6906.     this.moveMenuClass();
  6907.     return e;
  6908.   },
  6909.   nickListRemove: function(nick, stored) {
  6910.     this.nicklist.removeChild(stored);
  6911.     this.moveMenuClass();
  6912.   },
  6913.   updateTopic: function(topic) {
  6914.     var t = this.topic;
  6915.  
  6916.     while(t.firstChild)
  6917.       t.removeChild(t.firstChild);
  6918.  
  6919.     if(topic) {
  6920.       t.topicText = topic;
  6921.       this.parent(topic, t);
  6922.     } else {
  6923.       t.topicText = topic;
  6924.       var e = new Element("div");
  6925.       e.set("text", "(no topic set)");
  6926.       e.addClass("emptytopic");
  6927.       t.appendChild(e);
  6928.     }
  6929.     this.reflow();
  6930.   },
  6931.   select: function() {
  6932.     var inputVisible = this.type != qwebirc.ui.WINDOW_CUSTOM;
  6933.  
  6934.     this.tab.removeClass("tab-unselected");
  6935.     this.tab.addClass("tab-selected");
  6936.  
  6937.     ui.setLines(this.lines);
  6938.     ui.setChannelItems(this.nicklist, this.topic);
  6939.     ui.qjsui.showInput(inputVisible);
  6940.     ui.qjsui.showChannel($defined(this.nicklist));
  6941.  
  6942.     this.reflow();
  6943.  
  6944.     this.parent();
  6945.  
  6946.     if(inputVisible)
  6947.       ui.inputbox.focus();
  6948.  
  6949.     if(this.type == qwebirc.ui.WINDOW_CHANNEL && this.nicksColoured != conf.ui.nick_colors) {
  6950.       this.nicksColoured = conf.ui.nick_colors;
  6951.  
  6952.       var nodes = this.nicklist.childNodes;
  6953.       if(conf.ui.nick_colors) {
  6954.         for(var i=0;i<nodes.length;i++) {
  6955.           var e = nodes[i], span = e.firstChild;
  6956.           var colour = e.realNick.toHSBColour(this.session);
  6957.           if($defined(colour))
  6958.             span.setStyle("color", colour.rgbToHex());
  6959.         };
  6960.       } else {
  6961.         for(var i=0;i<nodes.length;i++) {
  6962.           var span = nodes[i].firstChild;
  6963.           span.setStyle("color", null);
  6964.         };
  6965.       }
  6966.     }
  6967.   },
  6968.   deselect: function() {
  6969.     this.parent();
  6970.  
  6971.     this.tab.removeClass("tab-selected");
  6972.     this.tab.addClass("tab-unselected");
  6973.   },
  6974.   addLine: function(type, line, colourClass) {
  6975.     var e = new Element("div");
  6976.  
  6977.     if(colourClass) {
  6978.       e.addClass(colourClass);
  6979.     } else if(this.lastcolour) {
  6980.       e.addClass("linestyle1");
  6981.     } else {
  6982.       e.addClass("linestyle2");
  6983.     }
  6984.     this.lastcolour = !this.lastcolour;
  6985.  
  6986.     this.parent(type, line, colourClass, e);
  6987.   },
  6988.   setHilighted: function(state) {
  6989.     var laststate = this.hilighted;
  6990.  
  6991.     this.parent(state);
  6992.  
  6993.     if(state == laststate)
  6994.       return;
  6995.  
  6996.     this.tab.removeClass("tab-hilight-activity");
  6997.     this.tab.removeClass("tab-hilight-us");
  6998.     this.tab.removeClass("tab-hilight-speech");
  6999.  
  7000.     switch(this.hilighted) {
  7001.       case qwebirc.ui.HILIGHT_US:
  7002.         this.tab.addClass("tab-hilight-us");
  7003.         break;
  7004.       case qwebirc.ui.HILIGHT_SPEECH:
  7005.         this.tab.addClass("tab-hilight-speech");
  7006.         break;
  7007.       case qwebirc.ui.HILIGHT_ACTIVITY:
  7008.         this.tab.addClass("tab-hilight-activity");
  7009.         break;
  7010.     }
  7011.   }
  7012. });
Add Comment
Please, Sign In to add comment