function tinymceAttachCharCounter(ed) {
    /*
       tinymce editor (ed.getBody()), it is a <body> with DOM in editorMode.
       Gecko: DOMCharacterDataModified, DOMSubtreeModified, paste
       WebKit: DOMCharacterDataModified, DOMSubtreeModified, paste
       Opera: keyup only
       IE: onkeyup, onpaste, oncut, ondragend
     */
    el = ed.getBody();
 
    // First check most speific engines, and end with generic standard events.
    if (BEDetector.engine.is.Gecko) {
        // Gecko sometimes looses DOMCharacterDataModified
        el.addEventListener('DOMSubtreeModified',
            function(e) {
                return function() { tinymceUpdateCharCounter(e) }
            }(ed),
            false);
    } 
    else if (BEDetector.engine.is.WebKit) {
        el.addEventListener('DOMCharacterDataModified',
            function(e) {
                return function() { tinymceUpdateCharCounter(e) }
            }(ed),
            false);
    } 
    else if (BEDetector.engine.is.MSHTML) {
        el.attachEvent('onkeyup',
            function(e) {
                return function() { tinymceUpdateCharCounter(e) }
            }(ed) );
        el.attachEvent('onpaste',
            function(e) {
                return function() { tinymceUpdateCharCounter(e) }
            }(ed) );
        el.attachEvent('oncut',
            function(e) {
                return function() { tinymceUpdateCharCounter(e) }
            }(ed) );
        el.attachEvent('ondragend',
            function(e) {
                return function() { tinymceUpdateCharCounter(e) }
            }(ed) );
    } else {
        // Fall to generic standard support, also Opera is here
        el.addEventListener('keyup',
            function(e) {
                return function() { tinymceUpdateCharCounter(e) }
            }(ed),
            false);
        // Not Opera, maybe KHTML (DOMCharacterDataModified, also DOMSubtreeModified)
        el.addEventListener('DOMCharacterDataModified',
            function(e) {
                return function() { tinymceUpdateCharCounter(e) }
            }(ed),
            false);
    }
}





function tinymceAttachCharCounter(ed) {
    ed.addEventListener('DOMSubtreeModified',
        function(e) {
            return function() { tinymceUpdateCharCounter(e) }
        }(ed),
        false);
}





function updateCounterText(id, n) {
    var el = document.getElementById(id);
    if (n <= 0) {
        el.innerHTML = '<span style="color:#8a1f11">No characters left.</span>';
    } else {
        if (n == 1)
            el.innerHTML = "1 character left.";
        else
            el.innerHTML = n + " characters left.";
    }
}
 
function tinymceGetLength(oEditor) 
{
    // Get the Editor Area DOM (Document object).
    var oDOM = oEditor.getDoc() ;
 
    var iLength ;
    // The are two diffent ways to get the text (without HTML markups).
    // It is browser specific.
    if (document.all) {
        // If Internet Explorer.
        iLength = oDOM.body.innerText.length;
    } else {
        // If Gecko.
        var r = oDOM.createRange() ;
        r.selectNodeContents(oDOM.body);
        iLength = r.toString().length;
    }
 
    return iLength;
}



 
function tinymceUpdateCharCounter(ed)
{
    // To make things faster, remove bookmarks.
    currCount = tinymceGetLength(ed);
    remainCount = tinymceMaxChars - currCount + 1; //fix: allow +1
 
    if (remainCount <= 0) {
        // Need to use own undo for small events.
        //ed.execCommand('Undo');
        if (tinymceUndoBuffer[ed.editorId]) {
            ed.setContent(tinymceUndoBuffer[ed.editorId]);
            ed.selection.moveToBookmark(tinymceUndoBookmark[ed.editorId]);
        }
    } else {
        tinymceUndoBuffer[ed.editorId] = ed.getContent();
        tinymceUndoBookmark[ed.editorId] = ed.selection.getBookmark();
    }
 
    //-1 to counteract above +1
    updateCounterText('msgCounter_' + ed.editorId, remainCount - 1);
}
