Server connection dialog extracted
[KiwiIRC.git] / client / assets / dev / view.js
index d6d3ebb30ac9e47785b7532a825e8f5dd25a28d9..4fc6f9e7bb563a9909e0dd82321528c1c57b1aa5 100644 (file)
@@ -20,11 +20,13 @@ _kiwi.view.MemberList = Backbone.View.extend({
                 .data('member', member);\r
         });\r
     },\r
-    nickClick: function (x) {\r
-        var $target = $(x.currentTarget).parent('li'),\r
+    nickClick: function (event) {\r
+        var $target = $(event.currentTarget).parent('li'),\r
             member = $target.data('member'),\r
             userbox;\r
         \r
+        event.stopPropagation();\r
+\r
         // If the userbox already exists here, hide it\r
         if ($target.find('.userbox').length > 0) {\r
             $('.userbox', this.$el).remove();\r
@@ -33,11 +35,33 @@ _kiwi.view.MemberList = Backbone.View.extend({
 \r
         userbox = new _kiwi.view.UserBox();\r
         userbox.member = member;\r
+        userbox.channel = this.model.channel;\r
+\r
+        if (!this.model.getByNick(_kiwi.app.connections.active_connection.get('nick')).get('is_op')) {\r
+            userbox.$el.children('.if_op').remove();\r
+        }\r
 \r
-        // Remove any existing userboxes\r
-        $('.userbox', this.$el).remove();\r
+        var menu = new _kiwi.view.MenuBox(member.get('nick') || 'User');\r
+        menu.addItem('userbox', userbox.$el);\r
+        menu.show();\r
+\r
+        // Position the userbox + menubox\r
+        (function() {\r
+            var t = event.pageY,\r
+                m_bottom = t + menu.$el.outerHeight(),  // Where the bottom of menu will be\r
+                memberlist_bottom = this.$el.parent().offset().top + this.$el.parent().outerHeight();\r
+\r
+            // If the bottom of the userbox is going to be too low.. raise it\r
+            if (m_bottom > memberlist_bottom){\r
+                t = memberlist_bottom - menu.$el.outerHeight();\r
+            }\r
 \r
-        $target.append(userbox.$el);\r
+            // Set the new positon\r
+            menu.$el.offset({\r
+                left: _kiwi.app.view.$el.width() - menu.$el.outerWidth() - 20,\r
+                top: t\r
+            });\r
+        }).call(this);\r
     },\r
     show: function () {\r
         $('#memberlists').children().removeClass('active');\r
@@ -51,7 +75,13 @@ _kiwi.view.UserBox = Backbone.View.extend({
     events: {\r
         'click .query': 'queryClick',\r
         'click .info': 'infoClick',\r
-        'click .slap': 'slapClick'\r
+        'click .slap': 'slapClick',\r
+        'click .op': 'opClick',\r
+        'click .deop': 'deopClick',\r
+        'click .voice': 'voiceClick',\r
+        'click .devoice': 'devoiceClick',\r
+        'click .kick': 'kickClick',\r
+        'click .ban': 'banClick'\r
     },\r
 \r
     initialize: function () {\r
@@ -60,7 +90,7 @@ _kiwi.view.UserBox = Backbone.View.extend({
 \r
     queryClick: function (event) {\r
         var panel = new _kiwi.model.Query({name: this.member.get('nick')});\r
-        _kiwi.app.panels.add(panel);\r
+        _kiwi.app.connections.active_connection.panels.add(panel);\r
         panel.view.show();\r
     },\r
 \r
@@ -70,6 +100,32 @@ _kiwi.view.UserBox = Backbone.View.extend({
 \r
     slapClick: function (event) {\r
         _kiwi.app.controlbox.processInput('/slap ' + this.member.get('nick'));\r
+    },\r
+\r
+    opClick: function (event) {\r
+        _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' +o ' + this.member.get('nick'));\r
+    },\r
+\r
+    deopClick: function (event) {\r
+        _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' -o ' + this.member.get('nick'));\r
+    },\r
+\r
+    voiceClick: function (event) {\r
+        _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' +v ' + this.member.get('nick'));\r
+    },\r
+\r
+    devoiceClick: function (event) {\r
+        _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' -v ' + this.member.get('nick'));\r
+    },\r
+\r
+    kickClick: function (event) {\r
+        // TODO: Enable the use of a custom kick message\r
+        _kiwi.app.controlbox.processInput('/kick ' + this.member.get('nick') + ' Bye!');\r
+    },\r
+\r
+    banClick: function (event) {\r
+        // TODO: Set ban on host, not just on nick\r
+        _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' +b ' + this.member.get('nick') + '!*');\r
     }\r
 });\r
 \r
@@ -98,7 +154,10 @@ _kiwi.view.NickChangeBox = Backbone.View.extend({
 \r
     changeNick: function (event) {\r
         var that = this;\r
-        _kiwi.gateway.changeNick(this.$el.find('input').val(), function (err, val) {\r
+\r
+        event.preventDefault();\r
+\r
+        _kiwi.app.connections.active_connection.gateway.changeNick(this.$el.find('input').val(), function (err, val) {\r
             that.close();\r
         });\r
         return false;\r
@@ -129,24 +188,17 @@ _kiwi.view.ServerSelect = function () {
                 }\r
             }\r
 \r
-\r
             _kiwi.gateway.bind('onconnect', this.networkConnected, this);\r
             _kiwi.gateway.bind('connecting', this.networkConnecting, this);\r
+            _kiwi.gateway.bind('onirc_error', this.onIrcError, this);\r
+        },\r
 \r
-            _kiwi.gateway.bind('onirc_error', function (data) {\r
-                $('button', this.$el).attr('disabled', null);\r
-\r
-                if (data.error == 'nickname_in_use') {\r
-                    this.setStatus('Nickname already taken');\r
-                    this.show('nick_change');\r
-                }\r
+        dispose: function() {\r
+            _kiwi.gateway.off('onconnect', this.networkConnected, this);\r
+            _kiwi.gateway.off('connecting', this.networkConnecting, this);\r
+            _kiwi.gateway.off('onirc_error', this.onIrcError, this);\r
 \r
-                if (data.error == 'password_mismatch') {\r
-                    this.setStatus('Incorrect Password');\r
-                    this.show('nick_change');\r
-                    that.$el.find('.password').select();\r
-                }\r
-            }, this);\r
+            this.$el.remove();\r
         },\r
 \r
         submitForm: function (event) {\r
@@ -172,7 +224,7 @@ _kiwi.view.ServerSelect = function () {
         submitLogin: function (event) {\r
             // If submitting is disabled, don't do anything\r
             if ($('button', this.$el).attr('disabled')) return;\r
-            \r
+\r
             var values = {\r
                 nick: $('input.nick', this.$el).val(),\r
                 server: $('input.server', this.$el).val(),\r
@@ -270,6 +322,21 @@ _kiwi.view.ServerSelect = function () {
             this.setStatus('Connecting..', 'ok');\r
         },\r
 \r
+        onIrcError: function (data) {\r
+            $('button', this.$el).attr('disabled', null);\r
+\r
+            if (data.error == 'nickname_in_use') {\r
+                this.setStatus('Nickname already taken');\r
+                this.show('nick_change');\r
+            }\r
+\r
+            if (data.error == 'password_mismatch') {\r
+                this.setStatus('Incorrect Password');\r
+                this.show('nick_change');\r
+                that.$el.find('.password').select();\r
+            }\r
+        },\r
+\r
         showError: function (event) {\r
             this.setStatus('Error connecting', 'error');\r
             $('button', this.$el).attr('disabled', null);\r
@@ -284,7 +351,8 @@ _kiwi.view.ServerSelect = function () {
 \r
 _kiwi.view.Panel = Backbone.View.extend({\r
     tagName: "div",\r
-    className: "messages",\r
+    className: "panel messages",\r
+\r
     events: {\r
         "click .chan": "chanClick",\r
         'click .media .open': 'mediaClick',\r
@@ -331,7 +399,7 @@ _kiwi.view.Panel = Backbone.View.extend({
             nick_colour_hex, nick_hex, is_highlight, msg_css_classes = '';\r
 \r
         // Nick highlight detecting\r
-        if ((new RegExp('\\b' + _kiwi.gateway.get('nick') + '\\b', 'i')).test(msg.msg)) {\r
+        if ((new RegExp('\\b' + _kiwi.app.connections.active_connection.get('nick') + '\\b', 'i')).test(msg.msg)) {\r
             is_highlight = true;\r
             msg_css_classes += ' highlight';\r
         }\r
@@ -406,6 +474,7 @@ _kiwi.view.Panel = Backbone.View.extend({
 \r
         } else if (is_highlight) {\r
             _kiwi.app.view.alertWindow('* People are talking!');\r
+            _kiwi.app.view.playSound('highlight');\r
             this.alert('highlight');\r
 \r
         } else {\r
@@ -416,6 +485,11 @@ _kiwi.view.Panel = Backbone.View.extend({
             this.alert('activity');\r
         }\r
 \r
+        if (this.model.isQuery() && !this.model.isActive()) {\r
+            _kiwi.app.view.alertWindow('* People are talking!');\r
+            _kiwi.app.view.playSound('highlight');\r
+        }\r
+\r
         // Update the activity counters\r
         (function () {\r
             // Only inrement the counters if we're not the active panel\r
@@ -502,7 +576,7 @@ _kiwi.view.Panel = Backbone.View.extend({
         var $this = this.$el;\r
 \r
         // Hide all other panels and show this one\r
-        this.$container.children().css('display', 'none');\r
+        this.$container.children('.panel').css('display', 'none');\r
         $this.css('display', 'block');\r
 \r
         // Show this panels memberlist\r
@@ -515,14 +589,14 @@ _kiwi.view.Panel = Backbone.View.extend({
             $('#memberlists').addClass('disabled').children().removeClass('active');\r
         }\r
 \r
-        _kiwi.app.view.doLayout();\r
-\r
         // Remove any alerts and activity counters for this panel\r
         this.alert('none');\r
         this.model.tab.find('.activity').text('0').addClass('zero');\r
 \r
-        this.trigger('active', this.model);\r
-        _kiwi.app.panels.trigger('active', this.model, _kiwi.app.panels.active);\r
+        _kiwi.app.panels.trigger('active', this.model, _kiwi.app.panels().active);\r
+        this.model.trigger('active', this.model);\r
+\r
+        _kiwi.app.view.doLayout();\r
 \r
         this.scrollToBottom(true);\r
     },\r
@@ -530,7 +604,7 @@ _kiwi.view.Panel = Backbone.View.extend({
 \r
     alert: function (level) {\r
         // No need to highlight if this si the active panel\r
-        if (this.model == _kiwi.app.panels.active) return;\r
+        if (this.model == _kiwi.app.panels().active) return;\r
 \r
         var types, type_idx;\r
         types = ['none', 'action', 'activity', 'highlight'];\r
@@ -567,7 +641,7 @@ _kiwi.view.Panel = Backbone.View.extend({
     // Scroll to the bottom of the panel\r
     scrollToBottom: function (force_down) {\r
         // If this isn't the active panel, don't scroll\r
-        if (this.model !== _kiwi.app.panels.active) return;\r
+        if (this.model !== _kiwi.app.panels().active) return;\r
 \r
         // Don't scroll down if we're scrolled up the panel a little\r
         if (force_down || this.$container.scrollTop() + this.$container.height() > this.$el.outerHeight() - 150) {\r
@@ -607,18 +681,50 @@ _kiwi.view.Channel = _kiwi.view.Panel.extend({
         if (typeof topic !== 'string' || !topic) {\r
             topic = this.model.get("topic");\r
         }\r
-        \r
+\r
         this.model.addMsg('', '== Topic for ' + this.model.get('name') + ' is: ' + topic, 'topic');\r
 \r
         // If this is the active channel then update the topic bar\r
-        if (_kiwi.app.panels.active === this) {\r
+        if (_kiwi.app.panels().active === this) {\r
             _kiwi.app.topicbar.setCurrentTopic(this.model.get("topic"));\r
         }\r
     }\r
 });\r
 \r
+\r
+\r
+// Model for this = _kiwi.model.NetworkPanelList\r
+_kiwi.view.NetworkTabs = Backbone.View.extend({\r
+    tagName: 'ul',\r
+    className: 'connections',\r
+\r
+    initialize: function() {\r
+        this.model.on('add', this.networkAdded, this);\r
+        this.model.on('remove', this.networkRemoved, this);\r
+\r
+        this.$el.appendTo($('#kiwi #tabs'));\r
+    },\r
+\r
+    networkAdded: function(network) {\r
+        $('<li class="connection"></li>')\r
+            .append(network.panels.view.$el)\r
+            .appendTo(this.$el);\r
+    },\r
+\r
+    networkRemoved: function(network) {\r
+        network.panels.view.remove();\r
+\r
+        _kiwi.app.view.doLayout();\r
+    }\r
+});\r
+\r
+\r
+\r
 // Model for this = _kiwi.model.PanelList\r
 _kiwi.view.Tabs = Backbone.View.extend({\r
+    tagName: 'ul',\r
+    className: 'panellist',\r
+\r
     events: {\r
         'click li': 'tabClick',\r
         'click li .part': 'partClick'\r
@@ -631,31 +737,43 @@ _kiwi.view.Tabs = Backbone.View.extend({
 \r
         this.model.on('active', this.panelActive, this);\r
 \r
-        this.tabs_applets = $('ul.applets', this.$el);\r
-        this.tabs_msg = $('ul.channels', this.$el);\r
+        // Network tabs start with a server, so determine what we are now\r
+        this.is_network = false;\r
 \r
-        _kiwi.gateway.on('change:name', function (gateway, new_val) {\r
-            $('span', this.model.server.tab).text(new_val);\r
-        }, this);\r
+        if (this.model.network) {\r
+            this.is_network = true;\r
+\r
+            this.model.network.on('change:name', function (network, new_val) {\r
+                $('span', this.model.server.tab).text(new_val);\r
+            }, this);\r
+        }\r
     },\r
+\r
     render: function () {\r
         var that = this;\r
 \r
-        this.tabs_msg.empty();\r
+        this.$el.empty();\r
         \r
-        // Add the server tab first\r
-        this.model.server.tab\r
-            .data('panel_id', this.model.server.cid)\r
-            .appendTo(this.tabs_msg);\r
+        if (this.is_network) {\r
+            // Add the server tab first\r
+            this.model.server.tab\r
+                .data('panel', this.model.server)\r
+                .data('connection_id', this.model.network.get('connection_id'))\r
+                .appendTo(this.$el);\r
+        }\r
 \r
         // Go through each panel adding its tab\r
         this.model.forEach(function (panel) {\r
             // If this is the server panel, ignore as it's already added\r
-            if (panel == that.model.server) return;\r
+            if (this.is_network && panel == that.model.server)\r
+                return;\r
+\r
+            panel.tab.data('panel', panel);\r
+\r
+            if (this.is_network)\r
+                panel.tab.data('connection_id', this.model.network.get('connection_id'));\r
 \r
-            panel.tab\r
-                .data('panel_id', panel.cid)\r
-                .appendTo(panel.isApplet() ? this.tabs_applets : this.tabs_msg);\r
+            panel.tab.appendTo(that.$el);\r
         });\r
 \r
         _kiwi.app.view.doLayout();\r
@@ -671,10 +789,15 @@ _kiwi.view.Tabs = Backbone.View.extend({
 \r
         if (panel.isServer()) {\r
             panel.tab.addClass('server');\r
+            panel.tab.addClass('icon-nonexistant');\r
         }\r
 \r
-        panel.tab.data('panel_id', panel.cid)\r
-            .appendTo(panel.isApplet() ? this.tabs_applets : this.tabs_msg);\r
+        panel.tab.data('panel', panel);\r
+\r
+        if (this.is_network)\r
+            panel.tab.data('connection_id', this.model.network.get('connection_id'));\r
+\r
+        panel.tab.appendTo(this.$el);\r
 \r
         panel.bind('change:title', this.updateTabTitle);\r
         _kiwi.app.view.doLayout();\r
@@ -688,22 +811,21 @@ _kiwi.view.Tabs = Backbone.View.extend({
 \r
     panelActive: function (panel, previously_active_panel) {\r
         // Remove any existing tabs or part images\r
-        $('.part', this.$el).remove();\r
-        this.tabs_applets.children().removeClass('active');\r
-        this.tabs_msg.children().removeClass('active');\r
+        _kiwi.app.view.$el.find('.panellist .part').remove();\r
+        _kiwi.app.view.$el.find('.panellist .active').removeClass('active');\r
 \r
         panel.tab.addClass('active');\r
 \r
         // Only show the part image on non-server tabs\r
         if (!panel.isServer()) {\r
-            panel.tab.append('<span class="part"></span>');\r
+            panel.tab.append('<span class="part icon-nonexistant"></span>');\r
         }\r
     },\r
 \r
     tabClick: function (e) {\r
         var tab = $(e.currentTarget);\r
 \r
-        var panel = this.model.getByCid(tab.data('panel_id'));\r
+        var panel = tab.data('panel');\r
         if (!panel) {\r
             // A panel wasn't found for this tab... wadda fuck\r
             return;\r
@@ -714,26 +836,26 @@ _kiwi.view.Tabs = Backbone.View.extend({
 \r
     partClick: function (e) {\r
         var tab = $(e.currentTarget).parent();\r
-        var panel = this.model.getByCid(tab.data('panel_id'));\r
+        var panel = this.model.getByCid(tab.data('panel'));\r
 \r
         // Only need to part if it's a channel\r
         // If the nicklist is empty, we haven't joined the channel as yet\r
         if (panel.isChannel() && panel.get('members').models.length > 0) {\r
-            _kiwi.gateway.part(panel.get('name'));\r
+            this.model.network.gateway.part(panel.get('name'));\r
         } else {\r
             panel.close();\r
         }\r
     },\r
 \r
     next: function () {\r
-        var next = _kiwi.app.panels.active.tab.next();\r
-        if (!next.length) next = $('li:first', this.tabs_msgs);\r
+        var next = this.$tab_container.find('.active').next();\r
+        if (!next.length) next = $('li:first', this.$tab_container);\r
 \r
         next.click();\r
     },\r
     prev: function () {\r
-        var prev = _kiwi.app.panels.active.tab.prev();\r
-        if (!prev.length) prev = $('li:last', this.tabs_msgs);\r
+        var prev = this.$tab_container.find('.active').prev();\r
+        if (!prev.length) prev = $('li:last', this.$tab_container);\r
 \r
         prev.click();\r
     }\r
@@ -766,13 +888,13 @@ _kiwi.view.TopicBar = Backbone.View.extend({
             inp_val = inp.text();\r
         \r
         // Only allow topic editing if this is a channel panel\r
-        if (!_kiwi.app.panels.active.isChannel()) {\r
+        if (!_kiwi.app.panels().active.isChannel()) {\r
             return false;\r
         }\r
 \r
         // If hit return key, update the current topic\r
         if (ev.keyCode === 13) {\r
-            _kiwi.gateway.topic(_kiwi.app.panels.active.get('name'), inp_val);\r
+            _kiwi.gateway.topic(_kiwi.app.panels().active.get('name'), inp_val);\r
             return false;\r
         }\r
     },\r
@@ -805,8 +927,18 @@ _kiwi.view.ControlBox = Backbone.View.extend({
         // Hold tab autocomplete data\r
         this.tabcomplete = {active: false, data: [], prefix: ''};\r
 \r
-        _kiwi.gateway.bind('change:nick', function () {\r
-            $('.nick', that.$el).text(this.get('nick'));\r
+        // Keep the nick view updated with nick changes\r
+        _kiwi.app.connections.on('change:nick', function(connection) {\r
+            // Only update the nick view if it's the active connection\r
+            if (connection !== _kiwi.app.connections.active_connection)\r
+                return;\r
+\r
+            $('.nick', that.$el).text(connection.get('nick'));\r
+        });\r
+\r
+        // Update our nick view as we flick between connections\r
+        _kiwi.app.connections.on('active', function(panel, connection) {\r
+            $('.nick', that.$el).text(connection.get('nick'));\r
         });\r
     },\r
 \r
@@ -877,11 +1009,19 @@ _kiwi.view.ControlBox = Backbone.View.extend({
             this.tabcomplete.active = true;\r
             if (_.isEqual(this.tabcomplete.data, [])) {\r
                 // Get possible autocompletions\r
-                var ac_data = [];\r
-                $.each(_kiwi.app.panels.active.get('members').models, function (i, member) {\r
+                var ac_data = [],\r
+                    members = _kiwi.app.panels().active.get('members');\r
+\r
+                // If we have a members list, get the models. Otherwise empty array\r
+                members = members ? members.models : [];\r
+\r
+                $.each(members, function (i, member) {\r
                     if (!member) return;\r
                     ac_data.push(member.get('nick'));\r
                 });\r
+\r
+                ac_data.push(_kiwi.app.panels().active.get('name'));\r
+\r
                 ac_data = _.sortBy(ac_data, function (nick) {\r
                     return nick;\r
                 });\r
@@ -893,12 +1033,20 @@ _kiwi.view.ControlBox = Backbone.View.extend({
             }\r
             \r
             (function () {\r
-                var tokens = inp_val.substring(0, inp[0].selectionStart).split(' '),\r
-                    val,\r
-                    p1,\r
-                    newnick,\r
-                    range,\r
-                    nick = tokens[tokens.length - 1];\r
+                var tokens,              // Words before the cursor position\r
+                    val,                 // New value being built up\r
+                    p1,                  // Position in the value just before the nick \r
+                    newnick,             // New nick to be displayed (cycles through)\r
+                    range,               // TextRange for setting new text cursor position\r
+                    nick,                // Current nick in the value\r
+                    trailing = ': ';     // Text to be inserted after a tabbed nick\r
+\r
+                tokens = inp_val.substring(0, inp[0].selectionStart).split(' ');\r
+                if (tokens[tokens.length-1] == ':')\r
+                    tokens.pop();\r
+\r
+                nick  = tokens[tokens.length - 1];\r
+\r
                 if (this.tabcomplete.prefix === '') {\r
                     this.tabcomplete.prefix = nick;\r
                 }\r
@@ -908,21 +1056,31 @@ _kiwi.view.ControlBox = Backbone.View.extend({
                 });\r
 \r
                 if (this.tabcomplete.data.length > 0) {\r
+                    // Get the current value before cursor position\r
                     p1 = inp[0].selectionStart - (nick.length);\r
                     val = inp_val.substr(0, p1);\r
+\r
+                    // Include the current selected nick\r
                     newnick = this.tabcomplete.data.shift();\r
                     this.tabcomplete.data.push(newnick);\r
                     val += newnick;\r
+\r
+                    if (inp_val.substr(inp[0].selectionStart, 2) !== trailing)\r
+                        val += trailing;\r
+\r
+                    // Now include the rest of the current value\r
                     val += inp_val.substr(inp[0].selectionStart);\r
+\r
                     inp.val(val);\r
 \r
+                    // Move the cursor position to the end of the nick\r
                     if (inp[0].setSelectionRange) {\r
-                        inp[0].setSelectionRange(p1 + newnick.length, p1 + newnick.length);\r
+                        inp[0].setSelectionRange(p1 + newnick.length + trailing.length, p1 + newnick.length + trailing.length);\r
                     } else if (inp[0].createTextRange) { // not sure if this bit is actually needed....\r
                         range = inp[0].createTextRange();\r
                         range.collapse(true);\r
-                        range.moveEnd('character', p1 + newnick.length);\r
-                        range.moveStart('character', p1 + newnick.length);\r
+                        range.moveEnd('character', p1 + newnick.length + trailing.length);\r
+                        range.moveStart('character', p1 + newnick.length + trailing.length);\r
                         range.select();\r
                     }\r
                 }\r
@@ -942,12 +1100,12 @@ _kiwi.view.ControlBox = Backbone.View.extend({
             command_raw = command_raw.replace(/^\/\//, '/');\r
 \r
             // Prepend the default command\r
-            command_raw = '/msg ' + _kiwi.app.panels.active.get('name') + ' ' + command_raw;\r
+            command_raw = '/msg ' + _kiwi.app.panels().active.get('name') + ' ' + command_raw;\r
         }\r
 \r
         // Process the raw command for any aliases\r
-        this.preprocessor.vars.server = _kiwi.gateway.get('name');\r
-        this.preprocessor.vars.channel = _kiwi.app.panels.active.get('name');\r
+        this.preprocessor.vars.server = _kiwi.app.connections.active_connection.get('name');\r
+        this.preprocessor.vars.channel = _kiwi.app.panels().active.get('name');\r
         this.preprocessor.vars.destination = this.preprocessor.vars.channel;\r
         command_raw = this.preprocessor.process(command_raw);\r
 \r
@@ -959,7 +1117,7 @@ _kiwi.view.ControlBox = Backbone.View.extend({
         } else {\r
             // Default command\r
             command = 'msg';\r
-            params.unshift(_kiwi.app.panels.active.get('name'));\r
+            params.unshift(_kiwi.app.panels().active.get('name'));\r
         }\r
 \r
         // Trigger the command events\r
@@ -971,6 +1129,13 @@ _kiwi.view.ControlBox = Backbone.View.extend({
         if (!this._callbacks['command:' + command]) {\r
             this.trigger('unknown_command', {command: command, params: params});\r
         }\r
+    },\r
+\r
+\r
+    addPluginIcon: function ($icon) {\r
+        var $tool = $('<div class="tool"></div>').append($icon);\r
+        this.$el.find('.input_tools').append($tool);\r
+        _kiwi.app.view.doLayout();\r
     }\r
 });\r
 \r
@@ -1098,6 +1263,8 @@ _kiwi.view.Application = Backbone.View.extend({
                 return 'This will close all KiwiIRC conversations. Are you sure you want to close this window?';\r
             }\r
         };\r
+\r
+        this.initSound();\r
     },\r
 \r
 \r
@@ -1142,7 +1309,7 @@ _kiwi.view.Application = Backbone.View.extend({
         if (show_timestamps === _kiwi.global.settings) {\r
             show_timestamps = arguments[1];\r
         }\r
-        \r
+\r
         if (show_timestamps) {\r
             this.$el.addClass('timestamps');\r
         } else {\r
@@ -1219,6 +1386,9 @@ _kiwi.view.Application = Backbone.View.extend({
             // And move the handle just out of sight to the right\r
             el_resize_handle.css('left', el_panels.outerWidth(true));\r
         }\r
+\r
+        var input_wrap_width = parseInt($('#kiwi #controlbox .input_tools').outerWidth());\r
+        el_controlbox.find('.input_wrap').css('right', input_wrap_width + 7);\r
     },\r
 \r
 \r
@@ -1317,6 +1487,40 @@ _kiwi.view.Application = Backbone.View.extend({
             $('#controlbox').slideDown(0);\r
             this.doLayout();\r
         }\r
+    },\r
+\r
+\r
+    initSound: function () {\r
+        var that = this,\r
+            base_path = this.model.get('base_path');\r
+\r
+        $script(base_path + '/assets/libs/soundmanager2/soundmanager2-nodebug-jsmin.js', function() {\r
+            if (typeof soundManager === 'undefined')\r
+                return;\r
+\r
+            soundManager.setup({\r
+                url: base_path + '/assets/libs/soundmanager2/',\r
+                flashVersion: 9, // optional: shiny features (default = 8)// optional: ignore Flash where possible, use 100% HTML5 mode\r
+                preferFlash: true,\r
+\r
+                onready: function() {\r
+                    that.sound_object = soundManager.createSound({\r
+                        id: 'highlight',\r
+                        url: base_path + '/assets/sound/highlight.mp3'\r
+                    });\r
+                }\r
+            });\r
+        });\r
+    },\r
+\r
+\r
+    playSound: function (sound_id) {\r
+        if (!this.sound_object) return;\r
+\r
+        if (_kiwi.global.settings.get('mute_sounds'))\r
+            return;\r
+        \r
+        soundManager.play(sound_id);\r
     }\r
 });\r
 \r
@@ -1447,4 +1651,105 @@ _kiwi.view.MediaMessage = Backbone.View.extend({
 \r
         return html;\r
     }\r
-});
\ No newline at end of file
+});\r
+\r
+\r
+\r
+_kiwi.view.MenuBox = Backbone.View.extend({\r
+    events: {\r
+        'click .ui_menu_foot .close': 'dispose'\r
+    },\r
+\r
+    initialize: function(title) {\r
+        var that = this;\r
+\r
+        this.$el = $('<div class="ui_menu"></div>');\r
+\r
+        this._title = title || '';\r
+        this._items = {};\r
+        this._display_footer = true;\r
+        this._close_on_blur = true;\r
+\r
+        this._close_proxy = function(event) {\r
+            that.onDocumentClick(event);\r
+        };\r
+        $(document).on('click', this._close_proxy);\r
+    },\r
+\r
+\r
+    render: function() {\r
+        var that = this;\r
+\r
+        this.$el.find('*').remove();\r
+\r
+        if (this._title) {\r
+            $('<div class="ui_menu_title"></div>')\r
+                .text(this._title)\r
+                .appendTo(this.$el);\r
+        }\r
+\r
+\r
+        _.each(this._items, function(item) {\r
+            var $item = $('<div class="ui_menu_content hover"></div>')\r
+                .append(item);\r
+\r
+            that.$el.append($item);\r
+        });\r
+\r
+        if (this._display_footer)\r
+            this.$el.append('<div class="ui_menu_foot"><a class="close" onclick="">Close <i class="icon-remove"></i></a></div>');\r
+    },\r
+\r
+\r
+    onDocumentClick: function(event) {\r
+        var $target = $(event.target);\r
+\r
+        if (!this._close_on_blur)\r
+            return;\r
+\r
+        // If this is not itself AND we don't contain this element, dispose $el\r
+        if ($target[0] != this.$el[0] && this.$el.has($target).length === 0)\r
+            this.dispose();\r
+    },\r
+\r
+\r
+    dispose: function() {\r
+        _.each(this._items, function(item) {\r
+            item.dispose && item.dispose();\r
+            item.remove && item.remove();\r
+        });\r
+\r
+        this._items = null;\r
+        this.remove();\r
+\r
+        $(document).off('click', this._close_proxy);\r
+    },\r
+\r
+\r
+    addItem: function(item_name, $item) {\r
+        $item = $($item);\r
+        if ($item.is('a')) $item.addClass('icon-chevron-right');\r
+        this._items[item_name] = $item;\r
+    },\r
+\r
+\r
+    removeItem: function(item_name) {\r
+        delete this._items[item_name];\r
+    },\r
+\r
+\r
+    showFooter: function(show) {\r
+        this._display_footer = show;\r
+    },\r
+\r
+\r
+    closeOnBlur: function(close_it) {\r
+        this._close_on_blur = close_it;\r
+    },\r
+\r
+\r
+    show: function() {\r
+        this.render();\r
+        this.$el.appendTo(_kiwi.app.view.$el);\r
+    }\r
+});\r