Logo accreditation
[KiwiIRC.git] / js / front.js
index 3989776ba4a1d519c4f56e043f0283c155edd171..05591769935d26b0dcef9e765b17054d0c9febd3 100644 (file)
@@ -1,50 +1,60 @@
-/*jslint undef: true, browser: true, continue: true, sloppy: true, evil: true, forin: true, newcap: false, plusplus: true, maxerr: 50, indent: 4 */
-/*global gateway, io, $, iScroll, agent, touchscreen*/
-var front = {
+/*jslint regexp: true, nomen: true, devel: true, undef: true, browser: true, continue: true, sloppy: true, forin: true, newcap: true, plusplus: true, maxerr: 50, indent: 4 */
+/*global gateway, io, $, iScroll, agent, touchscreen, init_data, plugs, plugins, registerTouches, randomString */
+kiwi.front = {
     revision: 38,
     
     cur_channel: '',
     windows: {},
     tabviews: {},
+    utilityviews: {},
     boxes: {},
     
     buffer: [],
     buffer_pos: 0,
 
+    cache: {},
+    
     original_topic: '',
     
     init: function () {
-        var about_info, supportsOrientationChange, orientationEvent;
-        gateway.nick = 'kiwi_' + Math.ceil(100 * Math.random()) + Math.ceil(100 * Math.random());
-        gateway.session_id = null;
+        /*global Box, touch_scroll:true */
+        var about_info, supportsOrientationChange, orientationEvent, scroll_opts;
+        kiwi.gateway.nick = 'kiwi_' + Math.ceil(100 * Math.random()) + Math.ceil(100 * Math.random());
+        kiwi.gateway.session_id = null;
         
-        $(gateway).bind("onmsg", front.onMsg);
-        $(gateway).bind("onnotice", front.onNotice);
-        $(gateway).bind("onaction", front.onAction);
-        $(gateway).bind("onmotd", front.onMOTD);
-        $(gateway).bind("onoptions", front.onOptions);
-        $(gateway).bind("onconnect", front.onConnect);
-        $(gateway).bind("ondisconnect", front.onDisconnect);
-        $(gateway).bind("onnick", front.onNick);
-        $(gateway).bind("onuserlist", front.onUserList);
-        $(gateway).bind("onuserlist_end", front.onUserListEnd);
-        $(gateway).bind("onjoin", front.onJoin);
-        $(gateway).bind("ontopic", front.onTopic);
-        $(gateway).bind("onpart", front.onPart);
-        $(gateway).bind("onkick", front.onKick);
-        $(gateway).bind("onquit", front.onQuit);
-        $(gateway).bind("onwhois", front.onWhois);
-        $(gateway).bind("onsync", front.onSync);
-        $(gateway).bind("onchannel_redirect", front.onChannelRedirect);
-        $(gateway).bind("ondebug", front.onDebug);
-        $(gateway).bind("onctcp_request", front.onCTCPRequest);
-        $(gateway).bind("onctcp_response", front.onCTCPResponse);
-        $(gateway).bind("onirc_error", front.onIRCError);
+        $(kiwi.gateway).bind("onmsg", kiwi.front.onMsg);
+        $(kiwi.gateway).bind("onnotice", kiwi.front.onNotice);
+        $(kiwi.gateway).bind("onaction", kiwi.front.onAction);
+        $(kiwi.gateway).bind("onmotd", kiwi.front.onMOTD);
+        $(kiwi.gateway).bind("onoptions", kiwi.front.onOptions);
+        $(kiwi.gateway).bind("onconnect", kiwi.front.onConnect);
+        $(kiwi.gateway).bind("onconnect_fail", kiwi.front.onConnectFail);
+        $(kiwi.gateway).bind("ondisconnect", kiwi.front.onDisconnect);
+        $(kiwi.gateway).bind("onnick", kiwi.front.onNick);
+        $(kiwi.gateway).bind("onuserlist", kiwi.front.onUserList);
+        $(kiwi.gateway).bind("onuserlist_end", kiwi.front.onUserListEnd);
+        $(kiwi.gateway).bind("onlist_start", kiwi.front.onChannelListStart);
+        $(kiwi.gateway).bind("onlist_channel", kiwi.front.onChannelList);
+        $(kiwi.gateway).bind("onlist_end", kiwi.front.onChannelListEnd);
+        $(kiwi.gateway).bind("onjoin", kiwi.front.onJoin);
+        $(kiwi.gateway).bind("ontopic", kiwi.front.onTopic);
+        $(kiwi.gateway).bind("onpart", kiwi.front.onPart);
+        $(kiwi.gateway).bind("onkick", kiwi.front.onKick);
+        $(kiwi.gateway).bind("onquit", kiwi.front.onQuit);
+        $(kiwi.gateway).bind("onmode", kiwi.front.onMode);
+        $(kiwi.gateway).bind("onwhois", kiwi.front.onWhois);
+        $(kiwi.gateway).bind("onsync", kiwi.front.onSync);
+        $(kiwi.gateway).bind("onchannel_redirect", kiwi.front.onChannelRedirect);
+        $(kiwi.gateway).bind("ondebug", kiwi.front.onDebug);
+        $(kiwi.gateway).bind("onctcp_request", kiwi.front.onCTCPRequest);
+        $(kiwi.gateway).bind("onctcp_response", kiwi.front.onCTCPResponse);
+        $(kiwi.gateway).bind("onirc_error", kiwi.front.onIRCError);
+        $(kiwi.gateway).bind("onkiwi", kiwi.front.onKiwi);
         
         this.buffer = [];
         
         // Build the about box
-        front.boxes.about = new Box("about");
+        kiwi.front.boxes.about = new Box("about");
         about_info = 'UI adapted for ' + agent;
         if (touchscreen) {
             about_info += ' touchscreen ';
@@ -52,11 +62,11 @@ var front = {
         about_info += 'usage';
         $('#tmpl_about_box').tmpl({
             about: about_info,
-            front_revision: front.revision,
-            gateway_revision: gateway.revision
-        }).appendTo(front.boxes.about.content);
+            front_revision: kiwi.front.revision,
+            gateway_revision: kiwi.gateway.revision
+        }).appendTo(kiwi.front.boxes.about.content);
 
-        //$(window).bind("beforeunload", function(){ gateway.quit(); });
+        //$(window).bind("beforeunload", function(){ kiwi.gateway.quit(); });
         
         if (touchscreen) {
             $('#kiwi').addClass('touchscreen');
@@ -66,12 +76,34 @@ var front = {
             touch_scroll = new iScroll('windows', scroll_opts);
         }
 
-        front.registerKeys();
+        kiwi.front.registerKeys();
         
-        $('#kiwi .cur_topic').resize(front.doLayoutSize);
+        $('#kiwi .toolbars').resize(kiwi.front.doLayoutSize);
+        $(window).resize(kiwi.front.doLayoutSize);
+
+        // Add the resizer for the userlist
+        $('<div id="nicklist_resize" style="position:absolute; cursor:w-resize; width:5px;"></div>').appendTo('#kiwi');
+        $('#nicklist_resize').draggable({axis: "x", drag: function () {
+            var t = $(this),
+                new_width = $(document).width() - parseInt(t.css('left'), 10);
+            
+            new_width = new_width - parseInt($('#kiwi .userlist').css('margin-left'), 10);
+            new_width = new_width - parseInt($('#kiwi .userlist').css('margin-right'), 10);
+
+            // Make sure we don't remove the userlist alltogether
+            if (new_width < 20) {
+                $(this).data('draggable').offset.click.left = 10;
+                console.log('whoaa');
+            }
+
+            kiwi.front.cur_channel.setUserlistWidth(new_width);
+        }});
+
 
         $('#kiwi .formconnectwindow').submit(function () {
             var netsel = $('#kiwi .formconnectwindow .network'),
+                netport = $('#kiwi .formconnectwindow .port'),
+                netssl = $('#kiwi .formconnectwindow .ssl'),
                 nick = $('#kiwi .formconnectwindow .nick'),
                 tmp;
             
@@ -82,15 +114,18 @@ var front = {
             }
             
             tmp = nick.val().split(' ');
-            gateway.nick = tmp[0];
-            front.doLayout();
+            kiwi.gateway.nick = tmp[0];
+
+            init_data.channel = $('#channel').val();
+
+            kiwi.front.doLayout();
             try {
-                front.run('/connect ' + netsel.val());
+                kiwi.front.run('/connect ' + netsel.val() + ' ' + netport.val() + ' ' + (netssl.attr('checked') ? 'true' : ''));
             } catch (e) {
-                alert(e);
+                console.log(e);
             }
             
-            $('#kiwi .connectwindow').slideUp();
+            $('#kiwi .connectwindow').slideUp('', kiwi.front.barsShow);
             $('#windows').click(function () { $('#kiwi_msginput').focus(); });
 
             return false;
@@ -98,19 +133,32 @@ var front = {
 
         supportsOrientationChange = (typeof window.onorientationchange !==  undefined);
         orientationEvent = supportsOrientationChange ? "orientationchange" : "resize";
-        window.addEventListener(orientationEvent, front.doLayoutSize, false);
-        //$('#kiwi').bind("resize", front.doLayoutSize, false);
+        if (window.addEventListener) {
+            window.addEventListener(orientationEvent, kiwi.front.doLayoutSize, false);
+        } else {
+            // < IE9
+            window.attachEvent(orientationEvent, kiwi.front.doLayoutSize, false);
+        }
+        //$('#kiwi').bind("resize", kiwi.front.doLayoutSize, false);
 
-        front.doLayout();
-        //front.windowAdd('server');
-        front.tabviewAdd('server');
+        kiwi.front.doLayout();
+        kiwi.front.barsHide();
+        
+        kiwi.front.tabviewAdd('server');
+        kiwi.front.tabviews.server.userlist_width = 0; // Disable the userlist
         
         // Any pre-defined nick?
-        if (typeof init_data.nick === "string") {
+        if (typeof window.init_data.nick === "string") {
             $('#kiwi .formconnectwindow .nick').val(init_data.nick);
         }
+
+        // Any pre-defined channels?
+        if (typeof window.init_data.channel === 'string') {
+            $('#channel').val(init_data.channel);
+        }
         
-        //gateway.session_id = 'testses';
+        // Fix for Opera inserting a spurious <br/>
+        $('#kiwi .cur_topic br').remove();
         
         $('#kiwi .cur_topic').keydown(function (e) {
             if (e.which === 13) {
@@ -121,7 +169,7 @@ var front = {
             } else if (e.which === 27) {
                 // escape
                 e.preventDefault();
-                $(this).text(front.original_topic);
+                $(this).text(kiwi.front.original_topic);
                 $('#kiwi_msginput').focus();
             }
         });
@@ -134,25 +182,38 @@ var front = {
             } else if (e.keyCode === 27) {
                 // escape
                 e.preventDefault();
-                $(this).text(front.original_topic);
+                $(this).text(kiwi.front.original_topic);
             }
         });*/
         $('.cur_topic').live('change', function () {
             var chan, text;
             text = $(this).text();
-            if (text !== front.original_topic) {
-                chan = front.cur_channel.name;
-                gateway.setTopic(chan, text);
+            if (text !== kiwi.front.original_topic) {
+                chan = kiwi.front.cur_channel.name;
+                kiwi.gateway.setTopic(chan, text);
             }
         });
+
+
+        $('#windows a.chan').live('click', function () {
+            kiwi.front.joinChannel($(this).text());
+            return false;
+        });
         
-        //gateway.start();
-        //front.sync();
+        kiwi.data.set('chanList', []);
+
+        (function () {
+            var i;
+            for (i in plugins) {
+                kiwi.plugs.loadPlugin(plugins[i]);
+            }
+        }());
     },
     
     doLayoutSize: function () {
-        var kiwi, ct, ul, n_top, n_bottom;
+        var kiwi, toolbars, ul, n_top, n_bottom, nl;
         kiwi = $('#kiwi');
+
         if (kiwi.width() < 330 && !kiwi.hasClass('small_kiwi')) {
             console.log("switching to small kiwi");
             kiwi.removeClass('large_kiwi');
@@ -162,25 +223,22 @@ var front = {
             kiwi.addClass('large_kiwi');
         }
 
-        ct = $('#kiwi .cur_topic');
+        toolbars = $('#kiwi .cur_topic');
         ul = $('#kiwi .userlist');
 
-        n_top = parseInt(ct.offset().top, 10) + parseInt(ct.height(), 10);
-        n_top = n_top + parseInt(ct.css('border-top-width').replace('px', ''), 10);
-        n_top = n_top + parseInt(ct.css('border-bottom-width').replace('px', ''), 10);
-        n_top = n_top + parseInt(ct.css('padding-top').replace('px', ''), 10);
-        n_top = n_top + parseInt(ct.css('padding-bottom').replace('px', ''), 10);
-        n_top += 1; // Dunno why this is needed.. but it's always 1 px out :/
-
+        n_top = parseInt(toolbars.offset().top, 10) + parseInt(toolbars.outerHeight(true), 10);
         n_bottom = $(document).height() - parseInt($('#kiwi .control').offset().top, 10);
 
         $('#kiwi .windows').css({top: n_top + 'px', bottom: n_bottom + 'px'});
-        $('#kiwi .userlist').css({top: n_top + 'px', bottom: n_bottom + 'px'});
+        ul.css({top: n_top + 'px', bottom: n_bottom + 'px'});
+
+        nl = $('#nicklist_resize');
+        nl.css({top: n_top + 'px', bottom: n_bottom + 'px', left: $(document).width() - ul.outerWidth(true)});
     },
 
 
     doLayout: function () {
-        $('#kiwi .msginput .nick a').text(gateway.nick);
+        $('#kiwi .msginput .nick a').text(kiwi.gateway.nick);
         $('#kiwi_msginput').val(' ');
         $('#kiwi_msginput').focus();
     },
@@ -188,110 +246,151 @@ var front = {
     
     joinChannel: function (chan_name) {
         var chans = chan_name.split(','),
-            i;
+            i,
+            chan;
         for (i in chans) {
             chan = chans[i];
-            if (front.tabviews[chan.toLowerCase()] === undefined || (front.tabviews[chan.toLowerCase()] !== undefined && front.tabviews[chan.toLowerCase()].safe_to_close === true)) {
-                gateway.join(chan);
-                front.tabviewAdd(chan);
+            if (kiwi.front.tabviews[chan.toLowerCase()] === undefined || (kiwi.front.tabviews[chan.toLowerCase()] !== undefined && kiwi.front.tabviews[chan.toLowerCase()].safe_to_close === true)) {
+                kiwi.gateway.join(chan);
+                kiwi.front.tabviewAdd(chan);
             } else {
-                front.tabviews[chan.toLowerCase()].show();
+                kiwi.front.tabviews[chan.toLowerCase()].show();
             }
         }
     },
     
     
     run: function (msg) {
-        var parts, dest, t, pos, textRange, d;
+        var parts, dest, t, pos, textRange, d, plugin_event, msg_sliced;
+        
+        // Run through any plugins
+        plugin_event = {command: msg};
+        plugin_event = kiwi.plugs.run('command_run', plugin_event);
+        if (!plugin_event || typeof plugin_event.command === 'undefined') {
+            return;
+        }
+
+        // Update msg if it's been changed by any plugins
+        msg = plugin_event.command.toString();
+
         console.log("running " + msg);
         if (msg.substring(0, 1) === '/') {
             parts = msg.split(' ');
             switch (parts[0].toLowerCase()) {
             case '/j':
             case '/join':
-                front.joinChannel(parts[1]);
+                kiwi.front.joinChannel(parts[1]);
                 break;
                 
             case '/connect':
             case '/server':
-                if (parts[1] === undefined) {
-                    alert('Usage: /connect servername [port]');
+                if (typeof parts[1] === 'undefined') {
+                    alert('Usage: /connect servername [port] [ssl]');
                     break;
                 }
                 
-                if (parts[2] === undefined) {
+                if (typeof parts[2] === 'undefined') {
                     parts[2] = 6667;
                 }
-                front.cur_channel.addMsg(null, ' ', '=== Connecting to ' + parts[1] + '...', 'status');
-                gateway.connect(parts[1], parts[2], 0);
+                
+                if ((typeof parts[3] === 'undefined') || !parts[3] || (parts[3] === 'false') || (parts[3] === 'no')) {
+                    parts[3] = false;
+                } else {
+                    parts[3] = true;
+                }
+                
+                kiwi.front.cur_channel.addMsg(null, ' ', '=== Connecting to ' + parts[1] + ' on port ' + parts[2] + (parts[3] ? ' using SSL' : '') + '...', 'status');
+                kiwi.gateway.connect(parts[1], parts[2], parts[3]);
                 break;
                 
             case '/nick':
                 console.log("/nick");
                 if (parts[1] === undefined) {
                     console.log("calling show nick");
-                    front.showChangeNick();
+                    kiwi.front.showChangeNick();
                 } else {
                     console.log("sending raw");
-                    gateway.raw(msg.substring(1));
+                    kiwi.gateway.raw(msg.substring(1));
                 }
                 break;
 
             case '/part':
                 if (typeof parts[1] === "undefined") {
-                    if (front.cur_channel.safe_to_close) {
-                        front.cur_channel.close();
+                    if (kiwi.front.cur_channel.safe_to_close) {
+                        kiwi.front.cur_channel.close();
                     } else {
-                        gateway.raw(msg.substring(1) + ' ' + front.cur_channel.name);
+                        kiwi.gateway.raw(msg.substring(1) + ' ' + kiwi.front.cur_channel.name);
                     }
                 } else {
-                    gateway.raw(msg.substring(1));
+                    kiwi.gateway.raw(msg.substring(1));
                 }
                 break;
                 
             case '/names':
                 if (typeof parts[1] !== "undefined") {
-                    gateway.raw(msg.substring(1));
+                    kiwi.gateway.raw(msg.substring(1));
                 }
                 break;
                 
             case '/debug':
-                gateway.debug();
+                kiwi.gateway.debug();
                 break;
                 
             case '/q':
             case '/query':
                 if (typeof parts[1] !== "undefined") {
-                    front.tabviewAdd(parts[1]);
+                    kiwi.front.tabviewAdd(parts[1]);
                 }
                 break;
-                
+
+            
+            case '/m':
+            case '/msg':
+                if (typeof parts[1] !== "undefined") {
+                    msg_sliced = msg.split(' ').slice(2).join(' ');
+                    kiwi.gateway.msg(parts[1], msg_sliced);
+
+                    if (!kiwi.front.tabviewExists(parts[1])) {
+                        kiwi.front.tabviewAdd(parts[1]);
+                    }
+                    kiwi.front.tabviews[parts[1].toLowerCase()].addMsg(null, kiwi.gateway.nick, msg_sliced);
+                }
+                break;
+            
+            case '/k':
+            case '/kick':
+                if (typeof parts[1] === 'undefined') {
+                    return;
+                }
+                kiwi.gateway.raw('KICK ' + kiwi.front.cur_channel.name + ' ' + msg.split(' ', 2)[1]);
+                break;
+
             case '/quote':
-                gateway.raw(msg.replace(/^\/quote /i, ''));
+                kiwi.gateway.raw(msg.replace(/^\/quote /i, ''));
                 break;
                 
             case '/me':
-                gateway.action(front.cur_channel.name, msg.substring(4));
-                //front.tabviews[destination.toLowerCase()].addMsg(null, ' ', '* '+data.nick+' '+data.msg, 'color:green;');
-                front.cur_channel.addMsg(null, ' ', '* ' + gateway.nick + ' ' + msg.substring(4), 'action', 'color:#555;');
+                kiwi.gateway.action(kiwi.front.cur_channel.name, msg.substring(4));
+                //kiwi.front.tabviews[destination.toLowerCase()].addMsg(null, ' ', '* '+data.nick+' '+data.msg, 'color:green;');
+                kiwi.front.cur_channel.addMsg(null, ' ', '* ' + kiwi.gateway.nick + ' ' + msg.substring(4), 'action', 'color:#555;');
                 break;
 
             case '/notice':
                 dest = parts[1];
                 msg = parts.slice(2).join(' ');
 
-                gateway.notice(dest, msg);
-                this.onNotice({}, {nick: gateway.nick, channel: dest, msg: msg});
+                kiwi.gateway.notice(dest, msg);
+                this.onNotice({}, {nick: kiwi.gateway.nick, channel: dest, msg: msg});
                 break;
 
             case '/win':
                 if (parts[1] !== undefined) {
-                    front.windowsShow(parseInt(parts[1], 10));
+                    kiwi.front.windowsShow(parseInt(parts[1], 10));
                 }
                 break;
 
             case '/quit':
-                gateway.quit(msg.split(" ", 2)[1]);
+                kiwi.gateway.quit(parts.slice(1).join(' '));
                 break;
 
             case '/topic':
@@ -308,13 +407,18 @@ var front = {
                         t.setSelectionRange(pos, pos);
                     }
                 } else {
-                    gateway.setTopic(front.cur_channel.name, msg.split(' ', 2)[1]);
-                    //gateway.raw('TOPIC ' + front.cur_channel.name + ' :' + msg.split(' ', 2)[1]);
+                    kiwi.gateway.setTopic(kiwi.front.cur_channel.name, msg.split(' ', 2)[1]);
+                    //kiwi.gateway.raw('TOPIC ' + kiwi.front.cur_channel.name + ' :' + msg.split(' ', 2)[1]);
                 }
                 break;
+
+            case '/kiwi':
+                kiwi.gateway.kiwi(kiwi.front.cur_channel.name, msg.substring(6));
+                break;
+
             default:
-                //front.cur_channel.addMsg(null, ' ', '--> Invalid command: '+parts[0].substring(1));
-                gateway.raw(msg.substring(1));
+                //kiwi.front.cur_channel.addMsg(null, ' ', '--> Invalid command: '+parts[0].substring(1));
+                kiwi.gateway.raw(msg.substring(1)); 
             }
 
         } else {
@@ -322,70 +426,76 @@ var front = {
             if (msg.trim() === '') {
                 return;
             }
-            if (front.cur_channel.name !== 'server') {
-                gateway.msg(front.cur_channel.name, msg);
+            if (kiwi.front.cur_channel.name !== 'server') {
+                kiwi.gateway.msg(kiwi.front.cur_channel.name, msg);
                 d = new Date();
                 d = d.getHours() + ":" + d.getMinutes();
-                //front.addMsg(d, gateway.nick, msg);
-                front.cur_channel.addMsg(null, gateway.nick, msg);
+                //kiwi.front.addMsg(d, kiwi.gateway.nick, msg);
+                kiwi.front.cur_channel.addMsg(null, kiwi.gateway.nick, msg);
             }
         }
     },
     
     
     onMsg: function (e, data) {
-        var destination;
+        var destination, plugin_event;
         // Is this message from a user?
-        if (data.channel === gateway.nick) {
+        if (data.channel === kiwi.gateway.nick) {
             destination = data.nick.toLowerCase();
         } else {
             destination = data.channel.toLowerCase();    
         }
         
-        if (!front.tabviewExists(destination)) {
-            front.tabviewAdd(destination);
+        plugin_event = {nick: data.nick, msg: data.msg, destination: destination};
+        plugin_event = kiwi.plugs.run('msg_recieved', plugin_event);
+        if (!plugin_event) {
+            return;
+        }
+
+        if (!kiwi.front.tabviewExists(plugin_event.destination)) {
+            kiwi.front.tabviewAdd(plugin_event.destination);
         }
-        front.tabviews[destination].addMsg(null, data.nick, data.msg);
+        kiwi.front.tabviews[plugin_event.destination].addMsg(null, plugin_event.nick, plugin_event.msg);
     },
     
     onDebug: function (e, data) {
-        if (!front.tabviewExists('kiwi_debug')) {
-            front.tabviewAdd('kiwi_debug');
+        if (!kiwi.front.tabviewExists('kiwi_debug')) {
+            kiwi.front.tabviewAdd('kiwi_debug');
         }
-        front.tabviews.kiwi_debug.addMsg(null, ' ', data.msg);
+        kiwi.front.tabviews.kiwi_debug.addMsg(null, ' ', data.msg);
     },
     
     onAction: function (e, data) {
         var destination;
         // Is this message from a user?
-        if (data.channel === gateway.nick) {
+        if (data.channel === kiwi.gateway.nick) {
             destination = data.nick;
         } else {
             destination = data.channel;    
         }
         
-        if (!front.tabviewExists(destination)) {
-            front.tabviewAdd(destination);
+        if (!kiwi.front.tabviewExists(destination)) {
+            kiwi.front.tabviewAdd(destination);
         }
-        front.tabviews[destination.toLowerCase()].addMsg(null, ' ', '* ' + data.nick + ' ' + data.msg, 'action', 'color:#555;');
+        kiwi.front.tabviews[destination.toLowerCase()].addMsg(null, ' ', '* ' + data.nick + ' ' + data.msg, 'action', 'color:#555;');
     },
     
     onTopic: function (e, data) {
-        if (front.tabviewExists(data.channel)) {
-            front.tabviews[data.channel.toLowerCase()].changeTopic(data.topic);            
+        if (kiwi.front.tabviewExists(data.channel)) {
+            kiwi.front.tabviews[data.channel.toLowerCase()].changeTopic(data.topic);            
         }
     },
     
     onNotice: function (e, data) {
-        var nick = (data.nick === undefined || data.nick === '') ? '' : '[' + data.nick + ']';
-        if (data.channel !== undefined) {
-            if (front.tabviewExists(data.channel)) {
-                front.tabviews[data.channel.toLowerCase()].addMsg(null, nick, data.msg, 'notice');
-            } else {
-                front.tabviews.server.addMsg(null, nick, data.msg, 'notice');
-            }
+        var nick = (data.nick === undefined) ? '' : data.nick,
+            enick = '[' + nick + ']';
+
+        if (kiwi.front.tabviewExists(data.target)) {
+            kiwi.front.tabviews[data.target.toLowerCase()].addMsg(null, enick, data.msg, 'notice');
+        } else if (kiwi.front.tabviewExists(nick)) {
+            kiwi.front.tabviews[nick.toLowerCase()].addMsg(null, enick, data.msg, 'notice');
         } else {
-            front.tabviews.server.addMsg(null, nick, data.msg, 'notice');
+            kiwi.front.tabviews.server.addMsg(null, enick, data.msg, 'notice');
         }
     },
     
@@ -396,61 +506,109 @@ var front = {
             if (typeof msg[1] === 'undefined') {
                 msg[1] = '';
             }
-            gateway.notice(data.nick, String.fromCharCode(1) + 'PING ' + msg[1] + String.fromCharCode(1));
+            kiwi.gateway.notice(data.nick, String.fromCharCode(1) + 'PING ' + msg[1] + String.fromCharCode(1));
             break;
         case 'TIME':
-            gateway.notice(data.nick, String.fromCharCode(1) + 'TIME ' + (new Date()).toLocaleString() + String.fromCharCode(1));
+            kiwi.gateway.notice(data.nick, String.fromCharCode(1) + 'TIME ' + (new Date()).toLocaleString() + String.fromCharCode(1));
             break;
         }
-        front.tabviews.server.addMsg(null, 'CTCP [' + data.nick + ']', data.msg, 'ctcp');
+        kiwi.front.tabviews.server.addMsg(null, 'CTCP [' + data.nick + ']', data.msg, 'ctcp');
     },
     
     onCTCPResponse: function (e, data) {
     },
+
+    onKiwi: function (e, data) {
+        //console.log(data);
+    },
     
     onConnect: function (e, data) {
         if (data.connected) {
-            front.tabviews.server.addMsg(null, ' ', '=== Connected OK :)', 'status');
+            if (kiwi.gateway.nick !== data.nick) {
+                kiwi.gateway.nick = data.nick;
+                kiwi.front.doLayout();
+            }
+
+            kiwi.front.tabviews.server.addMsg(null, ' ', '=== Connected OK :)', 'status');
             if (typeof init_data.channel === "string") {
-                front.joinChannel(init_data.channel);
+                kiwi.front.joinChannel(init_data.channel);
             }
+            kiwi.plugs.run('connect', {success: true});
         } else {
-            front.tabviews.server.addMsg(null, ' ', '=== Failed to connect :(', 'status');
+            kiwi.front.tabviews.server.addMsg(null, ' ', '=== Failed to connect :(', 'status');
+            kiwi.plugs.run('connect', {success: false});
         }
     },
+    onConnectFail: function (e, data) {
+        var reason = (typeof data.reason === 'string') ? data.reason : '';
+        kiwi.front.tabviews.server.addMsg(null, '', 'There\'s a problem connecting! (' + reason + ')', 'error');
+        kiwi.plugs.run('connect', {success: false});
+    },
     onDisconnect: function (e, data) {
         var tab;
-        for (tab in front.tabviews) {
-            front.tabviews[tab].addMsg(null, '', 'Disconnected from server!', 'error');
+        for (tab in kiwi.front.tabviews) {
+            kiwi.front.tabviews[tab].addMsg(null, '', 'Disconnected from server!', 'error');
         }
+        kiwi.plugs.run('disconnect', {success: false});
     },
     onOptions: function (e, data) {
-        if (typeof gateway.network_name === "string" && gateway.network_name !== "") {
-            front.tabviews.server.tab.text(gateway.network_name);
+        if (typeof kiwi.gateway.network_name === "string" && kiwi.gateway.network_name !== "") {
+            kiwi.front.tabviews.server.tab.text(kiwi.gateway.network_name);
         }
     },
     onMOTD: function (e, data) {
-        front.tabviews.server.addMsg(null, data.server, data.msg, 'motd');
+        kiwi.front.tabviews.server.addMsg(null, data.server, data.msg, 'motd');
     },
     onWhois: function (e, data) {
         var d;
         if (data.msg) {
-            front.cur_channel.addMsg(null, data.nick, data.msg, 'whois');
+            kiwi.front.cur_channel.addMsg(null, data.nick, data.msg, 'whois');
         } else if (data.logon) {
             d = new Date();
             d.setTime(data.logon * 1000);
             d = d.toLocaleString();
-            front.cur_channel.addMsg(null, data.nick, 'idle for ' + data.idle + ' second' + ((data.idle !== 1) ? 's' : '') + ', signed on ' + d, 'whois');
+            kiwi.front.cur_channel.addMsg(null, data.nick, 'idle for ' + data.idle + ' second' + ((data.idle !== 1) ? 's' : '') + ', signed on ' + d, 'whois');
         } else {
-            front.cur_channel.addMsg(null, data.nick, 'idle for ' + data.idle + ' seconds', 'whois');
+            kiwi.front.cur_channel.addMsg(null, data.nick, 'idle for ' + data.idle + ' seconds', 'whois');
+        }
+    },
+    onMode: function (e, data) {
+        var i, new_nick_text;
+        
+        // TODO: Store the modes in the elements data, then work out the current
+        // mode symbol from the highest mode. Eg. -h may leave +o from previous modes; It
+        // doesn't simply clear it! ~Darren
+        if (typeof data.channel === 'string' && typeof data.effected_nick === 'string') {
+            kiwi.front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '[' + data.mode + '] ' + data.effected_nick + ' by ' + data.nick, 'mode', '');
+            kiwi.front.tabviews[data.channel.toLowerCase()].userlist.children().each(function () {
+                if (kiwi.front.nickStripPrefix($(this).text()) === data.effected_nick) {
+
+                    if (data.mode.split('')[0] === '+') {
+                        for (i = 0; i < kiwi.gateway.user_prefixes.length; i++) {
+                            if (kiwi.gateway.user_prefixes[i].mode === data.mode.split('')[1]) {
+                                new_nick_text = kiwi.gateway.user_prefixes[i].symbol + data.effected_nick;
+                                break;
+                            }
+                        }
+                    } else if (data.mode.split('')[0] === '-') {
+                        new_nick_text = data.effected_nick;
+                    }
+
+                    if (new_nick_text !== '') {
+                        $(this).text(new_nick_text);
+                        return false;
+                    }
+
+                }
+            });
         }
     },
     onUserList: function (e, data) {
         var ul, nick, mode;
-        if (front.tabviews[data.channel.toLowerCase()] === undefined) {
+        if (kiwi.front.tabviews[data.channel.toLowerCase()] === undefined) {
             return;
         }
-        ul = front.tabviews[data.channel.toLowerCase()].userlist;
+        ul = kiwi.front.tabviews[data.channel.toLowerCase()].userlist;
         
         if (!document.userlist_updating) {
             document.userlist_updating = true;
@@ -460,38 +618,100 @@ var front = {
         $.each(data.users, function (i, item) {
             nick = i; //i.match(/^.+!/g);
             mode = item;
-            $('<li><a class="nick" onclick="front.userClick(this);">' + mode + nick + '</a></li>').appendTo(ul);
+            $('<li><a class="nick" onclick="kiwi.front.userClick(this);">' + mode + nick + '</a></li>').appendTo(ul);
         });
         
-        front.tabviews[data.channel.toLowerCase()].userlistSort();
+        kiwi.front.tabviews[data.channel.toLowerCase()].userlistSort();
     },
     onUserListEnd: function (e, data) {
         document.userlist_updating = false;
     },
     
+    onChannelListStart: function (e, data) {
+        var tab, table;
+        
+        tab = new Utilityview('Channel List');
+        tab.div.css('overflow-y', 'scroll');
+        table = $('<table style="margin:1em 2em;"><thead style="font-weight: bold;"><tr><td>Channel Name</td><td>Members</td><td style="padding-left: 2em;">Topic</td></tr></thead><tbody style="vertical-align: top;"></tbody>');
+        tab.div.append(table);
+        
+        kiwi.front.cache.list = {chans: [], tab: tab, table: table,
+            update: function (newChans) {
+                var body = this.table.children('tbody:first').detach(),
+                    chan,
+                    html;
+                
+                html = '';
+                for (chan in newChans) {
+                    this.chans.push(newChans[chan]);
+                    html += newChans[chan].html;
+                }
+                body.append(html);
+                this.table.append(body);
+                
+            },
+            finalise: function () {
+                var body = this.table.children('tbody:first').detach(),
+                    list,
+                    chan;
+                
+                list = $.makeArray($(body).children());
+                
+                for (chan in list) {
+                    list[chan] = $(list[chan]).detach();
+                }
+                
+                list = _.sortBy(list, function (channel) {
+                    return parseInt(channel.children('.num_users').first().text(), 10);
+                }).reverse();
+                
+                for (chan in list) {
+                    body.append(list[chan]);
+                }
+                
+                this.table.append(body);
+                
+            }};
+    },
+    onChannelList: function (e, data) {
+        var chans;
+        console.log(data);
+        data = data.chans;
+        //data = [data];
+        for (chans in data) {
+            data[chans] = {data: data[chans], html: '<tr><td><a class="chan">' + data[chans].channel + '</a></td><td class="num_users" style="text-align: center;">' + data[chans].num_users + '</td><td style="padding-left: 2em;">' + kiwi.front.format(data[chans].topic) + '</td></tr>'};
+        }
+        kiwi.front.cache.list.update(data);
+    },
+    onChannelListEnd: function (e, data) {
+        kiwi.front.cache.list.finalise();
+        kiwi.front.cache.list.tab.show();
+    },
+
+
     onJoin: function (e, data) {
-        if (!front.tabviewExists(data.channel)) {
-            front.tabviewAdd(data.channel.toLowerCase());
+        if (!kiwi.front.tabviewExists(data.channel)) {
+            kiwi.front.tabviewAdd(data.channel.toLowerCase());
         }
         
-        if (data.nick === gateway.nick) {
+        if (data.nick === kiwi.gateway.nick) {
             return; // Not needed as it's already in nicklist
         }
-        front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '--> ' + data.nick + ' has joined', 'action', 'color:#009900;');
-        $('<li><a class="nick" onclick="front.userClick(this);">' + data.nick + '</a></li>').appendTo(front.tabviews[data.channel.toLowerCase()].userlist);
-        front.tabviews[data.channel.toLowerCase()].userlistSort();
+        kiwi.front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '--> ' + data.nick + ' has joined', 'action join', 'color:#009900;');
+        $('<li><a class="nick" onclick="kiwi.front.userClick(this);">' + data.nick + '</a></li>').appendTo(kiwi.front.tabviews[data.channel.toLowerCase()].userlist);
+        kiwi.front.tabviews[data.channel.toLowerCase()].userlistSort();
     },
     onPart: function (e, data) {
-        if (front.tabviewExists(data.channel)) {
+        if (kiwi.front.tabviewExists(data.channel)) {
             // If this is us, close the tabview
-            if (data.nick === gateway.nick) {
-                front.tabviews[data.channel.toLowerCase()].close();
-                front.tabviews.server.show();
+            if (data.nick === kiwi.gateway.nick) {
+                kiwi.front.tabviews[data.channel.toLowerCase()].close();
+                kiwi.front.tabviews.server.show();
                 return;
             }
             
-            front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '<-- ' + data.nick + ' has left (' + data.message + ')', 'action', 'color:#990000;');
-            front.tabviews[data.channel.toLowerCase()].userlist.children().each(function () {
+            kiwi.front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '<-- ' + data.nick + ' has left (' + data.message + ')', 'action part', 'color:#990000;');
+            kiwi.front.tabviews[data.channel.toLowerCase()].userlist.children().each(function () {
                 if ($(this).text() === data.nick) {
                     $(this).remove();
                 }
@@ -499,18 +719,18 @@ var front = {
         }
     },
     onKick: function (e, data) {
-        if (front.tabviewExists(data.channel)) {
+        if (kiwi.front.tabviewExists(data.channel)) {
             // If this is us, close the tabview
-            if (data.kicked === gateway.nick) {
-                //front.tabviews[data.channel.toLowerCase()].close();
-                front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '=== You have been kicked from ' + data.channel + '. ' + data.message, 'status');
-                front.tabviews[data.channel.toLowerCase()].safe_to_close = true;
-                $('li', front.tabviews[data.channel.toLowerCase()].userlist).remove();
+            if (data.kicked === kiwi.gateway.nick) {
+                //kiwi.front.tabviews[data.channel.toLowerCase()].close();
+                kiwi.front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '=== You have been kicked from ' + data.channel + '. ' + data.message, 'status kick');
+                kiwi.front.tabviews[data.channel.toLowerCase()].safe_to_close = true;
+                $('li', kiwi.front.tabviews[data.channel.toLowerCase()].userlist).remove();
                 return;
             }
             
-            front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '<-- ' + data.kicked + ' kicked by ' + data.nick + '(' + data.message + ')', 'action', 'color:#990000;');
-            front.tabviews[data.channel.toLowerCase()].userlist.children().each(function () {
+            kiwi.front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '<-- ' + data.kicked + ' kicked by ' + data.nick + '(' + data.message + ')', 'action kick', 'color:#990000;');
+            kiwi.front.tabviews[data.channel.toLowerCase()].userlist.children().each(function () {
                 if ($(this).text() === data.nick) {
                     $(this).remove();
                 }
@@ -518,38 +738,38 @@ var front = {
         }
     },
     onNick: function (e, data) {
-        if (data.nick === gateway.nick) {
-            gateway.nick = data.newnick;
-            front.doLayout();
+        if (data.nick === kiwi.gateway.nick) {
+            kiwi.gateway.nick = data.newnick;
+            kiwi.front.doLayout();
         }
         
-        $.each(front.tabviews, function (i, item) {
-            $.each(front.tabviews, function (i, item) {
+        $.each(kiwi.front.tabviews, function (i, item) {
+            $.each(kiwi.front.tabviews, function (i, item) {
                 item.changeNick(data.newnick, data.nick);
             });
         });
     },
     onQuit: function (e, data) {
-        $.each(front.tabviews, function (i, item) {
-            $.each(front.tabviews, function (i, item) {
+        $.each(kiwi.front.tabviews, function (i, item) {
+            $.each(kiwi.front.tabviews, function (i, item) {
                 item.userlist.children().each(function () {
                     if ($(this).text() === data.nick) {
                         $(this).remove();
-                        item.addMsg(null, ' ', '<-- ' + data.nick + ' has quit (' + data.message + ')', 'action', 'color:#990000;');
+                        item.addMsg(null, ' ', '<-- ' + data.nick + ' has quit (' + data.message + ')', 'action quit', 'color:#990000;');
                     }
                 });
             });
         });
     },
     onChannelRedirect: function (e, data) {
-        front.tabviews[data.from.toLowerCase()].close();
-        front.tabviewAdd(data.to.toLowerCase());
-        front.tabviews[data.to.toLowerCase()].addMsg(null, ' ', '=== Redirected from ' + data.from, 'action');
+        kiwi.front.tabviews[data.from.toLowerCase()].close();
+        kiwi.front.tabviewAdd(data.to.toLowerCase());
+        kiwi.front.tabviews[data.to.toLowerCase()].addMsg(null, ' ', '=== Redirected from ' + data.from, 'action');
     },
     
     onIRCError: function (e, data) {
         var t_view;
-        if (data.channel !== undefined && front.tabviewExists(data.channel)) {
+        if (data.channel !== undefined && kiwi.front.tabviewExists(data.channel)) {
             t_view = data.channel;
         } else {
             t_view = 'server';
@@ -557,48 +777,59 @@ var front = {
 
         switch (data.error) {
         case 'banned_from_channel':
-            front.tabviews[t_view].addMsg(null, ' ', '=== You are banned from ' + data.channel + '. ' + data.reason, 'status');
+            kiwi.front.tabviews[t_view].addMsg(null, ' ', '=== You are banned from ' + data.channel + '. ' + data.reason, 'status');
             if (t_view !== 'server') {
-                front.tabviews[t_view].safe_to_close = true;
+                kiwi.front.tabviews[t_view].safe_to_close = true;
             }
             break;
         case 'bad_channel_key':
-            front.tabviews[t_view].addMsg(null, ' ', '=== Bad channel key for ' + data.channel, 'status');
+            kiwi.front.tabviews[t_view].addMsg(null, ' ', '=== Bad channel key for ' + data.channel, 'status');
             if (t_view !== 'server') {
-                front.tabviews[t_view].safe_to_close = true;
+                kiwi.front.tabviews[t_view].safe_to_close = true;
             }
             break;
         case 'invite_only_channel':
-            front.tabviews[t_view].addMsg(null, ' ', '=== ' + data.channel + ' is invite only.', 'status');
+            kiwi.front.tabviews[t_view].addMsg(null, ' ', '=== ' + data.channel + ' is invite only.', 'status');
             if (t_view !== 'server') {
-                front.tabviews[t_view].safe_to_close = true;
+                kiwi.front.tabviews[t_view].safe_to_close = true;
             }
             break;
         case 'channel_is_full':
-            front.tabviews[t_view].addMsg(null, ' ', '=== ' + data.channel + ' is full.', 'status');
+            kiwi.front.tabviews[t_view].addMsg(null, ' ', '=== ' + data.channel + ' is full.', 'status');
             if (t_view !== 'server') {
-                front.tabviews[t_view].safe_to_close = true;
+                kiwi.front.tabviews[t_view].safe_to_close = true;
             }
             break;
         case 'chanop_privs_needed':
-            front.tabviews[data.channel].addMsg(null, ' ', '=== ' + data.reason, 'status');
+            kiwi.front.tabviews[data.channel].addMsg(null, ' ', '=== ' + data.reason, 'status');
             break;
         case 'no_such_nick':
-            front.tabviews.server.addMsg(null, ' ', '=== ' + data.nick + ': ' + data.reason, 'status'); 
+            kiwi.front.tabviews.server.addMsg(null, ' ', '=== ' + data.nick + ': ' + data.reason, 'status'); 
+            break;
+        case 'nickname_in_use':
+            kiwi.front.tabviews.server.addMsg(null, ' ', '=== The nickname ' + data.nick + ' is already in use. Please select a new nickname', 'status');
+            kiwi.front.showChangeNick();
             break;
         default:
             // We don't know what data contains, so don't do anything with it.
-            //front.tabviews.server.addMsg(null, ' ', '=== ' + data, 'status');
+            //kiwi.front.tabviews.server.addMsg(null, ' ', '=== ' + data, 'status');
         }
     },
     
     registerKeys: function () {
+        var tabcomplete = {active: false, data: [], prefix: ''};
         $('#kiwi_msginput').bind('keydown', function (e) {
-            var windows, meta, num, msg, data, candidates, word_pos, word;
+            var windows, meta, num, msg, data, candidates, word_pos, word, i, self;
             windows = $('#windows');
             //var meta = e.altKey;
             meta = e.ctrlKey;
             
+            if (e.which !== 9) {
+                tabcomplete.active = false;
+                tabcomplete.data = [];
+                tabcomplete.prefix = '';
+            }
+            
             switch (true) {
             case (e.which >= 48) && (e.which <= 57):
                 if (meta) {
@@ -607,7 +838,7 @@ var front = {
                         num = 10;
                     }
                     num = num - 1;
-                    front.windowsShow(num);
+                    kiwi.front.windowsShow(num);
                     return false;
                 }
                 break;
@@ -617,10 +848,10 @@ var front = {
                 msg = $('#kiwi_msginput').val();
                 msg = msg.trim();
                 
-                front.buffer.push(msg);
-                front.buffer_pos = front.buffer.length;
+                kiwi.front.buffer.push(msg);
+                kiwi.front.buffer_pos = kiwi.front.buffer.length;
                 
-                front.run(msg);
+                kiwi.front.run(msg);
                 $('#kiwi_msginput').val('');
                 
                 break;
@@ -633,70 +864,90 @@ var front = {
                 return false;
             case e.which === 37:            // left
                 if (meta) {
-                    front.windowsPrevious();
+                    kiwi.front.windowsPrevious();
                     return false;
                 }
                 break;
             case e.which === 38:            // up
-                if (front.buffer_pos > 0) {
-                    front.buffer_pos--;
-                    $('#kiwi_msginput').val(front.buffer[front.buffer_pos]);
+                if (kiwi.front.buffer_pos > 0) {
+                    kiwi.front.buffer_pos--;
+                    $('#kiwi_msginput').val(kiwi.front.buffer[kiwi.front.buffer_pos]);
                 }
                 break;
             case e.which === 39:            // right
                 if (meta) {
-                    front.windowsNext();
+                    kiwi.front.windowsNext();
                     return false;
                 }
                 break;
             case e.which === 40:            // down
-                if (front.buffer_pos < front.buffer.length) {
-                    front.buffer_pos++;
-                    $('#kiwi_msginput').val(front.buffer[front.buffer_pos]);
+                if (kiwi.front.buffer_pos < kiwi.front.buffer.length) {
+                    kiwi.front.buffer_pos++;
+                    $('#kiwi_msginput').val(kiwi.front.buffer[kiwi.front.buffer_pos]);
                 }
                 break;
                 
             case e.which === 9:                // tab
-                // Get possible autocompletions
-                data = [];
-                front.cur_channel.userlist.children().each(function () {
-                    nick = front.nickStripPrefix($('a.nick', this).text());
-                    data.push(nick);
-                });
+                tabcomplete.active = true;
+                if (_.isEqual(tabcomplete.data, [])) {
+                    // Get possible autocompletions
+                    data = [];
+                    kiwi.front.cur_channel.userlist.children().each(function () {
+                        var nick;
+                        nick = kiwi.front.nickStripPrefix($('a.nick', this).text());
+                        data.push(nick);
+                    });
+                    data = _.sortBy(data, function (nick) {
+                        return nick;
+                    });
+                    tabcomplete.data = data;
+                }
                 
-                // Do the autocomplete
-                if (this.value.length === this.selectionStart && this.value.length === this.selectionEnd) {
-                    candidates = [];
-                    
-                    word_pos = this.value.lastIndexOf(' ');
-                    word = "";
-                    if (word_pos === -1) {
-                        word = this.value;
-                    } else {
-                        word = this.value.substr(word_pos);
+                if (this.value[this.selectionStart - 1] === ' ') {
+                    return false;
+                }
+                self = this;
+                (function () {
+                    var tokens = self.value.substring(0, self.selectionStart).split(" "),
+                        val,
+                        p1,
+                        newnick,
+                        range;
+                    nick = tokens[tokens.length - 1];
+                    if (tabcomplete.prefix === '') {
+                        tabcomplete.prefix = nick;
                     }
-                    word = word.trim();
                     
-                    // filter data to find only strings that start with existing value
-                    for (i = 0; i < data.length; i++) {
-                        if (data[i].indexOf(word) === 0 && data[i].length > word.length) {
-                            candidates.push(data[i]);
-                        }
-                    }
+                    tabcomplete.data = _.select(tabcomplete.data, function (n) {
+                        return (n.toLowerCase().indexOf(tabcomplete.prefix.toLowerCase()) === 0);
+                    });
                     
-                    if (candidates.length > 0) {
-                        // some candidates for autocompletion are found
-                        this.value = this.value.substring(0, word_pos) + ' ' + candidates[0] + ': ';
-                        this.selectionStart = this.value.length;
+                    if (tabcomplete.data.length > 0) {
+                        p1 = self.selectionStart - (nick.length);
+                        val = self.value.substr(0, p1);
+                        newnick = tabcomplete.data.shift();
+                        tabcomplete.data.push(newnick);
+                        val += newnick;
+                        val += self.value.substr(self.selectionStart);
+                        self.value = val;
+                        if (self.setSelectionRange) {
+                            self.setSelectionRange(p1 + newnick.length, p1 + newnick.length);
+                        } else if (self.createTextRange) { // not sure if this bit is actually needed....
+                            range = self.createTextRange();
+                            range.collapse(true);
+                            range.moveEnd('character', p1 + newnick.length);
+                            range.moveStart('character', p1 + newnick.length);
+                            range.select();
+                        }
                     }
-                }
+                }());
                 return false;
             }
         });
         
         
         $('#kiwi .control .msginput .nick').click(function () {
-            front.showChangeNick();            
+            kiwi.front.showChangeNick();            
         });
         
         
@@ -704,55 +955,42 @@ var front = {
         
         
         $('#kiwi .plugins .load_plugin_file').click(function () {
-            var lst, j, txt;
-            if (typeof front.boxes.plugins !== "undefined") {
+            if (typeof kiwi.front.boxes.plugins !== "undefined") {
                 return;
             }
             
-            front.boxes.plugins = new Box("plugin_file");
-            $('#tmpl_plugins').tmpl({}).appendTo(front.boxes.plugins.content);
-            front.boxes.plugins.box.css('top', -(front.boxes.plugins.height + 40));
+            kiwi.front.boxes.plugins = new Box("plugin_file");
+            $('#tmpl_plugins').tmpl({}).appendTo(kiwi.front.boxes.plugins.content);
+            kiwi.front.boxes.plugins.box.css('top', -(kiwi.front.boxes.plugins.height + 40));
             
             // Populate the plugin list..
-            lst = $('#plugin_list');
-            lst.find('option').remove();
-            for (j in plugins.privmsg) {
-                txt = plugins.privmsg[j].name;
-                lst.append('<option value="' + txt + '">' + txt + '</option>');
+            function enumPlugins() {
+                var lst, j, txt;
+                lst = $('#plugin_list');
+                lst.find('option').remove();
+                for (j in kiwi.plugs.loaded) {
+                    txt = kiwi.plugs.loaded[j].name;
+                    lst.append('<option value="' + txt + '">' + txt + '</option>');
+                }
             }
+            enumPlugins();
             
             // Event bindings
             $('#kiwi .plugin_file').submit(function () {
-                $.getJSON($('.txtpluginfile').val(), function (data) {
-                    var plg = {};
-                    plg.name = data.name;
-                    eval("plg.onprivmsg = " + data.onprivmsg);
-                    eval("plg.onload = " + data.onload);
-                    eval("plg.onunload = " + data.onunload);
-                    plugins.privmsg.push(plg);
-                    
-                    if (plg.onload instanceof Function) {
-                        plg.onload();
-                    }
+                $('<div></div>').load($('.txtpluginfile').val(), function (e) {
+                    enumPlugins();
                 });
                 return false;
             });
             $('#kiwi .cancelpluginfile').click(function () {
-                front.boxes.plugins.destroy();
+                kiwi.front.boxes.plugins.destroy();
             });
             
             $('#kiwi #plugins_list_unload').click(function () {
-                var selected_plugin, i;
+                var selected_plugin;
                 selected_plugin = $('#plugin_list').val();
-                console.log("removing plugin: " + selected_plugin);
-                for (i in plugins.privmsg) {
-                    if (plugins.privmsg[i].name === selected_plugin) {
-                        if (plugins.privmsg[i].onunload instanceof Function) {
-                            plugins.privmsg[i].onunload();
-                        }
-                        delete plugins.privmsg[i];
-                    }
-                }
+                kiwi.plugs.unloadPlugin(selected_plugin);
+                enumPlugins();
             });
             
             $('#kiwi .txtpluginfile').focus();
@@ -789,7 +1027,7 @@ var front = {
         $('#kiwi').append($('#tmpl_change_nick').tmpl({}));
         
         $('#kiwi .form_newnick').submit(function () {
-            front.run('/NICK ' + $('#kiwi .txtnewnick').val());
+            kiwi.front.run('/NICK ' + $('#kiwi .txtnewnick').val());
             $('#kiwi .newnick').remove();
             return false;
         });
@@ -817,37 +1055,43 @@ var front = {
 
 
     tabviewExists: function (name) {
-        return (typeof front.tabviews[name.toLowerCase()] !== 'undefined');
+        return (typeof kiwi.front.tabviews[name.toLowerCase()] !== 'undefined');
     },
     
     tabviewAdd: function (v_name) {
-        var re, htmlsafe_name, tmp_divname, tmp_userlistname, tmp_tabname;
-        if (v_name.charAt(0) === gateway.channel_prefix) {
-            re = new RegExp(gateway.channel_prefix, "g");
+        /*global Tabview */
+        var re, htmlsafe_name, tmp_divname, tmp_userlistname, tmp_tabname, userlist_enabled = true;
+
+        if (v_name.charAt(0) === kiwi.gateway.channel_prefix) {
+            re = new RegExp(kiwi.gateway.channel_prefix, "g");
             htmlsafe_name = v_name.replace(re, 'pre');
             htmlsafe_name = "chan_" + htmlsafe_name;
         } else {
             htmlsafe_name = 'query_' + v_name;
+            userlist_enabled = false;
         }
         
         tmp_divname = 'kiwi_window_' + htmlsafe_name;
         tmp_userlistname = 'kiwi_userlist_' + htmlsafe_name;
         tmp_tabname = 'kiwi_tab_' + htmlsafe_name;
         
-        if (!front.tabviewExists(v_name)) {
+        if (!kiwi.front.tabviewExists(v_name)) {
             $('#kiwi .windows .scroller').append('<div id="' + tmp_divname + '" class="messages"></div>');
             $('#kiwi .userlist').append('<ul id="' + tmp_userlistname + '"></ul>');
-            $('#kiwi .windowlist ul').append('<li id="' + tmp_tabname + '" onclick="front.tabviews[\'' + v_name.toLowerCase() + '\'].show();">' + v_name + '</li>');
+            $('#kiwi .windowlist ul').append('<li id="' + tmp_tabname + '" onclick="kiwi.front.tabviews[\'' + v_name.toLowerCase() + '\'].show();">' + v_name + '</li>');
         }
-        //$('#kiwi .windowlist ul .window_'+v_name).click(function(){ front.windowShow(v_name); });
-        //front.windowShow(v_name);
+        //$('#kiwi .windowlist ul .window_'+v_name).click(function(){ kiwi.front.windowShow(v_name); });
+        //kiwi.front.windowShow(v_name);
         
-        front.tabviews[v_name.toLowerCase()] = new Tabview();
-        front.tabviews[v_name.toLowerCase()].name = v_name;
-        front.tabviews[v_name.toLowerCase()].div = $('#' + tmp_divname);
-        front.tabviews[v_name.toLowerCase()].userlist = $('#' + tmp_userlistname);
-        front.tabviews[v_name.toLowerCase()].tab = $('#' + tmp_tabname);
-        front.tabviews[v_name.toLowerCase()].show();
+        kiwi.front.tabviews[v_name.toLowerCase()] = new Tabview();
+        kiwi.front.tabviews[v_name.toLowerCase()].name = v_name;
+        kiwi.front.tabviews[v_name.toLowerCase()].div = $('#' + tmp_divname);
+        kiwi.front.tabviews[v_name.toLowerCase()].userlist = $('#' + tmp_userlistname);
+        kiwi.front.tabviews[v_name.toLowerCase()].tab = $('#' + tmp_tabname);
+        if (!userlist_enabled) {
+            kiwi.front.tabviews[v_name.toLowerCase()].userlist_width = 0;
+        }
+        kiwi.front.tabviews[v_name.toLowerCase()].show();
         
         if (typeof registerTouches === "function") {
             //alert("Registering touch interface");
@@ -855,99 +1099,92 @@ var front = {
             registerTouches(document.getElementById(tmp_divname));
         }
         /*
-        front.tabviews[v_name.toLowerCase()].userlist.click(function(){
+        kiwi.front.tabviews[v_name.toLowerCase()].userlist.click(function(){
             alert($(this).attr('id'));
         });
         */
 
-        front.doLayoutSize();
+        kiwi.front.doLayoutSize();
     },
     
     
     userClick: function (item) {
+        var li = $(item).parent();
+
         // Remove any existing userboxes
         $('#kiwi .userbox').remove();
-        
-        var li = $(item).parent();
-        /*var html = '<div class="userbox">\
-    <input type="hidden" class="userbox_nick" value="' + front.nickStripPrefix($(item).text()) + '" />\
-    <a href="#" class="userbox_query">Message</a>\
-    <a href="#" class="userbox_whois">Info</a>\
-</div>';
-        li.append(html);*/
-        $('#tmpl_user_box').tmpl({nick: front.nickStripPrefix($(item).text())}).appendTo(li);
-        
-        $('#kiwi .userbox .userbox_query').click(function (ev) {
-            var nick = $('#kiwi .userbox_nick').val();
-            front.run('/query ' + nick);
-        });
-        
-        $('#kiwi .userbox .userbox_whois').click(function (ev) {
-            var nick = $('#kiwi .userbox_nick').val();
-            front.run('/whois ' + nick);
-        });
+
+        if ($(li).data('userbox') === item) {
+            $(li).removeData('userbox');
+        } else {
+            $('#tmpl_user_box').tmpl({nick: kiwi.front.nickStripPrefix($(item).text())}).appendTo(li);
+            
+            $('#kiwi .userbox .userbox_query').click(function (ev) {
+                var nick = $('#kiwi .userbox_nick').val();
+                kiwi.front.run('/query ' + nick);
+            });
+            
+            $('#kiwi .userbox .userbox_whois').click(function (ev) {
+                var nick = $('#kiwi .userbox_nick').val();
+                kiwi.front.run('/whois ' + nick);
+            });
+            $(li).data('userbox', item);
+        }
     },
     
     
     sync: function () {
-        gateway.sync();
+        kiwi.gateway.sync();
     },
     
     onSync: function (e, data) {
         // Set any settings
         if (data.nick !== undefined) {
-            gateway.nick = data.nick;
+            kiwi.gateway.nick = data.nick;
         }
         
         // Add the tabviews
         if (data.tabviews !== undefined) {
             $.each(data.tabviews, function (i, tab) {
-                if (!front.tabviewExists(tab.name)) {
-                    front.tabviewAdd(gateway.channel_prefix + tab.name);
+                if (!kiwi.front.tabviewExists(tab.name)) {
+                    kiwi.front.tabviewAdd(kiwi.gateway.channel_prefix + tab.name);
                     
                     if (tab.userlist !== undefined) {
-                        front.onUserList({'channel': gateway.channel_prefix + tab.name, 'users': tab.userlist});
+                        kiwi.front.onUserList({'channel': kiwi.gateway.channel_prefix + tab.name, 'users': tab.userlist});
                     }
                 }
             });
         }
         
-        front.doLayout();
+        kiwi.front.doLayout();
     },
     
     
     setTopicText: function (new_topic) {
-        front.original_topic = new_topic;
+        kiwi.front.original_topic = new_topic;
         $('#kiwi .cur_topic .topic').text(new_topic);
-        front.doLayoutSize();
+        kiwi.front.doLayoutSize();
     },
     
-    
-    
-    
-    
-    
-    
     nickStripPrefix: function (nick) {
-        var tmp = nick, i;
+        var tmp = nick, i, prefix;
         
         prefix = tmp.charAt(0);
-        for (i in gateway.user_prefixes) {
-            if (gateway.user_prefixes[i].symbol !== prefix) {
-                continue;
+        for (i in kiwi.gateway.user_prefixes) {
+            if (kiwi.gateway.user_prefixes[i].symbol === prefix) {
+                return tmp.substring(1);
             }
-            return tmp.substring(1);
         }
 
         return tmp;
     },
     
     nickGetPrefix: function (nick) {
-        var tmp = nick, i;
+        var tmp = nick, i, prefix;
         
         prefix = tmp.charAt(0);
-        for (i in gateway.user_prefixes) {
-            if (gateway.user_prefixes[i].symbol === prefix) {
+        for (i in kiwi.gateway.user_prefixes) {
+            if (kiwi.gateway.user_prefixes[i].symbol === prefix) {
                 return prefix;
             }
         }
@@ -956,8 +1193,9 @@ var front = {
     },
     
     isChannel: function (name) {
+        var prefix, is_chan;
         prefix = name.charAt(0);
-        if (gateway.channel_prefix.indexOf(prefix) > -1) {
+        if (kiwi.gateway.channel_prefix.indexOf(prefix) > -1) {
             is_chan = true;
         } else {
             is_chan = false;
@@ -981,14 +1219,14 @@ var front = {
     windowsNext: function () {
         var tab, next;
         next = false;
-        for (tab in front.tabviews) {
+        for (tab in kiwi.front.tabviews) {
             if (!next) {
-                if (front.tabviews[tab] === front.cur_channel) {
+                if (kiwi.front.tabviews[tab] === kiwi.front.cur_channel) {
                     next = true;
                     continue;
                 }
             } else {
-                front.tabviews[tab].show();
+                kiwi.front.tabviews[tab].show();
                 return;
             }
         }
@@ -997,14 +1235,14 @@ var front = {
     windowsPrevious: function () {
         var tab, prev_tab, next;
         next = false;
-        for (tab in front.tabviews) {
-            if (front.tabviews[tab] === front.cur_channel) {
+        for (tab in kiwi.front.tabviews) {
+            if (kiwi.front.tabviews[tab] === kiwi.front.cur_channel) {
                 if (prev_tab) {
                     prev_tab.show();
                 }
                 return;
             }
-            prev_tab = front.tabviews[tab];
+            prev_tab = kiwi.front.tabviews[tab];
         }
     },
 
@@ -1012,14 +1250,108 @@ var front = {
         num = parseInt(num, 10);
         console.log('Showing window ' + num.toString());
         var i = 0, tab;
-        for (tab in front.tabviews) {
+        for (tab in kiwi.front.tabviews) {
             if (i === num) {
-                front.tabviews[tab].show();
+                kiwi.front.tabviews[tab].show();
                 return;
             }
             i++;
         }
+    },
+
+
+
+    barsShow: function () {
+        $('#kiwi .toolbars').slideDown();
+        $('#kiwi .control').slideDown();
+    },
+
+    barsHide: function () {
+        $('#kiwi .toolbars').slideUp();
+        $('#kiwi .control').slideUp();
+    },
+    
+    format: function (msg) {
+        var re;
+        
+        if ((!msg) || (typeof msg !== 'string')) {
+            return;
+        }
+        
+        // bold
+        if (msg.indexOf(String.fromCharCode(2)) !== -1) {
+            next = '<b>';
+            while (msg.indexOf(String.fromCharCode(2)) !== -1) {
+                msg = msg.replace(String.fromCharCode(2), next);
+                next = (next === '<b>') ? '</b>' : '<b>';
+            }
+            if (next === '</b>') {
+                msg = msg + '</b>';
+            }
+        }
+        
+        // underline
+        if (msg.indexOf(String.fromCharCode(31)) !== -1) {
+            next = '<u>';
+            while (msg.indexOf(String.fromCharCode(31)) !== -1) {
+                msg = msg.replace(String.fromCharCode(31), next);
+                next = (next === '<u>') ? '</u>' : '<u>';
+            }
+            if (next === '</u>') {
+                msg = msg + '</u>';
+            }
+        }
+        
+        // colour
+        re = /\x03([0-9][0-5]?)(,([0-9][0-5]?))?(.*?)\x03/g;
+        
+        msg = msg.replace(re, function (str, p1, p2, p3, p4) {
+            var fg, bg,
+                col = function (num) {
+                    switch (parseInt(num, 10)) {
+                    case 0:
+                        return '#FFFFFF';
+                    case 1:
+                        return '#000000';
+                    case 2:
+                        return '#000080';
+                    case 3:
+                        return '#008000';
+                    case 4:
+                        return '#FF0000';
+                    case 5:
+                        return '#800040';
+                    case 6:
+                        return '#800080';
+                    case 7:
+                        return '#FF8040';
+                    case 8:
+                        return '#FFFF00';
+                    case 9:
+                        return '#80FF00';
+                    case 10:
+                        return '#008080';
+                    case 11:
+                        return '#00FFFF';
+                    case 12:
+                        return '#0000FF';
+                    case 13:
+                        return '#FF55FF';
+                    case 14:
+                        return '#808080';
+                    case 15:
+                        return '#C0C0C0';
+                    default:
+                        return null;
+                    }
+                };
+            fg = col(p1);
+            bg = col(p3);
+            return '<span style="' + ((fg !== null) ? 'color: ' + fg + '; ' : '') + ((bg !== null) ? 'background-color: ' + bg + ';' : '') + '">' + p4 + '</span>';
+        });
+        return msg;
     }
+    
 };
 
 
@@ -1037,6 +1369,110 @@ var front = {
 
 
 
+/*
+ *   MISC VIEW
+ */
+
+var Utilityview = function (name) {
+    var rand_name = randomString(15),
+        tmp_divname = 'kiwi_window_' + rand_name,
+        tmp_userlistname = 'kiwi_userlist_' + rand_name,
+        tmp_tabname = 'kiwi_tab_' + rand_name;
+    
+    this.name = rand_name;
+    this.title = name;
+    this.topic = ' ';
+    this.panel = $('#panel1');
+
+    if (typeof $('.scroller', this.panel)[0] === 'undefined') {
+        this.panel.append('<div id="' + tmp_divname + '" class="messages"></div>');
+    } else {
+        $('.scroller', this.panel).append('<div id="' + tmp_divname + '" class="messages"></div>');
+    }
+
+    this.tab = $('<li id="' + tmp_tabname + '">' + this.title + '</li>');
+    this.tab.click(function () {
+        kiwi.front.utilityviews[rand_name.toLowerCase()].show();
+    });
+    $('#kiwi .utilityviewlist ul').append(this.tab);
+    kiwi.front.doLayoutSize();
+    
+    this.div = $('#' + tmp_divname);
+    this.div.css('overflow', 'hidden');
+
+    kiwi.front.utilityviews[rand_name.toLowerCase()] = this;
+};
+
+Utilityview.prototype.name = null;
+Utilityview.prototype.title = null;
+Utilityview.prototype.div = null;
+Utilityview.prototype.tab = null;
+Utilityview.prototype.topic = ' ';
+Utilityview.prototype.panel = null;
+Utilityview.prototype.show = function () {
+    $('.messages', this.panel).removeClass("active");
+    $('#kiwi .userlist ul').removeClass("active");
+    $('#kiwi .toolbars ul li').removeClass("active");
+
+    this.panel.css('overflow-y', 'hidden');
+    $('#windows').css('right', 0);
+    // Activate this tab!
+    this.div.addClass('active');
+    this.tab.addClass('active');
+
+    this.addPartImage();
+
+    kiwi.front.setTopicText(this.topic);
+    kiwi.front.cur_channel = this;
+
+    // If we're using fancy scrolling, refresh it
+    if (touch_scroll) {
+        touch_scroll.refresh();
+    }
+};
+
+Utilityview.prototype.setPanel = function (new_panel) {
+    this.div.detach();
+    this.panel = new_panel;
+    this.panel.append(this.div);
+    this.show();
+};
+
+Utilityview.prototype.close = function () {
+    this.div.remove();
+    this.tab.remove();
+    
+    if (kiwi.front.cur_channel === this) {
+        kiwi.front.tabviews.server.show();
+    }
+    delete kiwi.front.utilityviews[this.name.toLowerCase()];
+};
+
+Utilityview.prototype.addPartImage = function () {
+    this.clearPartImage();
+    
+    // We can't close this tab, so don't have the close image
+    if (this.name === 'server') {
+        return;
+    }
+
+    var del_html = '<img src="/img/redcross.png" class="tab_part" />';
+    this.tab.append(del_html);
+    
+    $('.tab_part', this.tab).click(function () {
+        if (kiwi.front.cur_channel.name !== 'server') {
+            kiwi.front.cur_channel.close();
+        }
+    });
+};
+
+Utilityview.prototype.clearPartImage = function () {
+    $('#kiwi .toolbars .tab_part').remove();
+};
+
+
+
+
 
 /*
  *
@@ -1045,31 +1481,51 @@ var front = {
  */
 
 
-var Tabview = function () {};
+var Tabview = function () {
+    this.panel = $('#panel1');
+};
 Tabview.prototype.name = null;
 Tabview.prototype.div = null;
 Tabview.prototype.userlist = null;
+Tabview.prototype.userlist_width = 100;     // 0 for disabled
 Tabview.prototype.tab = null;
 Tabview.prototype.topic = "";
 Tabview.prototype.safe_to_close = false;                // If we have been kicked/banned/etc from this channel, don't wait for a part message
+Tabview.prototype.panel = null;
 
 Tabview.prototype.show = function () {
-    $('#kiwi .messages').removeClass("active");
+    var w, u;
+
+    $('.messages', this.panel).removeClass("active");
     $('#kiwi .userlist ul').removeClass("active");
-    $('#kiwi .windowlist ul li').removeClass("active");
+    $('#kiwi .toolbars ul li').removeClass("active");
     
+    w = $('#windows');
+    u = $('#kiwi .userlist');
+
+    this.panel.css('overflow-y', 'scroll');
+
+    // Set the window size accordingly
+    this.setUserlistWidth();
+
     // Activate this tab!
     this.div.addClass('active');
-    this.userlist.addClass('active');
+    if (this.userlist_width > 0) {
+        this.userlist.addClass('active');
+        // Enable the userlist resizer
+        $('#nicklist_resize').css('display', 'block');
+    } else {
+        // Disable the userlist resizer
+        $('#nicklist_resize').css('display', 'none');
+    }
     this.tab.addClass('active');
     
-    document.tmp = this.div;
     // Add the part image to the tab
     this.addPartImage();
     
     this.clearHighlight();
-    front.setTopicText(this.topic);
-    front.cur_channel = this;
+    kiwi.front.setTopicText(this.topic);
+    kiwi.front.cur_channel = this;
     
     // If we're using fancy scrolling, refresh it
     if (touch_scroll) {
@@ -1087,10 +1543,28 @@ Tabview.prototype.close = function () {
     this.userlist.remove();
     this.tab.remove();
     
-    if (front.cur_channel === this) {
-        front.tabviews.server.show();
+    if (kiwi.front.cur_channel === this) {
+        kiwi.front.tabviews.server.show();
+    }
+    delete kiwi.front.tabviews[this.name.toLowerCase()];
+};
+
+Tabview.prototype.setUserlistWidth = function (new_width) {
+    var w, u;
+    if (typeof new_width === 'number') {
+        this.userlist_width = new_width;
     }
-    delete front.tabviews[this.name.toLowerCase()];
+
+    w = $('#windows');
+    u = $('#kiwi .userlist');
+
+    // Set the window size accordingly
+    if (this.userlist_width > 0) {
+        u.width(this.userlist_width);
+        w.css('right', u.outerWidth(true));
+    } else {
+        w.css('right', 0);
+    }    
 };
 
 Tabview.prototype.addPartImage = function () {
@@ -1101,55 +1575,41 @@ Tabview.prototype.addPartImage = function () {
         return;
     }
 
-    var del_html = '<img src="img/redcross.png" class="tab_part" />';
+    var del_html = '<img src="/img/redcross.png" class="tab_part" />';
     this.tab.append(del_html);
     
     $('.tab_part', this.tab).click(function () {
-        if (front.isChannel($(this).parent().text())) {
-            front.run("/part");
+        if (kiwi.front.isChannel($(this).parent().text())) {
+            kiwi.front.run("/part");
         } else {
             // Make sure we don't close the server tab
-            if (front.cur_channel.name !== 'server') {
-                front.cur_channel.close();
+            if (kiwi.front.cur_channel.name !== 'server') {
+                kiwi.front.cur_channel.close();
             }
         }
     });
 };
 
 Tabview.prototype.clearPartImage = function () {
-    $('#kiwi .windowlist .tab_part').remove();
+    $('#kiwi .toolbars .tab_part').remove();
 };
 
 Tabview.prototype.addMsg = function (time, nick, msg, type, style) {
-    var html_nick, self, tmp, plugin_ret, i, d, re, line_msg;
-    html_nick = $('<div/>').text(nick).html();
+    var self, tmp, plugin_ret, i, d, re, line_msg, next;
     
     self = this;
     
-    tmp = msg;
-    plugin_ret = '';
-    for (i in plugins.privmsg) {
-        if ((plugins.privmsg[i].onprivmsg instanceof Function)) {
-            plugin_ret = '';
-            try {
-                plugin_ret = plugins.privmsg[i].onprivmsg(tmp, this.name);
-                
-                // If this plugin has returned false, do not add this message
-                if (plugin_ret === false) {
-                    return;
-                }
-            } catch (e) {
-            }
-            
-            // If we actually have a string from the plugin, use it
-            if (typeof plugin_ret === "string") {
-                tmp = plugin_ret;
-            }
-        }
+    tmp = {msg: msg, time: time, nick: nick, tabview: this.name};
+    tmp = kiwi.plugs.run('addmsg', tmp);
+    if (!tmp) {
+        return;
     }
-    msg = tmp;
     
-    //var html_msg = $('<div/>').text(msg).html()+' '; // Add the space so the styling always has at least 1 character to go from
+
+    msg = tmp.msg;
+    time = tmp.time;
+    nick = tmp.nick;
+
     if (time === null) {
         d = new Date();
         time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0");
@@ -1165,43 +1625,16 @@ Tabview.prototype.addMsg = function (time, nick, msg, type, style) {
         msg = '';
     }
     
-    // Text formatting
-    // bold
-    if (msg.indexOf(String.fromCharCode(2)) !== -1) {
-        next = '<b>';
-        while (msg.indexOf(String.fromCharCode(2)) !== -1) {
-            msg = msg.replace(String.fromCharCode(2), next);
-            next = (next === '<b>') ? '</b>' : '<b>';
-        }
-        if (next === '</b>') {
-            msg = msg + '</b>';
-        }
-    }
-    
-    // Wierd thing noticed by Dux0r on the irc.esper.net server
-    if (typeof msg !== "string") {
-        msg = '';
-    }
-    
-    // underline
-    if (msg.indexOf(String.fromCharCode(31)) !== -1) {
-        next = '<u>';
-        while (msg.indexOf(String.fromCharCode(31)) !== -1) {
-            msg = msg.replace(String.fromCharCode(31), next);
-            next = (next === '<u>') ? '</u>' : '<u>';
-        }
-        if (next === '</u>') {
-            msg = msg + '</u>';
-        }
-    }
-    
-    re = new RegExp('\\B(' + gateway.channel_prefix + '[^ ,.\\007]+)', 'g');
-    
+    // Make the channels clickable
+    re = new RegExp('\\B(' + kiwi.gateway.channel_prefix + '[^ ,.\\007]+)', 'g');
     msg = msg.replace(re, function (match) {
-        return '<a class="chan" href="#">' + match + '</a>';
+        return '<a class="chan">' + match + '</a>';
     });
 
-    line_msg = $('<div class="msg ' + type + '"><div class="time">' + time + '</div><div class="nick">' + html_nick + '</div><div class="text" style="' + style + '">' + msg + ' </div></div>');
+    msg = kiwi.front.format(msg);
+    
+    // Build up and add the line
+    line_msg = $('<div class="msg ' + type + '"><div class="time">' + time + '</div><div class="nick">' + nick + '</div><div class="text" style="' + style + '">' + msg + ' </div></div>');
     this.div.append(line_msg);
 
     if (!touchscreen) {
@@ -1217,21 +1650,22 @@ Tabview.prototype.addMsg = function (time, nick, msg, type, style) {
 };
 
 Tabview.prototype.scrollBottom = function () {
-    var w = $('#windows');
-    w[0].scrollTop = w[0].scrollHeight;
+    var panel = this.panel;
+    console.log(panel);
+    panel[0].scrollTop = panel[0].scrollHeight;
 };
 
 Tabview.prototype.changeNick = function (newNick, oldNick) {
     this.userlist.children().each(function () {
         var item = $('a.nick', this);
-        if (front.nickStripPrefix(item.text()) === oldNick) {
-            item.text(front.nickGetPrefix(item.text()) + newNick);
+        if (kiwi.front.nickStripPrefix(item.text()) === oldNick) {
+            item.text(kiwi.front.nickGetPrefix(item.text()) + newNick);
             document.temp_chan = 1;
         }
     });
     
     if (typeof document.temp_chan !== "undefined") {
-        this.addMsg(null, ' ', '=== ' + oldNick + ' is now known as ' + newNick, 'action');
+        this.addMsg(null, ' ', '=== ' + oldNick + ' is now known as ' + newNick, 'action changenick');
         delete document.temp_chan;
         this.userlistSort();
     }
@@ -1239,15 +1673,16 @@ Tabview.prototype.changeNick = function (newNick, oldNick) {
 
 Tabview.prototype.userlistSort = function () {
     var ul = this.userlist,
-        listitems = ul.children('li').get();
+        listitems = ul.children('li').get(),
+        prefix;
     listitems.sort(function (a, b) {
         var compA = $(a).text().toUpperCase(),
             compB = $(b).text().toUpperCase(),
             i;
         
         // Sort by prefixes first
-        for (i in gateway.user_prefixes) {
-            prefix = gateway.user_prefixes[i].symbol;
+        for (i in kiwi.gateway.user_prefixes) {
+            prefix = kiwi.gateway.user_prefixes[i].symbol;
             
             if (compA.charAt(0) === prefix && compB.charAt(0) === prefix) {
                 // Both have the same prefix, string compare time
@@ -1282,8 +1717,8 @@ Tabview.prototype.clearHighlight = function () {
 Tabview.prototype.changeTopic = function (new_topic) {
     this.topic = new_topic;
     this.addMsg(null, ' ', '=== Topic for ' + this.name + ' is: ' + new_topic, 'topic');
-    if (front.cur_channel.name === this.name) {
-        front.setTopicText(new_topic);
+    if (kiwi.front.cur_channel.name === this.name) {
+        kiwi.front.setTopicText(new_topic);
     }
 };
 
@@ -1311,9 +1746,9 @@ Box.prototype.content = null;
 Box.prototype.destroy = function () {
     var name;
     this.box.remove();
-    for (name in front.boxes) {
-        if (front.boxes[name].id === this.id) {
-            delete front.boxes[name];
+    for (name in kiwi.front.boxes) {
+        if (kiwi.front.boxes[name].id === this.id) {
+            delete kiwi.front.boxes[name];
         }
     }
 };