/*
 * =================================================================
 * HTML Editor - A WYSIWYG web based editor for IE5.5+ and Mozilla v1.4+
 *
 *   Website  : http://gossamer-threads.com/
 *   Support  : http://gossamer-threads.com/scripts/support/
 *   Revision : $Id: editor.js,v 1.12 2009/05/09 05:52:50 brewt Exp $
 *
 * Copyright (c) 2005 Gossamer Threads Inc.  All Rights Reserved.
 * Redistribution in part or in whole strictly prohibited. Please
 * see LICENSE file for full details.
 * =================================================================
 */

var dialogWindow = {};

function advancedEditor() {
    this.config = {
        imageURL          : 'images',
        imageDialogURL    : '',
        defaultFont       : 'Arial',
        defaultFontSize   : '3',
        defaultForeground : '#000000',
        defaultBackground : '#FFFFFF',
        focusOnload       : false
    };

    this.id = {
        editorFrame     : 'editor_iframe',
        toolbarArea     : 'toolbar_area',
        editableFrame   : 'editable_iframe',
        editableContent : 'editable_content',
        editableSource  : 'editable_source',
        form            : '',
        content         : ''
    };

// command, image, title, updateType(0 = don't update, 1 = queryCommandState, 2 = queryCommandEnabled, 3 = customQueryCommandState, 4 = customQueryCommandEnabled), customCommand, customStateCommand, useCSS (mozilla)
    this.toolbar = [
        {
            buttons : {
                copy  : ['Copy',  'copy.gif',  'Copy',  2],
                cut   : ['Cut',   'cut.gif',   'Cut',   2],
                paste : ['Paste', 'paste.gif', 'Paste', 2]
            },
            num_buttons : 3,
            hidden : isIE ? 0 : 1
        },
// The undo/redo buttons don't work too reliably and the buffer is lost after
// doing things like resizing (changing any properties of the contenteditable
// area will wipe it), or custom commands.
/*
        {
            buttons : {
                undo : ['Undo', 'undo.gif', 'Undo', 2],
                redo : ['Redo', 'redo.gif', 'Redo', 2]
            },
            num_buttons : 2
        },
*/
        {
            buttons : {
                bold      : ['Bold',      'bold.gif',      'Bold',      1],
                italic    : ['Italic',    'italic.gif',    'Italic',    1],
                underline : ['Underline', 'underline.gif', 'Underline', 1]
            },
            num_buttons : 3
        },
        {
            buttons : {
                quote : ['', 'quote.gif', 'Quote', 0, 'this.insertQuote()']
/*
                reply : ['', 'reply.gif', 'Reply', 0, 'this.insertReply()'],
                code  : ['', 'code.gif',  'Code',  0, 'this.insertCode()']
*/
            },
            num_buttons : 3
        },
        {
            buttons : {
                alignleft  : ['JustifyLeft',   'alignleft.gif',  'Align Left',  1],
                center     : ['JustifyCenter', 'center.gif',     'Center',      1],
                alignright : ['JustifyRight',  'alignright.gif', 'Align Right', 1]
            },
            num_buttons : 3
        },
        {
            buttons : {
                numlist : ['InsertOrderedList',   'numlist.gif', 'Numbering',       1],
                bullist : ['InsertUnorderedList', 'bullist.gif', 'Bullets',         1],
                outdent : ['Outdent',             'outdent.gif', 'Decrease Indent', 0],
                indent  : ['Indent',              'indent.gif',  'Increase Indent', 0]
            },
            num_buttons : 4
        },
        {
            buttons : {
                font    : ['FontName', 'font.gif',      'Font',             0, 'this.fontDialog()'],
                color   : ['',         'color.gif',     'Color',            0, 'this.colorDialog("foreground")']
            },
            num_buttons : 2
        },
        {
            buttons : {
                hr    : ['InsertHorizontalRule', 'hr.gif',    'Horizontal Rule', 0],
                image : ['',                     'image.gif', 'Insert Image',    0, 'this.imageDialog()'],
                link  : ['',                     'link.gif',  'Create Link',     4, 'this.linkDialog()', 'this.selectionMade()']
            },
            num_buttons : 3
        },
        {
            buttons : {
                source : ['', 'source.gif', 'Toggle HTML Source', 3, 'this.toggleSource()', 'this.sourceMode']
            },
            num_buttons : 1
        }
    ];
    this.toolbarMapping = {};
    this.toolbarQueryCommandState = {};
    this.toolbarQueryCommandEnabled = {};

    this.objects         = {};
    this.loadingInterval = 0;
    this.initCount       = 0;
    this.toolbarInterval = 0;
    this.selection       = null;
    this.loaded          = false;
    this.sourceMode      = false;

// TODO remove this and get values from currentStyle
// Dimensions of various objects
// *Extra are padding/border/margin widths that need to be added to properly
// calculate object total widths
    this.editorFrameExtra     = 2;
    this.editableContentExtra = 10;
    this.toolbarExtra         = 2;
    this.toolbarHeight        = 26;
    this.buttonWidth          = 25;
    this.separatorWidth       = 8;
}

advancedEditor.prototype.load = function (form, content) {
    var self = this;
    if (form)
        this.id.form = form;
    if (content)
        this.id.content = content;
    this.loadingInterval = setInterval(function () { self.init() }, 100);
}

advancedEditor.prototype.init = function () {
    if (this.initCount++ > 100) {
        clearInterval(this.loadingInterval);
        return;
    }

    var self = this;
    this.objects.editorFrame = document.getElementById(this.id.editorFrame);
    if (!this.objects.editorFrame)
        return;

    this.objects.editorFrameDoc = this.objects.editorFrame.contentWindow.document;
    this.objects.editableSource = this.objects.editorFrameDoc.getElementById(this.id.editableSource);
    this.objects.editableFrame  = this.objects.editorFrameDoc.getElementById(this.id.editableFrame);
    if (!this.objects.editableFrame)
        return;
    if (!this.objects.editableSource)
        this.deleteButton('source');

    this.objects.editableFrameDoc = this.objects.editableFrame.contentWindow.document;
    this.objects.editableContent  = this.objects.editableFrameDoc.getElementById(this.id.editableContent);
    if (!this.objects.editableContent)
        return;

    this.objects.toolbarArea = this.objects.editorFrameDoc.getElementById(this.id.toolbarArea);
    if (!this.objects.toolbarArea)
        return;

    clearInterval(this.loadingInterval);

    this.objects.form = document.getElementById(this.id.form);
    this.objects.content = document.getElementById(this.id.content);
    this.objects.editableContent.innerHTML = this.objects.content.value;
    registerEvent(this.objects.form, 'submit', function () {
        self.objects.content.value = self.getContent();
    });
// Before leaving the page, make sure the content is put back into the input so that the content isn't lost if the user goes back to the page
    registerEvent(window, 'beforeunload', function () {
        self.objects.content.value = self.getContent();
    });

// Tabbing from the subject takes you to the iframe in IE, but the document in Mozilla
    registerEvent(this.objects.editorFrame, 'focus', function () { self.focus() });
    registerEvent(this.objects.editorFrameDoc, 'focus', function () { self.focus() });

    var width = this.objects.editorFrame.offsetWidth - this.editorFrameExtra;
    var toolbar;
    for (var i = 0; i < this.toolbar.length; i++) {
        if (this.toolbar[i].hidden || this.toolbar[i].num_buttons <= 0)
            continue;

        var group = this.objects.editorFrameDoc.createElement('div');
        group.aewidth = this.buttonWidth * this.toolbar[i].num_buttons;

        if (!toolbar || toolbar.aewidth + this.toolbarExtra + this.separatorWidth + group.aewidth > width)
            toolbar = this.addToolbar();
        else
            this.addSeparator(toolbar);

        this.addButtonGroup(toolbar, group);

        for (var item in this.toolbar[i].buttons) {
            var button = this.objects.editorFrameDoc.createElement('div');
            group.appendChild(button);

            button.id = item;
            button.enabled = true;
            button.className = 'button';
            button.style.backgroundImage = 'url(' + this.config.imageURL + '/' + this.toolbar[i].buttons[item][1] + ')';
            button.title = this.toolbar[i].buttons[item][2];
            button.setAttribute('unselectable', 'on');
            registerEvent(button, 'mouseover', function (e) { self.mouseHandler(e) });
            registerEvent(button, 'mouseout', function (e) { self.mouseHandler(e) });
            registerEvent(button, 'mousedown', function (e) { self.mouseHandler(e) });
            registerEvent(button, 'click', function (e) { self.mouseHandler(e) });

            this.toolbarMapping[item] = i;
            if (this.toolbar[i].buttons[item][3] == 1 || this.toolbar[i].buttons[item][3] == 3)
                this.toolbarQueryCommandState[item] = i;
            else if (this.toolbar[i].buttons[item][3] == 2 || this.toolbar[i].buttons[item][3] == 4)
                this.toolbarQueryCommandEnabled[item] = i;
        }
    }

    this.objects.editableContent.style.fontFamily = this.config.defaultFont;
    var fontsize = {
        1 : 'xx-small',
        2 : 'x-small',
        3 : 'small',
        4 : 'medium',
        5 : 'large',
        6 : 'x-large',
        7 : 'xx-large'
    };
    this.objects.editableContent.style.fontSize = fontsize[parseInt(this.config.defaultFontSize) + (isIE ? 0 : 1)];
    this.objects.editableContent.style.backgroundColor = this.config.defaultBackground;

    this.objects.editableFrameDoc.body.contentEditable = true;
    if (!isIE)
        this.objects.editableFrameDoc.designMode = 'on';

// This actually turns *off* the usage of css
    if (isMozilla)
        this.execCommand("useCSS", true);

    this.toolbarInterval = setInterval(function () { self.updateButtonStatus(); }, 250);

    registerEvent(this.objects.editableContent, 'dblclick', function (e) {
        if (!e) e = self.objects.editableFrame.contentWindow.event;
        var target = e.target ? e.target : e.srcElement;

        if (target.tagName == 'IMG')
            self.imageDialog();
    });

    registerEvent(self.objects.editorFrame.contentWindow, 'resize', function () { self.resize(); });

    if (this.config.focusOnload)
        this.focus();

    this.loaded = true;
}

advancedEditor.prototype.getContent = function (bodyOnly) {
    var content = this.objects.editableContent.innerHTML;
    if (content) {
// Perform some clean up on the editor generated HTML
        if (isIE)
            content = content.replace(/<P>/g, '').replace(/<\/P>/g, '<BR>').replace(/\r?\n/g, '');
        else
            content = content.replace(/<br><(ol|ul)>/g, "<$1>");
        if (content.match(/^(?:&nbsp;)?<br>\n*$/i))
            content = '';
        if (bodyOnly)
            return content;
        else {
            var bgcolor = advancedEditor.getHexColor(this.objects.editableContent.style.backgroundColor);
            var html = '<html><body';
            if (bgcolor)
                html += ' bgcolor="' + bgcolor + '"';
            html += '>' + content + '</body></html>';
            return html;
        }
    }
    return '';
}

advancedEditor.prototype.setContent = function (html) {
    this.objects.editableContent.innerHTML = html;
}

advancedEditor.prototype.enable = function () {
    this.changeButtonState(true);
}

advancedEditor.prototype.disable = function () {
    this.changeButtonState(false);
}

advancedEditor.prototype.deleteButton = function () {
    var args = advancedEditor.prototype.deleteButton.arguments;
    if (this.loaded || !args.length) {
        alert("Can't call deleteButton() after the editor has loaded!");
        return;
    }

    var del = {};
    for (var i = 0; i < args.length; i++)
        del[args[i]] = 1;

    for (var i = 0; i < this.toolbar.length; i++) {
        for (var button in this.toolbar[i].buttons) {
            if (del[button]) {
                delete this.toolbar[i].buttons[button];
                delete del[button];
                this.toolbar[i].num_buttons--;
                if (!del) break;
            }
        }
    }
}

advancedEditor.prototype.changeButtonState = function (enabled) {
    var args = advancedEditor.prototype.changeButtonState.arguments;

    var leave = {};
    for (var i = 0; i < args.length; i++)
        leave[args[i]] = 1;

    for (var id in this.toolbarMapping) {
        if (leave[id])
            continue;

        var button = this.objects.editorFrameDoc.getElementById(id);
        if (enabled) {
            button.enabled = button.previousState;
            button.className = button.previousClass;
            button.noUpdates = false;
        }
        else {
            button.noUpdates = true;
            button.previousState = button.enabled;
            button.enabled = false;
            button.previousClass = button.className;
            button.className = 'button button-disabled';
        }
    }
}

advancedEditor.prototype.addToolbar = function () {
    var toolbar = this.objects.editorFrameDoc.createElement('div');
    this.objects.toolbarArea.appendChild(toolbar);
    toolbar.aewidth = 0;
    toolbar.className = 'toolbar';
    toolbar.setAttribute('unselectable', 'on');

    this.objects.editableFrame.style.height = this.objects.editorFrame.offsetHeight - this.editorFrameExtra - this.toolbarHeight * this.objects.toolbarArea.childNodes.length + 'px';

    return toolbar;
}

advancedEditor.prototype.addButtonGroup = function (toolbar, group, beforeElement) {
    if (beforeElement)
        toolbar.insertBefore(group, beforeElement);
    else
        toolbar.appendChild(group);
    toolbar.aewidth += group.aewidth;
}

advancedEditor.prototype.addSeparator = function (toolbar, beforeElement) {
    var separator = this.objects.editorFrameDoc.createElement('div');
    separator.className = 'separator';
    separator.setAttribute('unselectable', 'on');

    if (beforeElement)
        toolbar.insertBefore(separator, beforeElement);
    else
        toolbar.appendChild(separator);
    toolbar.aewidth += this.separatorWidth;
}

advancedEditor.prototype.updateButtonStatus = function () {
    for (var item in this.toolbarQueryCommandState) {
        var buttonConfig = this.toolbar[this.toolbarQueryCommandState[item]].buttons[item];
        var command = buttonConfig[0];
        var button = this.objects.editorFrameDoc.getElementById(item);

        if (button.mousedown || button.noUpdates)
            continue;
        var previousState = button.pressed;
        if (buttonConfig[3] == 3)
            button.pressed = eval(buttonConfig[5]);
        else
            button.pressed = this.objects.editableFrameDoc.queryCommandState(command);

        if (previousState == button.pressed)
            continue;

        if (button.pressed)
            button.className = 'button button-pressed' + (button.mouseover ? ' button-pressed-mouse-over' : '');
        else
            button.className = 'button' + (button.mouseover ? ' button-mouse-over' : '');
    }

    for (var item in this.toolbarQueryCommandEnabled) {
        var buttonConfig = this.toolbar[this.toolbarQueryCommandEnabled[item]].buttons[item];
        var command = buttonConfig[0];
        var button = this.objects.editorFrameDoc.getElementById(item);

        if (button.noUpdates)
            continue;
        var enable;
        if (buttonConfig[3] == 4)
            enable = eval(buttonConfig[5]);
        else
            enable = this.objects.editableFrameDoc.queryCommandEnabled(command);

        if (button.enabled == enable)
            continue;

        if (enable) {
            button.enabled = true;
// mozilla bug loses mouseout event after changing applying the alpha transparency
            button.className = 'button' + (button.mouseover && !isMozilla ? ' button-mouse-over' : '');
        }
        else {
            button.enabled = false;
            button.className = 'button button-disabled';
        }
    }
}

advancedEditor.prototype.mouseHandler = function (e) {
    if (!e) e = this.objects.editorFrame.contentWindow.event;
    var target = e.target ? e.target : e.srcElement;

    switch (e.type) {
        case 'mouseover':
            target.mouseover = true;
            break;
        case 'mouseout':
            target.mouseover = false;
            break;
        case 'mousedown':
            target.mousedown = true;
            break;
        case 'click':
            target.mousedown = false;
            break;
    }

    if (!target.enabled)
        return;

    switch (e.type) {
        case 'mouseover':
            if (target.pressed)
                target.className = 'button button-pressed button-pressed-mouse-over';
            else
                target.className = 'button button-mouse-over';
            break;
        case 'mouseout':
            if (target.pressed)
                target.className = 'button button-pressed';
            else
                target.className = 'button';
            break;
        case 'mousedown':
            if (!target.pressed)
                target.className = 'button button-pressed button-pressed-mouse-over';
            break;
        case 'click':
// Only do something on a left click
            if ((e.which && e.which != 1) || (e.button && e.button != 1))
                return;

            var button = this.toolbar[this.toolbarMapping[target.id]].buttons[target.id];
            if (button[4])
                eval(button[4]);
            else
                this.execCommand(button[0], null, button[6]);

            if (button[3] == 1)
                target.pressed = this.objects.editableFrameDoc.queryCommandState(button[0]);
            else if (button[3] == 3)
                target.pressed = eval(button[5]);

            if (target.pressed)
                target.className = 'button button-pressed' + (target.mouseover ? ' button-pressed-mouse-over' : '');
            else
                target.className = 'button' + (target.mouseover ? ' button-mouse-over' : '');
            break;

    }
}

advancedEditor.prototype.resize = function () {
    var width = this.objects.editorFrame.offsetWidth - this.editorFrameExtra;
    var height = this.objects.editorFrame.offsetHeight - this.editorFrameExtra;
    var toolbarCount = this.objects.toolbarArea.childNodes.length;

    for (var i = 0; i < this.objects.toolbarArea.childNodes.length; i++) {
        var toolbar = this.objects.toolbarArea.childNodes[i];
// Window has shrunk, move overflowing buttons down to next toolbar
        while (toolbar.aewidth + this.toolbarExtra > width && toolbar.childNodes.length > 2) {
            var group = toolbar.removeChild(toolbar.lastChild);
            toolbar.removeChild(toolbar.lastChild);
            toolbar.aewidth -= group.aewidth + this.separatorWidth;
            var nextToolbar;
            if (nextToolbar = toolbar.nextSibling) {
                this.addSeparator(nextToolbar, nextToolbar.firstChild);
                this.addButtonGroup(nextToolbar, group, nextToolbar.firstChild);
            }
            else
                this.addButtonGroup(this.addToolbar(), group);
        }

// Window has grown, fill up empty space
        var nextToolbar;
        if (nextToolbar = toolbar.nextSibling) {
            var group = nextToolbar.firstChild;
            while (nextToolbar && group && toolbar.aewidth + this.toolbarExtra + this.separatorWidth + group.aewidth <= width) {
                nextToolbar.removeChild(group);
                nextToolbar.aewidth -= group.aewidth;
                if (nextToolbar.hasChildNodes()) {
                    nextToolbar.removeChild(nextToolbar.firstChild);
                    nextToolbar.aewidth -= this.separatorWidth;
                }
                else {
                    this.objects.toolbarArea.removeChild(nextToolbar);
                    nextToolbar = toolbar.nextSibling;
                }
                this.addSeparator(toolbar);
                this.addButtonGroup(toolbar, group);
                group = nextToolbar ? nextToolbar.firstChild : null;
            }
        }
    }

    if (toolbarCount != this.objects.toolbarArea.childNodes.length) {
        var contentHeight = height - this.toolbarHeight * this.objects.toolbarArea.childNodes.length;
        if (this.sourceMode) {
            this.objects.editableSource.style.top = this.toolbarHeight * this.objects.toolbarArea.childNodes.length + 'px';
            this.objects.editableSource.style.height = contentHeight + 'px';
// Reset height back to 0px, since addToolbar() sets it
            this.objects.editableFrame.style.height = '0px';
        }
        else
            this.objects.editableFrame.style.height = contentHeight + 'px';
    }
}

// Set focus on the editor content area
advancedEditor.prototype.focus = function (focusWindow) {
    if (focusWindow)
        window.focus();
    this.objects.editableFrame.contentWindow.focus();
}

advancedEditor.prototype.execCommand = function (cmd, value, useCSS) {
    if (isMozilla && useCSS)
        this.objects.editableFrameDoc.execCommand("useCSS", false, false);

// For some commands (eg. indent, lists), without focusing it ends up running the
// command in the toolbar area.  If you comment out the focus code, then in IE
// click in the editor, click outside the editor, then click indent, you'll see
// the problem.
    if (isIE || isWebkit)
        this.focus();

    var res;
    try {
        res = this.objects.editableFrameDoc.execCommand(cmd, false, value);
    }
    catch (err) {
//        debug("execCommand failed (" + cmd + " => " + value + ")" + err);
    }
//    debug("execCommand(" + cmd + ", " + value + ") => " + res + (useCSS ? ' (css)' : ''));

    if (isMozilla && useCSS)
        this.objects.editableFrameDoc.execCommand("useCSS", false, true);
}

advancedEditor.prototype.queryCommandValue = function (cmd, useCSS) {
    if (isMozilla && useCSS)
        this.objects.editableFrameDoc.execCommand("useCSS", false, false);

    var value = this.objects.editableFrameDoc.queryCommandValue(cmd);

    if (isMozilla && useCSS)
        this.objects.editableFrameDoc.execCommand("useCSS", false, true);
//    debug("queryCommandValue(" + cmd + ") => " + value + (useCSS ? ' (css)' : ''));

    cmd = cmd.toLowerCase();
    if (cmd == 'forecolor' || cmd == 'backcolor' || cmd == 'hilitecolor')
        return advancedEditor.getHexColor(value);
    return value;
}

advancedEditor.prototype.setColor = function () {
    this.focus(1);
    if (dialogWindow.values._type == 'foreground')
        this.execCommand('ForeColor', dialogWindow.values.hexcolor);
    else if (dialogWindow.values._type == 'background')
        this.objects.editableContent.style.backgroundColor = dialogWindow.values.hexcolor;
    else if (dialogWindow.values._type == 'highlight')
        this.execCommand(isIE ? 'BackColor' : 'HiliteColor', dialogWindow.values.hexcolor, true);
}

advancedEditor.prototype.setFont = function () {
    this.focus(1);
    if (dialogWindow.values.size)
        this.execCommand('FontSize', dialogWindow.values.size);
    if (dialogWindow.values.font)
        this.execCommand('FontName', dialogWindow.values.font);
    if (dialogWindow.values.hexcolor)
        this.execCommand('ForeColor', dialogWindow.values.hexcolor);
    if ((dialogWindow.values.style.indexOf('b') != -1) != dialogWindow.values._bold)
        this.execCommand('Bold');
    if ((dialogWindow.values.style.indexOf('i') != -1) != dialogWindow.values._italic)
        this.execCommand('Italic');
    if ((dialogWindow.values.underline == 'on') != dialogWindow.values._underline)
        this.execCommand('Underline');
}

advancedEditor.prototype.insertImage = function () {
    this.focus(1);
    var img = this.objects.editableFrameDoc.createElement('img');

    var values = dialogWindow.values;
    img.src = values['src-url-input'];

    if (values['src-inline'])
        img.setAttribute('gforuminline', values['src-inline-input']);

    var currentSelection = new advancedEditorSelection(isIE ? this.objects.editableFrameDoc : this.objects.editableFrame.contentWindow);
    currentSelection.replaceSelection(img);
}

advancedEditor.prototype.insertLink = function () {
    this.focus(1);
    if (dialogWindow.values.url)
        this.execCommand('CreateLink', dialogWindow.values.url);
}

advancedEditor.prototype.colorDialog = function (type) {
    var values = {
        hexcolor    : null,
        _type       : type,
        _foreground : null,
        _background : null,
        _highlight  : null
    };

    values._selection = new advancedEditorSelection(isIE ? this.objects.editableFrameDoc : this.objects.editableFrame.contentWindow);
    var selectionObjects = values._selection.getSelection();

    values._foreground = this.queryCommandValue('ForeColor') || this.config.defaultForeground;
    values._background = advancedEditor.getHexColor(this.objects.editableContent.style.backgroundColor) || this.config.defaultBackground;
    values._highlight =  this.queryCommandValue(isIE ? 'BackColor' : 'HiliteColor', true);

    if (type == 'foreground')
        values.hexcolor = values._foreground;
    else if (type == 'background')
        values.hexcolor = values._background;
    else if (type == 'highlight')
        values.hexcolor = values._highlight;

    this.showDialog('editor_color.html', isSafari ? 375 : 358, 200, values, 'opener.dialogWindow.editor.setColor()');
}

advancedEditor.prototype.fontDialog = function () {
    var values = {
        font        : null,
        style       : null,
        size        : null,
        underline   : null,
        hexcolor    : null,
        _type       : 'foreground',
        _foreground : null,
        _background : null,
        _highlight  : null
    };

    values._selection = new advancedEditorSelection(isIE ? this.objects.editableFrameDoc : this.objects.editableFrame.contentWindow);
    var selectionObjects = values._selection.getSelection();

    values.font = this.queryCommandValue('FontName') || this.config.defaultFont;
    values.size = this.queryCommandValue('FontSize') || this.config.defaultFontSize;
    values.underline = this.objects.editableFrameDoc.queryCommandState('Underline');
    values._underline = values.underline;
    values._bold = this.objects.editableFrameDoc.queryCommandState('Bold');
    values._italic = this.objects.editableFrameDoc.queryCommandState('Italic');
    values.style = (values._bold && values._italic ? 'bi' : values._bold ? 'b' : values._italic ? 'i' : 'r');

    values._foreground = this.queryCommandValue('ForeColor') || this.config.defaultForeground;
    values._background = advancedEditor.getHexColor(this.objects.editableContent.style.backgroundColor) || this.config.defaultBackground;
    values._highlight =  this.queryCommandValue(isIE ? 'BackColor' : 'HiliteColor', true);
    values.hexcolor = values._foreground;

    this.showDialog('editor_font.html', 447, 250, values, 'opener.dialogWindow.editor.setFont()');
}

advancedEditor.prototype.imageDialog = function () {
    var values = {
        'src-inline'       : null,
        'src-inline-input' : null,
        'src-url-input'    : null
    };
    var url = 'editor_image.html';
    if (this.config.imageDialogURL && this.config.imageDialogURL.match(/^https?:/i))
        url = this.config.imageDialogURL;
    else
        url = parent.location.protocol + '//' + parent.location.host +
            parent.location.pathname.substr(0, parent.location.pathname.lastIndexOf('/') + 1) +
            this.config.imageDialogURL;
    this.showDialog(url, 430, 500, values, 'opener.dialogWindow.editor.insertImage()');
}

advancedEditor.prototype.linkDialog = function () {
    var values = {
        protocol : null,
        url      : null,
        title    : null
    };

    values._selection = new advancedEditorSelection(isIE ? this.objects.editableFrameDoc : this.objects.editableFrame.contentWindow);
    var selectionObjects = values._selection.getSelection();
    if (selectionObjects.length == 1 && selectionObjects[0].tagName == 'A') {
        values.url = selectionObjects[0].href;
        values.protocol = values.url.replace(/^(\w+:(?:\/\/)?).*$/, "$1");
        values.title = selectionObjects[0].title;
    }

    this.showDialog('editor_link.html', 390, 100, values, 'opener.dialogWindow.editor.insertLink()');
}

advancedEditor.prototype.showDialog = function (url, width, height, values, returnCall) {
    if (dialogWindow.win && !dialogWindow.win.closed)
        dialogWindow.win.close();

    if (url.match(/^https?:/i))
        dialogWindow.url = url;
    else
        dialogWindow.url = this.objects.editorFrame.contentWindow.location.href.replace(/editor_iframe\.html$/, '') + url;
    dialogWindow.width   = width;
    dialogWindow.height  = height;
    dialogWindow.name    = Math.random().toString().replace(/\./, "");

    dialogWindow.left    = (screen.width - width) / 2;
    dialogWindow.top     = (screen.height - height) / 2;
    dialogWindow.attribs = 'left=' + dialogWindow.left + ',top=' + dialogWindow.top +
        'location=0,menubar=0,resizable=1,scrollbars=0,status=0,toolbar=0,width=' + dialogWindow.width +
        ',height=' + dialogWindow.height;

    dialogWindow.editor     = this;
    dialogWindow.values     = values;
    dialogWindow.returnCall = returnCall;
    try {
        dialogWindow.win = window.open(dialogWindow.url, dialogWindow.name, dialogWindow.attribs);
        dialogWindow.win.focus();
    }
    catch (e) {
        // FIXME should we alert?
    }
}

advancedEditor.prototype.toggleSource = function () {
    if (!this.objects.editableSource)
        return;

    this.sourceMode = !this.sourceMode;
    if (this.sourceMode) {
        this.changeButtonState(false, 'source');
        this.objects.editableSource.value = this.getContent();
        this.objects.editableSource.style.top = this.toolbarHeight * this.objects.toolbarArea.childNodes.length + 'px';
        this.objects.editableSource.style.height = this.objects.editableFrame.offsetHeight + 'px';
        this.objects.editableSource.style.visibility = 'visible';
        this.objects.editableFrame.style.visibility = 'hidden';
// Without setting this to 0px, mozilla loses the cursor in the source textarea
        this.objects.editableFrame.style.height = '0px';
        this.objects.editableSource.focus();
    }
    else {
        this.changeButtonState(true, 'source');
        this.objects.editableContent.innerHTML = this.objects.editableSource.value;
        this.objects.editableSource.style.visibility = 'hidden';
        this.objects.editableFrame.style.height = this.objects.editableSource.style.height;
        this.objects.editableFrame.style.visibility = 'visible';
        this.focus();
    }
}

advancedEditor.prototype.selectionMade = function () {
    if (this.objects.editableFrameDoc.selection)
        return this.objects.editableFrameDoc.selection.type != 'None';

    if (this.objects.editableFrame.contentWindow.getSelection) {
        var _selection = new advancedEditorSelection(this.objects.editableFrame.contentWindow);
        return _selection.getSelectionHTML().length > 0;
    }
}

advancedEditor.getHexColor = function (color) {
    function intToHex(i) {
        i = parseInt(i);
        return (i < 16 ? '0' : '') + i.toString(16).toUpperCase();
    }

    if (typeof(color) == 'number')
        return '#' + intToHex(color & 0xff) + intToHex((color >> 8) & 0xff) + intToHex((color >> 16) & 0xff);
    else if (typeof(color) != 'string')
        return;

    if (color.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i))
        return '#' + intToHex(RegExp.$1) + intToHex(RegExp.$2) + intToHex(RegExp.$3);

    if (color == 'transparent')
        return;

    return color.toUpperCase();
}

function advancedEditorSelection(documentWindow) {
    if (documentWindow.selection)
        this.document = documentWindow;
    else
        this.window = documentWindow;
}

advancedEditorSelection.prototype.getSelection = function () {
    if (this.document) {
        this.document.parentWindow.focus();
        this.selection = this.document.selection;
        this.range = this.selection.createRange();
        this.nodes = [];
        if (this.selection.type == 'Control') {
            for (var i = 0; i < this.range.length; i++)
                this.nodes[i] = this.range(i);
        }
        else if (this.selection.type == 'Text') {
            this.nodesContainer = this.document.createElement('span');
            this.nodesContainer.innerHTML = this.range.htmlText;
            this.nodes = this.nodesContainer.childNodes;
        }
    }
    else {
        this.selection = this.window.getSelection();
        this.nodes = [];
        if (this.selection && this.selection.getRangeAt) {
// Safari triggers an exception when there's nothing selected
            try {
                this.range = this.selection.getRangeAt(0);
                this.documentFragment = this.range.cloneContents();
                this.nodes = this.documentFragment.childNodes;
                if (this.nodes.length == 1 && this.nodes[0].nodeType == 3 && this.nodes[0].nodeValue == '')
                    this.nodes = [];
            }
            catch (e) {}
        }
    }

    return this.nodes;
}

advancedEditorSelection.prototype.getSelectionHTML = function () {
    if (!this.selection)
        this.getSelection();

    if (this.document)
        return this.range.htmlText;
    else {
        var div = document.createElement('div');
        div.appendChild(this.documentFragment);
        return div.innerHTML;
    }
}

advancedEditorSelection.prototype.replaceSelection = function (node) {
    if (!this.selection)
        this.getSelection();

    if (this.document) {
        if (this.selection.type == 'Control') {
            this.selection.clear();
            this.range = this.selection.createRange();
        }
        this.range.pasteHTML(node.outerHTML);
    }
    else {
        this.range.deleteContents();
        this.range.insertNode(node);
    }
}
