incubator-graffito-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From clomb...@apache.org
Subject svn commit: r454522 [3/10] - in /incubator/graffito/trunk: applications/browser/src/java/org/apache/portals/graffito/servlets/ applications/browser/src/java/org/apache/portals/graffito/servlets/browser/ applications/browser/src/webapp/META-INF/ applica...
Date Mon, 09 Oct 2006 21:28:32 GMT
Added: incubator/graffito/trunk/applications/browser/src/webapp/kupu-1.3.5/kupubasetools.js
URL: http://svn.apache.org/viewvc/incubator/graffito/trunk/applications/browser/src/webapp/kupu-1.3.5/kupubasetools.js?view=auto&rev=454522
==============================================================================
--- incubator/graffito/trunk/applications/browser/src/webapp/kupu-1.3.5/kupubasetools.js (added)
+++ incubator/graffito/trunk/applications/browser/src/webapp/kupu-1.3.5/kupubasetools.js Mon Oct  9 14:28:27 2006
@@ -0,0 +1,2610 @@
+/*****************************************************************************
+ *
+ * Copyright (c) 2003-2005 Kupu Contributors. All rights reserved.
+ *
+ * This software is distributed under the terms of the Kupu
+ * License. See LICENSE.txt for license text. For a list of Kupu
+ * Contributors see CREDITS.txt.
+ *
+ *****************************************************************************/
+
+// $Id: kupubasetools.js 21219 2005-12-16 15:45:56Z duncan $
+
+
+//----------------------------------------------------------------------------
+//
+// Toolboxes
+//
+//  These are addons for Kupu, simple plugins that implement a certain 
+//  interface to provide functionality and control view aspects.
+//
+//----------------------------------------------------------------------------
+
+//----------------------------------------------------------------------------
+// Superclasses
+//----------------------------------------------------------------------------
+
+function KupuTool() {
+    /* Superclass (or actually more of an interface) for tools 
+    
+        Tools must implement at least an initialize method and an 
+        updateState method, and can implement other methods to add 
+        certain extra functionality (e.g. createContextMenuElements).
+    */
+
+    this.toolboxes = {};
+
+    // methods
+    this.initialize = function(editor) {
+        /* Initialize the tool.
+
+            Obviously this can be overriden but it will do
+            for the most simple cases
+        */
+        this.editor = editor;
+    };
+
+    this.registerToolBox = function(id, toolbox) {
+        /* register a ui box 
+        
+            Note that this needs to be called *after* the tool has been 
+            registered to the KupuEditor
+        */
+        this.toolboxes[id] = toolbox;
+        toolbox.initialize(this, this.editor);
+    };
+    
+    this.updateState = function(selNode, event) {
+        /* Is called when user moves cursor to other element 
+
+            Calls the updateState for all toolboxes and may want perform
+            some actions itself
+        */
+        for (id in this.toolboxes) {
+            this.toolboxes[id].updateState(selNode, event);
+        };
+    };
+
+    this.enable = function() {
+        // Called when the tool is enabled after a form is dismissed.
+    }
+
+    this.disable = function() {
+        // Called when the tool is disabled (e.g. for a modal form)
+    }
+    // private methods
+    addEventHandler = addEventHandler;
+    
+    this._selectSelectItem = function(select, item) {
+        this.editor.logMessage(_('Deprecation warning: KupuTool._selectSelectItem'));
+    };
+    this._fixTabIndex = function(element) {
+        var tabIndex = this.editor.getDocument().getEditable().tabIndex-1;
+        if (tabIndex && !element.tabIndex) {
+            element.tabIndex = tabIndex;
+        }
+    }
+}
+
+function KupuToolBox() {
+    /* Superclass for a user-interface object that controls a tool */
+
+    this.initialize = function(tool, editor) {
+        /* store a reference to the tool and the editor */
+        this.tool = tool;
+        this.editor = editor;
+    };
+
+    this.updateState = function(selNode, event) {
+        /* update the toolbox according to the current iframe's situation */
+    };
+    
+    this._selectSelectItem = function(select, item) {
+        this.editor.logMessage(_('Deprecation warning: KupuToolBox._selectSelectItem'));
+    };
+};
+
+function NoContextMenu(object) {
+    /* Decorator for a tool to suppress the context menu */
+    object.createContextMenuElements = function(selNode, event) {
+        return [];
+    }
+    return object;
+}
+
+// Helper function for enabling/disabling tools
+function KupuButtonDisable(button) {
+    button = button || this.button;
+    button.disabled = "disabled";
+    button.className += ' disabled';
+}
+function KupuButtonEnable(button) {
+    button = button || this.button;
+    button.disabled = "";
+    button.className = button.className.replace(/ *\bdisabled\b/g, '');
+}
+
+
+//----------------------------------------------------------------------------
+// Implementations
+//----------------------------------------------------------------------------
+
+function KupuButton(buttonid, commandfunc, tool) {
+    /* Base prototype for kupu button tools */
+    this.buttonid = buttonid;
+    this.button = getFromSelector(buttonid);
+    this.commandfunc = commandfunc;
+    this.tool = tool;
+
+    this.initialize = function(editor) {
+        this.editor = editor;
+        this._fixTabIndex(this.button);
+        addEventHandler(this.button, 'click', this.execCommand, this);
+    };
+
+    this.execCommand = function() {
+        /* exec this button's command */
+        this.commandfunc(this, this.editor, this.tool);
+    };
+
+    this.updateState = function(selNode, event) {
+        /* override this in subclasses to determine whether a button should
+            look 'pressed in' or not
+        */
+    };
+    this.disable = KupuButtonDisable;
+    this.enable = KupuButtonEnable;
+};
+
+KupuButton.prototype = new KupuTool;
+function KupuStateButton(buttonid, commandfunc, checkfunc, offclass, onclass) {
+    /* A button that can have two states (e.g. pressed and
+       not-pressed) based on CSS classes */
+    this.buttonid = buttonid;
+    this.button = getFromSelector(buttonid);
+    this.commandfunc = commandfunc;
+    this.checkfunc = checkfunc;
+    this.offclass = offclass;
+    this.onclass = onclass;
+    this.pressed = false;
+
+    this.execCommand = function() {
+        /* exec this button's command */
+        this.button.className = (this.pressed ? this.offclass : this.onclass);
+        this.pressed = !this.pressed;
+        this.editor.focusDocument();
+        this.commandfunc(this, this.editor);
+    };
+
+    this.updateState = function(selNode, event) {
+        /* check if we need to be clicked or unclicked, and update accordingly 
+        
+            if the state of the button should be changed, we set the class
+        */
+        var currclass = this.button.className;
+        var newclass = null;
+        if (this.checkfunc(selNode, this, this.editor, event)) {
+            newclass = this.onclass;
+            this.pressed = true;
+        } else {
+            newclass = this.offclass;
+            this.pressed = false;
+        };
+        if (currclass != newclass) {
+            this.button.className = newclass;
+        };
+    };
+};
+
+KupuStateButton.prototype = new KupuButton;
+
+/* Same as the state button, but the focusDocument call is delayed.
+ * Mozilla&Firefox have a bug on windows which can cause a crash if you
+ * change CSS positioning styles on an element which has focus.
+ */
+function KupuLateFocusStateButton(buttonid, commandfunc, checkfunc, offclass, onclass) {
+    KupuStateButton.apply(this, [buttonid, commandfunc, checkfunc, offclass, onclass]);
+    this.execCommand = function() {
+        /* exec this button's command */
+        this.button.className = (this.pressed ? this.offclass : this.onclass);
+        this.pressed = !this.pressed;
+        this.commandfunc(this, this.editor);
+        this.editor.focusDocument();
+    };
+}
+KupuLateFocusStateButton.prototype = new KupuStateButton;
+
+function KupuRemoveElementButton(buttonid, element_name, cssclass) {
+    /* A button specialized in removing elements in the current node
+       context. Typical usages include removing links, images, etc. */
+    this.button = getFromSelector(buttonid);
+    this.onclass = 'invisible';
+    this.offclass = cssclass;
+    this.pressed = false;
+
+    this.commandfunc = function(button, editor) {
+        editor.removeNearestParentOfType(editor.getSelectedNode(), element_name);
+    };
+
+    this.checkfunc = function(currnode, button, editor, event) {
+        var element = editor.getNearestParentOfType(currnode, element_name);
+        return (element ? false : true);
+    };
+};
+
+KupuRemoveElementButton.prototype = new KupuStateButton;
+
+function KupuUI(textstyleselectid) {
+    /* View 
+    
+        This is the main view, which controls most of the toolbar buttons.
+        Even though this is probably never going to be removed from the view,
+        it was easier to implement this as a plain tool (plugin) as well.
+    */
+    
+    // attributes
+    this.tsselect = getFromSelector(textstyleselectid);
+    var paraoptions = [];
+    var tableoptions = [];
+    this.optionstate = -1;
+    this.otherstyle = null;
+    this.tablestyles = {};
+    this.styles = {}; // use an object here so we can use the 'in' operator later on
+
+    this.initialize = function(editor) {
+        /* initialize the ui like tools */
+        this.editor = editor;
+        this.cleanStyles();
+        this.enableOptions(false);
+        this._fixTabIndex(this.tsselect);
+        this._selectevent = addEventHandler(this.tsselect, 'change', this.setTextStyleHandler, this);
+    };
+
+    this.getStyles = function() {
+        if (!paraoptions) {
+            this.cleanStyles();
+        }
+        return [ paraoptions, tableoptions ];
+    }
+
+    this.setTextStyleHandler = function(event) {
+        this.setTextStyle(this.tsselect.options[this.tsselect.selectedIndex].value);
+    };
+    
+    // event handlers
+    this.basicButtonHandler = function(action) {
+        /* event handler for basic actions (toolbar buttons) */
+        this.editor.execCommand(action);
+        this.editor.updateState();
+    };
+
+    this.saveButtonHandler = function() {
+        /* handler for the save button */
+        this.editor.saveDocument();
+    };
+
+    this.saveAndExitButtonHandler = function(redirect_url) {
+        /* save the document and, if successful, redirect */
+        this.editor.saveDocument(redirect_url);
+    };
+
+    this.cutButtonHandler = function() {
+        try {
+            this.editor.execCommand('Cut');
+        } catch (e) {
+            if (this.editor.getBrowserName() == 'Mozilla') {
+                alert(_('Cutting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
+            } else {
+                throw e;
+            };
+        };
+        this.editor.updateState();
+    };
+
+    this.copyButtonHandler = function() {
+        try {
+            this.editor.execCommand('Copy');
+        } catch (e) {
+            if (this.editor.getBrowserName() == 'Mozilla') {
+                alert(_('Copying from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
+            } else {
+                throw e;
+            };
+        };
+        this.editor.updateState();
+    };
+
+    this.pasteButtonHandler = function() {
+        try {
+            this.editor.execCommand('Paste');
+        } catch (e) {
+            if (this.editor.getBrowserName() == 'Mozilla') {
+                alert(_('Pasting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
+            } else {
+                throw e;
+            };
+        };
+        this.editor.updateState();
+    };
+
+    this.cleanStyles = function() {
+        var options = this.tsselect.options;
+        var parastyles = this.styles;
+        var tablestyles = this.tablestyles;
+
+        tableoptions.push([options[0].text, 'td|']);
+        tablestyles['td'] = 0;
+        paraoptions.push([options[0].text, 'p|']);
+        parastyles['p'] = 0;
+        while (options.length > 1) {
+            opt = options[1];
+            var v = opt.value;
+            if (/^thead|tbody|table|t[rdh]\b/i.test(v)) {
+                var otable = tableoptions;
+                var styles = tablestyles;
+            } else {
+                var otable = paraoptions;
+                var styles = parastyles;
+            }
+            if (v.indexOf('|') > -1) {
+                var split = v.split('|');
+                v = split[0].toLowerCase() + "|" + split[1];
+            } else {
+                v = v.toLowerCase()+"|";
+            };
+            otable.push([opt.text, v]);
+            styles[v] = otable.length - 1;
+            options[1] = null;
+        }
+        options[0] = null;
+    }
+
+    // Remove otherstyle and switch to appropriate style set.
+    this.enableOptions = function(inTable) {
+        var select = this.tsselect;
+        var options = select.options;
+        if (this.otherstyle) {
+            options[options.length-1] = null;
+            this.otherstyle = null;
+        }
+        if (this.optionstate == inTable) return; /* No change */
+
+        var valid = inTable ? tableoptions : paraoptions;
+
+        while (options.length) options[0] = null;
+        this.otherstyle = null;
+
+        for (var i = 0; i < valid.length; i++) {
+            var opt = document.createElement('option');
+            opt.text = valid[i][0];
+            opt.value = valid[i][1];
+            options.add(opt);
+        }
+        select.selectedIndex = 0;
+        this.optionstate = inTable;
+    }
+    
+    this.setIndex = function(currnode, tag, index, styles) {
+        var className = currnode.className;
+        this.styletag = tag;
+        this.classname = className;
+        var style = tag+'|'+className;
+
+        if (style in styles) {
+            return styles[style];
+        } else if (!className && tag in styles) {
+            return styles[tag];
+        }
+        return index;
+    }
+
+    this.nodeStyle = function(node) {
+        var currnode = node;
+        var index = -1;
+        var options = this.tsselect.options;
+        this.styletag = undefined;
+        this.classname = '';
+        this.intable = false;
+
+        while (currnode) {
+            var tag = currnode.nodeName.toLowerCase();
+
+            if (/^body$/.test(tag)) {
+                if (!this.styletag) {
+                    // Force style setting
+                    //this.setTextStyle(options[0].value, true);
+                    // Forced style messes up in Firefox: return -1 to
+                    // indicate no style 
+                    return -1;
+                }
+                break;
+            }
+            if (/^(p|div|h.|ul|ol|dl|menu|dir|pre|blockquote|address|center)$/.test(tag)) {
+                index = this.setIndex(currnode, tag, index, this.styles);
+            }
+            if (/^thead|tbody|table|t[rdh]$/.test(tag)) {
+                this.intable = true;
+                index = this.setIndex(currnode, tag, index, this.tablestyles);
+
+                if (index > 0 || tag=='table') {
+                    return index; // Stop processing if in a table
+                }
+            }
+            currnode = currnode.parentNode;
+        }
+        return index;
+    }
+
+    this.updateState = function(selNode) {
+        /* set the text-style pulldown */
+
+        // first get the nearest style
+        // search the list of nodes like in the original one, break if we encounter a match,
+        // this method does some more than the original one since it can handle commands in
+        // the form of '<style>|<classname>' next to the plain
+        // '<style>' commands
+        var index = undefined;
+        var mixed = false;
+        var styletag, classname;
+
+        var selection = this.editor.getSelection();
+
+        for (var el=selNode.firstChild; el; el=el.nextSibling) {
+            if (el.nodeType==1 && selection.containsNode(el)) {
+                var i = this.nodeStyle(el);
+                if (index===undefined) {
+                    index = i;
+                    styletag = this.styletag;
+                    classname = this.classname;
+                }
+                if (index != i || styletag!=this.styletag || classname != this.classname) {
+                    mixed = true;
+                    break;
+                }
+            }
+        };
+
+        if (index===undefined) {
+            index = this.nodeStyle(selNode);
+        }
+
+        this.enableOptions(this.intable);
+
+        if (index < 0 || mixed) {
+            if (mixed) {
+                var caption = 'Mixed styles';
+            } else if (this.styletag) {
+                var caption = 'Other: ' + this.styletag + ' '+ this.classname;
+            } else {
+                var caption = '<no style>';
+            }
+
+            var opt = document.createElement('option');
+            opt.text = caption;
+            this.otherstyle = opt;
+            this.tsselect.options.add(opt);
+
+            index = this.tsselect.length-1;
+        }
+        this.tsselect.selectedIndex = Math.max(index,0);
+    };
+
+    this._cleanNode = function(node) {
+                /* Clean up a block style node (e.g. P, DIV, Hn)
+                 * Remove trailing whitespace, then also remove up to one
+                 * trailing <br>
+                 * If the node is now empty, remove the node itself.
+                 */
+        var len = node.childNodes.length;
+        function stripspace() {
+            var c;
+            while ((c = node.lastChild) && c.nodeType==3 && /^\s*$/.test(c.data)) {
+                node.removeChild(c);
+            }
+        }
+        stripspace();
+        var c = node.lastChild;
+        if (c && c.nodeType==1 && c.tagName=='BR') {
+            node.removeChild(c);
+        }
+        stripspace();
+        if (node.childNodes.length==0) {
+            node.parentNode.removeChild(node);
+        };
+    }
+
+    this._cleanCell = function(eltype, classname) {
+        var selNode = this.editor.getSelectedNode();
+        var el = this.editor.getNearestParentOfType(selNode, eltype);
+        if (!el) {
+                // Maybe changing type
+            el = this.editor.getNearestParentOfType(selNode, eltype=='TD'?'TH':'TD');
+        }
+        if (!el) return;
+
+            // Remove formatted div or p from a cell
+        var node, nxt, n;
+        for (node = el.firstChild; node;) {
+            if (/DIV|P/.test(node.nodeName)) {
+                for (var n = node.firstChild; n;) {
+                    var nxt = n.nextSibling;
+                    el.insertBefore(n, node); // Move nodes out of div
+                    n = nxt;
+                }
+                nxt = node.nextSibling;
+                el.removeChild(node);
+                node = nxt;
+            } else {
+                node = node.nextSibling;
+            }
+        }
+        if (eltype != el.tagName) {
+                // Change node type.
+            var node = el.ownerDocument.createElement(eltype);
+            var parent = el.parentNode;
+            parent.insertBefore(node, el);
+            while (el.firstChild) {
+                node.appendChild(el.firstChild);
+            }
+            parent.removeChild(el);
+            el = node;
+        }
+            // now set the classname
+        if (classname) {
+            el.className = classname;
+        } else {
+            el.removeAttribute("class");
+            el.removeAttribute("className");
+        }
+
+    }
+
+    this._setClass = function(el, classname) {
+        var parent = el.parentNode;
+        if (parent.tagName=='DIV') {
+            // fixup buggy formatting
+            var gp = parent.parentNode;
+            if (el != parent.firstChild) {
+                var previous = parent.cloneNode(false);
+                while (el != parent.firstChild) {
+                    previous.appendChild(parent.firstChild);
+                }
+                gp.insertBefore(previous, parent);
+                this._cleanNode(previous);
+            }
+            gp.insertBefore(el, parent);
+            this._cleanNode(el);
+            this._cleanNode(parent);
+        } else {
+            this._cleanNode(el);
+        }
+        // now set the classname
+        if (classname) {
+            el.className = classname;
+        } else {
+            el.removeAttribute("class");
+            el.removeAttribute("className");
+        }
+    }
+    this.setTextStyle = function(style, noupdate) {
+            /* parse the argument into a type and classname part
+               generate a block element accordingly 
+            */
+        var classname = '';
+        var eltype = style.toUpperCase();
+        if (style.indexOf('|') > -1) {
+            style = style.split('|');
+            eltype = style[0].toUpperCase();
+            classname = style[1];
+        };
+
+        var command = eltype;
+            // first create the element, then find it and set the classname
+        if (this.editor.getBrowserName() == 'IE') {
+            command = '<' + eltype + '>';
+        };
+        if (/T[RDH]/.test(eltype)) {
+            this._cleanCell(eltype, classname);
+        }
+        else {
+            this.editor.getDocument().execCommand('formatblock', command);
+
+                // now get a reference to the element just added
+            var selNode = this.editor.getSelectedNode();
+            var el = this.editor.getNearestParentOfType(selNode, eltype);
+            if (el) {
+                this._setClass(el, classname);
+            } else {
+                var selection = this.editor.getSelection();
+                var elements = selNode.getElementsByTagName(eltype);
+                for (var i = 0; i < elements.length; i++) {
+                    el = elements[i];
+                    if (selection.containsNode(el)) {
+                        this._setClass(el, classname);
+                    }
+                }
+            }
+        }
+        if (el) {
+            this.editor.getSelection().selectNodeContents(el);
+        }
+        if (!noupdate) {
+            this.editor.updateState();
+        }
+    };
+  
+    this.createContextMenuElements = function(selNode, event) {
+        var ret = new Array();
+        ret.push(new ContextMenuElement(_('Cut'), 
+                    this.cutButtonHandler, this));
+        ret.push(new ContextMenuElement(_('Copy'), 
+                    this.copyButtonHandler, this));
+        ret.push(new ContextMenuElement(_('Paste'), 
+                    this.pasteButtonHandler, this));
+        return ret;
+    };
+    this.disable = function() {
+        this.tsselect.disabled = "disabled";
+    }
+    this.enable = function() {
+        this.tsselect.disabled = "";
+    }
+}
+
+KupuUI.prototype = new KupuTool;
+
+function ColorchooserTool(fgcolorbuttonid, hlcolorbuttonid, colorchooserid) {
+    /* the colorchooser */
+    
+    this.fgcolorbutton = getFromSelector(fgcolorbuttonid);
+    this.hlcolorbutton = getFromSelector(hlcolorbuttonid);
+    this.ccwindow = getFromSelector(colorchooserid);
+    this.command = null;
+
+    this.initialize = function(editor) {
+        /* attach the event handlers */
+        this.editor = editor;
+        
+        this.createColorchooser(this.ccwindow);
+
+        addEventHandler(this.fgcolorbutton, "click", this.openFgColorChooser, this);
+        addEventHandler(this.hlcolorbutton, "click", this.openHlColorChooser, this);
+        addEventHandler(this.ccwindow, "click", this.chooseColor, this);
+
+        this.hide();
+
+        this.editor.logMessage(_('Colorchooser tool initialized'));
+    };
+
+    this.updateState = function(selNode) {
+        /* update state of the colorchooser */
+        this.hide();
+    };
+
+    this.openFgColorChooser = function() {
+        /* event handler for opening the colorchooser */
+        this.command = "forecolor";
+        this.show();
+    };
+
+    this.openHlColorChooser = function() {
+        /* event handler for closing the colorchooser */
+        if (this.editor.getBrowserName() == "IE") {
+            this.command = "backcolor";
+        } else {
+            this.command = "hilitecolor";
+        }
+        this.show();
+    };
+
+    this.chooseColor = function(event) {
+        /* event handler for choosing the color */
+        var target = _SARISSA_IS_MOZ ? event.target : event.srcElement;
+        var cell = this.editor.getNearestParentOfType(target, 'td');
+        this.editor.execCommand(this.command, cell.getAttribute('bgColor'));
+        this.hide();
+    
+        this.editor.logMessage(_('Color chosen'));
+    };
+
+    this.show = function(command) {
+        /* show the colorchooser */
+        this.ccwindow.style.display = "block";
+    };
+
+    this.hide = function() {
+        /* hide the colorchooser */
+        this.command = null;
+        this.ccwindow.style.display = "none";
+    };
+
+    this.createColorchooser = function(table) {
+        /* create the colorchooser table */
+        
+        var chunks = new Array('00', '33', '66', '99', 'CC', 'FF');
+        table.setAttribute('id', 'kupu-colorchooser-table');
+        table.style.borderWidth = '2px';
+        table.style.borderStyle = 'solid';
+        table.style.position = 'absolute';
+        table.style.cursor = 'default';
+        table.style.display = 'none';
+
+        var tbody = document.createElement('tbody');
+
+        for (var i=0; i < 6; i++) {
+            var tr = document.createElement('tr');
+            var r = chunks[i];
+            for (var j=0; j < 6; j++) {
+                var g = chunks[j];
+                for (var k=0; k < 6; k++) {
+                    var b = chunks[k];
+                    var color = '#' + r + g + b;
+                    var td = document.createElement('td');
+                    td.setAttribute('bgColor', color);
+                    td.style.backgroundColor = color;
+                    td.style.borderWidth = '1px';
+                    td.style.borderStyle = 'solid';
+                    td.style.fontSize = '1px';
+                    td.style.width = '10px';
+                    td.style.height = '10px';
+                    var text = document.createTextNode('\u00a0');
+                    td.appendChild(text);
+                    tr.appendChild(td);
+                }
+            }
+            tbody.appendChild(tr);
+        }
+        table.appendChild(tbody);
+
+        return table;
+    };
+    this.enable = function() {
+        KupuButtonEnable(this.fgcolorbutton);
+        KupuButtonEnable(this.hlcolorbutton);
+    }
+    this.disable = function() {
+        KupuButtonDisable(this.fgcolorbutton);
+        KupuButtonDisable(this.hlcolorbutton);
+    }
+}
+
+ColorchooserTool.prototype = new KupuTool;
+
+function PropertyTool(titlefieldid, descfieldid) {
+    /* The property tool */
+
+    this.titlefield = getFromSelector(titlefieldid);
+    this.descfield = getFromSelector(descfieldid);
+
+    this.initialize = function(editor) {
+        /* attach the event handlers and set the initial values */
+        this.editor = editor;
+        addEventHandler(this.titlefield, "change", this.updateProperties, this);
+        addEventHandler(this.descfield, "change", this.updateProperties, this);
+        
+        // set the fields
+        var heads = this.editor.getInnerDocument().getElementsByTagName('head');
+        if (!heads[0]) {
+            this.editor.logMessage(_('No head in document!'), 1);
+        } else {
+            var head = heads[0];
+            var titles = head.getElementsByTagName('title');
+            if (titles.length) {
+                this.titlefield.value = titles[0].text;
+            }
+            var metas = head.getElementsByTagName('meta');
+            if (metas.length) {
+                for (var i=0; i < metas.length; i++) {
+                    var meta = metas[i];
+                    if (meta.getAttribute('name') && 
+                            meta.getAttribute('name').toLowerCase() == 
+                            'description') {
+                        this.descfield.value = meta.getAttribute('content');
+                        break;
+                    }
+                }
+            }
+        }
+
+        this.editor.logMessage(_('Property tool initialized'));
+    };
+
+    this.updateProperties = function() {
+        /* event handler for updating the properties form */
+        var doc = this.editor.getInnerDocument();
+        var heads = doc.getElementsByTagName('HEAD');
+        if (!heads) {
+            this.editor.logMessage(_('No head in document!'), 1);
+            return;
+        }
+
+        var head = heads[0];
+
+        // set the title
+        var titles = head.getElementsByTagName('title');
+        if (!titles) {
+            var title = doc.createElement('title');
+            var text = doc.createTextNode(this.titlefield.value);
+            title.appendChild(text);
+            head.appendChild(title);
+        } else {
+            var title = titles[0];
+            // IE6 title has no children, and refuses appendChild.
+            // Delete and recreate the title.
+            if (title.childNodes.length == 0) {
+                title.removeNode(true);
+                title = doc.createElement('title');
+                title.innerText = this.titlefield.value;
+                head.appendChild(title);
+            } else {
+                title.childNodes[0].nodeValue = this.titlefield.value;
+            }
+        }
+        document.title = this.titlefield.value;
+
+        // let's just fulfill the usecase, not think about more properties
+        // set the description
+        var metas = doc.getElementsByTagName('meta');
+        var descset = 0;
+        for (var i=0; i < metas.length; i++) {
+            var meta = metas[i];
+            if (meta.getAttribute('name') && 
+                    meta.getAttribute('name').toLowerCase() == 'description') {
+                meta.setAttribute('content', this.descfield.value);
+                descset = 1;
+            }
+        }
+
+        if (!descset) {
+            var meta = doc.createElement('meta');
+            meta.setAttribute('name', 'description');
+            meta.setAttribute('content', this.descfield.value);
+            head.appendChild(meta);
+        }
+
+        this.editor.logMessage(_('Properties modified'));
+    };
+}
+
+PropertyTool.prototype = new KupuTool;
+
+function LinkTool() {
+    /* Add and update hyperlinks */
+    
+    this.initialize = function(editor) {
+        this.editor = editor;
+        this.editor.logMessage(_('Link tool initialized'));
+    };
+    
+    this.createLinkHandler = function(event) {
+        /* create a link according to a url entered in a popup */
+        var linkWindow = openPopup('kupupopups/link.html', 300, 200);
+        linkWindow.linktool = this;
+        linkWindow.focus();
+    };
+
+    this.updateLink = function (linkel, url, type, name, target, title) {
+        if (type && type == 'anchor') {
+            linkel.removeAttribute('href');
+            linkel.setAttribute('name', name);
+        } else {
+            linkel.href = url;
+            if (linkel.innerHTML == "") {
+                var doc = this.editor.getInnerDocument();
+                linkel.appendChild(doc.createTextNode(title || url));
+            }
+            if (title) {
+                linkel.title = title;
+            } else {
+                linkel.removeAttribute('title');
+            }
+            if (target && target != '') {
+                linkel.setAttribute('target', target);
+            }
+            else {
+                linkel.removeAttribute('target');
+            };
+            linkel.style.color = this.linkcolor;
+        };
+    };
+
+    this.formatSelectedLink = function(url, type, name, target, title) {
+        var currnode = this.editor.getSelectedNode();
+
+        // selection inside link
+        var linkel = this.editor.getNearestParentOfType(currnode, 'A');
+        if (linkel) {
+            this.updateLink(linkel, url, type, name, target, title);
+            return true;
+        }
+
+        if (currnode.nodeType!=1) return false;
+
+        // selection contains links
+        var linkelements = currnode.getElementsByTagName('A');
+        var selection = this.editor.getSelection();
+        var containsLink = false;
+        for (var i = 0; i < linkelements.length; i++) {
+            linkel = linkelements[i];
+            if (selection.containsNode(linkel)) {
+                this.updateLink(linkel, url, type, name, target, title);
+                containsLink = true;
+            }
+        };
+        return containsLink;
+    }
+
+    // Can create a link in the following circumstances:
+    //   The selection is inside a link:
+    //      just update the link attributes.
+    //   The selection contains links:
+    //      update the attributes of the contained links
+    //   No links inside or outside the selection:
+    //      create a link around the selection
+    //   No selection:
+    //      insert a link containing the title
+    //
+    // the order of the arguments is a bit odd here because of backward
+    // compatibility
+    this.createLink = function(url, type, name, target, title) {
+        if (!this.formatSelectedLink(url, type, name, target, title)) {
+            // No links inside or outside.
+            this.editor.execCommand("CreateLink", url);
+            if (!this.formatSelectedLink(url, type, name, target, title)) {
+                // Insert link with no text selected, insert the title
+                // or URI instead.
+                var doc = this.editor.getInnerDocument();
+                linkel = doc.createElement("a");
+                linkel.setAttribute('href', url);
+                linkel.setAttribute('class', 'generated');
+                this.editor.getSelection().replaceWithNode(linkel, true);
+                this.updateLink(linkel, url, type, name, target, title);
+            };
+        }
+        this.editor.logMessage(_('Link added'));
+        this.editor.updateState();
+    };
+    
+    this.deleteLink = function() {
+        /* delete the current link */
+        var currnode = this.editor.getSelectedNode();
+        var linkel = this.editor.getNearestParentOfType(currnode, 'a');
+        if (!linkel) {
+            this.editor.logMessage(_('Not inside link'));
+            return;
+        };
+        while (linkel.childNodes.length) {
+            linkel.parentNode.insertBefore(linkel.childNodes[0], linkel);
+        };
+        linkel.parentNode.removeChild(linkel);
+        
+        this.editor.logMessage(_('Link removed'));
+        this.editor.updateState();
+    };
+    
+    this.createContextMenuElements = function(selNode, event) {
+        /* create the 'Create link' or 'Remove link' menu elements */
+        var ret = new Array();
+        var link = this.editor.getNearestParentOfType(selNode, 'a');
+        if (link) {
+            ret.push(new ContextMenuElement(_('Delete link'), this.deleteLink, this));
+        } else {
+            ret.push(new ContextMenuElement(_('Create link'), this.createLinkHandler, this));
+        };
+        return ret;
+    };
+}
+
+LinkTool.prototype = new KupuTool;
+
+function LinkToolBox(inputid, buttonid, toolboxid, plainclass, activeclass) {
+    /* create and edit links */
+    
+    this.input = getFromSelector(inputid);
+    this.button = getFromSelector(buttonid);
+    this.toolboxel = getFromSelector(toolboxid);
+    this.plainclass = plainclass;
+    this.activeclass = activeclass;
+    
+    this.initialize = function(tool, editor) {
+        /* attach the event handlers */
+        this.tool = tool;
+        this.editor = editor;
+        addEventHandler(this.input, "blur", this.updateLink, this);
+        addEventHandler(this.button, "click", this.addLink, this);
+    };
+
+    this.updateState = function(selNode) {
+        /* if we're inside a link, update the input, else empty it */
+        var linkel = this.editor.getNearestParentOfType(selNode, 'a');
+        if (linkel) {
+            // check first before setting a class for backward compatibility
+            if (this.toolboxel) {
+                this.toolboxel.className = this.activeclass;
+            };
+            this.input.value = linkel.getAttribute('href');
+        } else {
+            // check first before setting a class for backward compatibility
+            if (this.toolboxel) {
+                this.toolboxel.className = this.plainclass;
+            };
+            this.input.value = '';
+        }
+    };
+    
+    this.addLink = function(event) {
+        /* add a link */
+        var url = this.input.value;
+        this.tool.createLink(url);
+    };
+    
+    this.updateLink = function() {
+        /* update the current link */
+        var currnode = this.editor.getSelectedNode();
+        var linkel = this.editor.getNearestParentOfType(currnode, 'A');
+        if (!linkel) {
+            return;
+        }
+
+        var url = this.input.value;
+        linkel.setAttribute('href', url);
+
+        this.editor.logMessage(_('Link modified'));
+    };
+};
+
+LinkToolBox.prototype = new LinkToolBox;
+
+function ImageTool() {
+    /* Image tool to add images */
+    
+    this.initialize = function(editor) {
+        /* attach the event handlers */
+        this.editor = editor;
+        this.editor.logMessage(_('Image tool initialized'));
+    };
+
+    this.createImageHandler = function(event) {
+        /* create an image according to a url entered in a popup */
+        var imageWindow = openPopup('kupupopups/image.html', 300, 200);
+        imageWindow.imagetool = this;
+        imageWindow.focus();
+    };
+
+    this.createImage = function(url, alttext, imgclass) {
+        /* create an image */
+        var img = this.editor.getInnerDocument().createElement('img');
+        img.src = url;
+        img.removeAttribute('height');
+        img.removeAttribute('width');
+        if (alttext) {
+            img.alt = alttext;
+        };
+        if (imgclass) {
+            img.className = imgclass;
+        };
+        img = this.editor.insertNodeAtSelection(img, 1);
+        this.editor.logMessage(_('Image inserted'));
+        this.editor.updateState();
+        return img;
+    };
+
+    this.setImageClass = function(imgclass) {
+        /* set the class of the selected image */
+        var currnode = this.editor.getSelectedNode();
+        var currimg = this.editor.getNearestParentOfType(currnode, 'IMG');
+        if (currimg) {
+            currimg.className = imgclass;
+        };
+    };
+
+    this.createContextMenuElements = function(selNode, event) {
+        return new Array(new ContextMenuElement(_('Create image'), this.createImageHandler, this));
+    };
+}
+
+ImageTool.prototype = new KupuTool;
+
+function ImageToolBox(inputfieldid, insertbuttonid, classselectid, toolboxid, plainclass, activeclass) {
+    /* toolbox for adding images */
+
+    this.inputfield = getFromSelector(inputfieldid);
+    this.insertbutton = getFromSelector(insertbuttonid);
+    this.classselect = getFromSelector(classselectid);
+    this.toolboxel = getFromSelector(toolboxid);
+    this.plainclass = plainclass;
+    this.activeclass = activeclass;
+
+    this.initialize = function(tool, editor) {
+        this.tool = tool;
+        this.editor = editor;
+        addEventHandler(this.classselect, "change", this.setImageClass, this);
+        addEventHandler(this.insertbutton, "click", this.addImage, this);
+    };
+
+    this.updateState = function(selNode, event) {
+        /* update the state of the toolbox element */
+        var imageel = this.editor.getNearestParentOfType(selNode, 'img');
+        if (imageel) {
+            // check first before setting a class for backward compatibility
+            if (this.toolboxel) {
+                this.toolboxel.className = this.activeclass;
+                this.inputfield.value = imageel.getAttribute('src');
+                var imgclass = imageel.className ? imageel.className : 'image-inline';
+                selectSelectItem(this.classselect, imgclass);
+            };
+        } else {
+            if (this.toolboxel) {
+                this.toolboxel.className = this.plainclass;
+            };
+        };
+    };
+
+    this.addImage = function() {
+        /* add an image */
+        var url = this.inputfield.value;
+        var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
+        this.tool.createImage(url, null, sel_class);
+        this.editor.focusDocument();
+    };
+
+    this.setImageClass = function() {
+        /* set the class for the current image */
+        var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
+        this.tool.setImageClass(sel_class);
+        this.editor.focusDocument();
+    };
+};
+
+ImageToolBox.prototype = new KupuToolBox;
+
+function TableTool() {
+    /* The table tool */
+
+    // XXX There are some awfully long methods in here!!
+    this.createContextMenuElements = function(selNode, event) {
+        var table =  this.editor.getNearestParentOfType(selNode, 'table');
+        if (!table) {
+            ret = new Array();
+            var el = new ContextMenuElement(_('Add table'), this.addPlainTable, this);
+            ret.push(el);
+            return ret;
+        } else {
+            var ret = new Array();
+            ret.push(new ContextMenuElement(_('Add row'), this.addTableRow, this));
+            ret.push(new ContextMenuElement(_('Delete row'), this.delTableRow, this));
+            ret.push(new ContextMenuElement(_('Add column'), this.addTableColumn, this));
+            ret.push(new ContextMenuElement(_('Delete column'), this.delTableColumn, this));
+            ret.push(new ContextMenuElement(_('Delete Table'), this.delTable, this));
+            return ret;
+        };
+    };
+
+    this.addPlainTable = function() {
+        /* event handler for the context menu */
+        this.createTable(2, 3, 1, 'plain');
+    };
+
+    this.createTable = function(rows, cols, makeHeader, tableclass) {
+        /* add a table */
+        if (rows < 1 || rows > 99 || cols < 1 || cols > 99) {
+            this.editor.logMessage(_('Invalid table size'), 1);
+            return;
+        };
+
+        var doc = this.editor.getInnerDocument();
+
+        table = doc.createElement("table");
+        table.className = tableclass;
+
+        // If the user wants a row of headings, make them
+        if (makeHeader) {
+            var tr = doc.createElement("tr");
+            var thead = doc.createElement("thead");
+            for (i=0; i < cols; i++) {
+                var th = doc.createElement("th");
+                th.appendChild(doc.createTextNode("Col " + i+1));
+                tr.appendChild(th);
+            }
+            thead.appendChild(tr);
+            table.appendChild(thead);
+        }
+
+        tbody = doc.createElement("tbody");
+        for (var i=0; i < rows; i++) {
+            var tr = doc.createElement("tr");
+            for (var j=0; j < cols; j++) {
+                var td = doc.createElement("td");
+                var content = doc.createTextNode('\u00a0');
+                td.appendChild(content);
+                tr.appendChild(td);
+            }
+            tbody.appendChild(tr);
+        }
+        table.appendChild(tbody);
+        this.editor.insertNodeAtSelection(table);
+
+        this._setTableCellHandlers(table);
+
+        this.editor.logMessage(_('Table added'));
+        this.editor.updateState();
+        return table;
+    };
+
+    this._setTableCellHandlers = function(table) {
+        // make each cell select its full contents if it's clicked
+        addEventHandler(table, 'click', this._selectContentIfEmpty, this);
+
+        var cells = table.getElementsByTagName('td');
+        for (var i=0; i < cells.length; i++) {
+            addEventHandler(cells[i], 'click', this._selectContentIfEmpty, this);
+        };
+        
+        // select the nbsp in the first cell
+        var firstcell = cells[0];
+        if (firstcell) {
+            var children = firstcell.childNodes;
+            if (children.length == 1 && children[0].nodeType == 3 && 
+                    children[0].nodeValue == '\xa0') {
+                var selection = this.editor.getSelection();
+                selection.selectNodeContents(firstcell);
+            };
+        };
+    };
+    
+    this._selectContentIfEmpty = function() {
+        var selNode = this.editor.getSelectedNode();
+        var cell = this.editor.getNearestParentOfType(selNode, 'td');
+        if (!cell) {
+            return;
+        };
+        var children = cell.childNodes;
+        if (children.length == 1 && children[0].nodeType == 3 && 
+                children[0].nodeValue == '\xa0') {
+            var selection = this.editor.getSelection();
+            selection.selectNodeContents(cell);
+        };
+    };
+
+    this.addTableRow = function() {
+        /* Find the current row and add a row after it */
+        var currnode = this.editor.getSelectedNode();
+        var currtbody = this.editor.getNearestParentOfType(currnode, "TBODY");
+        var bodytype = "tbody";
+        if (!currtbody) {
+            currtbody = this.editor.getNearestParentOfType(currnode, "THEAD");
+            bodytype = "thead";
+        }
+        var parentrow = this.editor.getNearestParentOfType(currnode, "TR");
+        var nextrow = parentrow.nextSibling;
+
+        // get the number of cells we should place
+        var colcount = 0;
+        for (var i=0; i < currtbody.childNodes.length; i++) {
+            var el = currtbody.childNodes[i];
+            if (el.nodeType != 1) {
+                continue;
+            }
+            if (el.nodeName.toLowerCase() == 'tr') {
+                var cols = 0;
+                for (var j=0; j < el.childNodes.length; j++) {
+                    if (el.childNodes[j].nodeType == 1) {
+                        cols++;
+                    }
+                }
+                if (cols > colcount) {
+                    colcount = cols;
+                }
+            }
+        }
+
+        var newrow = this.editor.getInnerDocument().createElement("TR");
+
+        for (var i = 0; i < colcount; i++) {
+            var newcell;
+            if (bodytype == 'tbody') {
+                newcell = this.editor.getInnerDocument().createElement("TD");
+            } else {
+                newcell = this.editor.getInnerDocument().createElement("TH");
+            }
+            var newcellvalue = this.editor.getInnerDocument().createTextNode("\u00a0");
+            newcell.appendChild(newcellvalue);
+            newrow.appendChild(newcell);
+        }
+
+        if (!nextrow) {
+            currtbody.appendChild(newrow);
+        } else {
+            currtbody.insertBefore(newrow, nextrow);
+        }
+        
+        this.editor.focusDocument();
+        this.editor.logMessage(_('Table row added'));
+    };
+
+    this.delTableRow = function() {
+        /* Find the current row and delete it */
+        var currnode = this.editor.getSelectedNode();
+        var parentrow = this.editor.getNearestParentOfType(currnode, "TR");
+        if (!parentrow) {
+            this.editor.logMessage(_('No row to delete'), 1);
+            return;
+        }
+
+        // move selection aside
+        // XXX: doesn't work if parentrow is the only row of thead/tbody/tfoot
+        // XXX: doesn't preserve the colindex
+        var selection = this.editor.getSelection();
+        if (parentrow.nextSibling) {
+            selection.selectNodeContents(parentrow.nextSibling.firstChild);
+        } else if (parentrow.previousSibling) {
+            selection.selectNodeContents(parentrow.previousSibling.firstChild);
+        };
+
+        // remove the row
+        parentrow.parentNode.removeChild(parentrow);
+
+        this.editor.focusDocument();
+        this.editor.logMessage(_('Table row removed'));
+    };
+
+    this.addTableColumn = function() {
+        /* Add a new column after the current column */
+        var currnode = this.editor.getSelectedNode();
+        var currtd = this.editor.getNearestParentOfType(currnode, 'TD');
+        if (!currtd) {
+            currtd = this.editor.getNearestParentOfType(currnode, 'TH');
+        }
+        if (!currtd) {
+            this.editor.logMessage(_('No parentcolumn found!'), 1);
+            return;
+        }
+        var currtr = this.editor.getNearestParentOfType(currnode, 'TR');
+        var currtable = this.editor.getNearestParentOfType(currnode, 'TABLE');
+        
+        // get the current index
+        var tdindex = this._getColIndex(currtd);
+        // XXX this looks like a debug message, remove
+        this.editor.logMessage(_('tdindex: ${tdindex}'));
+
+        // now add a column to all rows
+        // first the thead
+        var theads = currtable.getElementsByTagName('THEAD');
+        if (theads) {
+            for (var i=0; i < theads.length; i++) {
+                // let's assume table heads only have ths
+                var currthead = theads[i];
+                for (var j=0; j < currthead.childNodes.length; j++) {
+                    var tr = currthead.childNodes[j];
+                    if (tr.nodeType != 1) {
+                        continue;
+                    }
+                    var currindex = 0;
+                    for (var k=0; k < tr.childNodes.length; k++) {
+                        var th = tr.childNodes[k];
+                        if (th.nodeType != 1) {
+                            continue;
+                        }
+                        if (currindex == tdindex) {
+                            var doc = this.editor.getInnerDocument();
+                            var newth = doc.createElement('th');
+                            var text = doc.createTextNode('\u00a0');
+                            newth.appendChild(text);
+                            if (tr.childNodes.length == k+1) {
+                                // the column will be on the end of the row
+                                tr.appendChild(newth);
+                            } else {
+                                tr.insertBefore(newth, tr.childNodes[k + 1]);
+                            }
+                            break;
+                        }
+                        currindex++;
+                    }
+                }
+            }
+        }
+
+        // then the tbody
+        var tbodies = currtable.getElementsByTagName('TBODY');
+        if (tbodies) {
+            for (var i=0; i < tbodies.length; i++) {
+                // let's assume table heads only have ths
+                var currtbody = tbodies[i];
+                for (var j=0; j < currtbody.childNodes.length; j++) {
+                    var tr = currtbody.childNodes[j];
+                    if (tr.nodeType != 1) {
+                        continue;
+                    }
+                    var currindex = 0;
+                    for (var k=0; k < tr.childNodes.length; k++) {
+                        var td = tr.childNodes[k];
+                        if (td.nodeType != 1) {
+                            continue;
+                        }
+                        if (currindex == tdindex) {
+                            var doc = this.editor.getInnerDocument();
+                            var newtd = doc.createElement('td');
+                            var text = doc.createTextNode('\u00a0');
+                            newtd.appendChild(text);
+                            if (tr.childNodes.length == k+1) {
+                                // the column will be on the end of the row
+                                tr.appendChild(newtd);
+                            } else {
+                                tr.insertBefore(newtd, tr.childNodes[k + 1]);
+                            }
+                            break;
+                        }
+                        currindex++;
+                    }
+                }
+            }
+        }
+        this.editor.focusDocument();
+        this.editor.logMessage(_('Table column added'));
+    };
+
+    this.delTableColumn = function() {
+        /* remove a column */
+        var currnode = this.editor.getSelectedNode();
+        var currtd = this.editor.getNearestParentOfType(currnode, 'TD');
+        if (!currtd) {
+            currtd = this.editor.getNearestParentOfType(currnode, 'TH');
+        }
+        var currcolindex = this._getColIndex(currtd);
+        var currtable = this.editor.getNearestParentOfType(currnode, 'TABLE');
+
+        // move selection aside
+        var selection = this.editor.getSelection();
+        if (currtd.nextSibling) {
+            selection.selectNodeContents(currtd.nextSibling);
+        } else if (currtd.previousSibling) {
+            selection.selectNodeContents(currtd.previousSibling);
+        };
+
+        // remove the theaders
+        var heads = currtable.getElementsByTagName('THEAD');
+        if (heads.length) {
+            for (var i=0; i < heads.length; i++) {
+                var thead = heads[i];
+                for (var j=0; j < thead.childNodes.length; j++) {
+                    var tr = thead.childNodes[j];
+                    if (tr.nodeType != 1) {
+                        continue;
+                    }
+                    var currindex = 0;
+                    for (var k=0; k < tr.childNodes.length; k++) {
+                        var th = tr.childNodes[k];
+                        if (th.nodeType != 1) {
+                            continue;
+                        }
+                        if (currindex == currcolindex) {
+                            tr.removeChild(th);
+                            break;
+                        }
+                        currindex++;
+                    }
+                }
+            }
+        }
+
+        // now we remove the column field, a bit harder since we need to take 
+        // colspan and rowspan into account XXX Not right, fix theads as well
+        var bodies = currtable.getElementsByTagName('TBODY');
+        for (var i=0; i < bodies.length; i++) {
+            var currtbody = bodies[i];
+            var relevant_rowspan = 0;
+            for (var j=0; j < currtbody.childNodes.length; j++) {
+                var tr = currtbody.childNodes[j];
+                if (tr.nodeType != 1) {
+                    continue;
+                }
+                var currindex = 0
+                for (var k=0; k < tr.childNodes.length; k++) {
+                    var cell = tr.childNodes[k];
+                    if (cell.nodeType != 1) {
+                        continue;
+                    }
+                    var colspan = cell.colSpan;
+                    if (currindex == currcolindex) {
+                        tr.removeChild(cell);
+                        break;
+                    }
+                    currindex++;
+                }
+            }
+        }
+        this.editor.focusDocument();
+        this.editor.logMessage(_('Table column deleted'));
+    };
+
+    this.delTable = function() {
+        /* delete the current table */
+        var currnode = this.editor.getSelectedNode();
+        var table = this.editor.getNearestParentOfType(currnode, 'table');
+        if (!table) {
+            this.editor.logMessage(_('Not inside a table!'));
+            return;
+        };
+        table.parentNode.removeChild(table);
+        this.editor.logMessage(_('Table removed'));
+    };
+
+    this.setColumnAlign = function(newalign) {
+        /* change the alignment of a full column */
+        var currnode = this.editor.getSelectedNode();
+        var currtd = this.editor.getNearestParentOfType(currnode, "TD");
+        var bodytype = 'tbody';
+        if (!currtd) {
+            currtd = this.editor.getNearestParentOfType(currnode, "TH");
+            bodytype = 'thead';
+        }
+        var currcolindex = this._getColIndex(currtd);
+        var currtable = this.editor.getNearestParentOfType(currnode, "TABLE");
+
+        // unfortunately this is not enough to make the browsers display
+        // the align, we need to set it on individual cells as well and
+        // mind the rowspan...
+        for (var i=0; i < currtable.childNodes.length; i++) {
+            var currtbody = currtable.childNodes[i];
+            if (currtbody.nodeType != 1 || 
+                    (currtbody.nodeName.toUpperCase() != "THEAD" &&
+                        currtbody.nodeName.toUpperCase() != "TBODY")) {
+                continue;
+            }
+            for (var j=0; j < currtbody.childNodes.length; j++) {
+                var row = currtbody.childNodes[j];
+                if (row.nodeType != 1) {
+                    continue;
+                }
+                var index = 0;
+                for (var k=0; k < row.childNodes.length; k++) {
+                    var cell = row.childNodes[k];
+                    if (cell.nodeType != 1) {
+                        continue;
+                    }
+                    if (index == currcolindex) {
+                        if (this.editor.config.use_css) {
+                            cell.style.textAlign = newalign;
+                        } else {
+                            cell.setAttribute('align', newalign);
+                        }
+                        cell.className = 'align-' + newalign;
+                    }
+                    index++;
+                }
+            }
+        }
+    };
+
+    this.setTableClass = function(sel_class) {
+        /* set the class for the table */
+        var currnode = this.editor.getSelectedNode();
+        var currtable = this.editor.getNearestParentOfType(currnode, 'TABLE');
+
+        if (currtable) {
+            currtable.className = sel_class;
+        }
+    };
+
+    this._getColIndex = function(currcell) {
+        /* Given a node, return an integer for which column it is */
+        var prevsib = currcell.previousSibling;
+        var currcolindex = 0;
+        while (prevsib) {
+            if (prevsib.nodeType == 1 && 
+                    (prevsib.tagName.toUpperCase() == "TD" || 
+                        prevsib.tagName.toUpperCase() == "TH")) {
+                var colspan = prevsib.colSpan;
+                if (colspan) {
+                    currcolindex += parseInt(colspan);
+                } else {
+                    currcolindex++;
+                }
+            }
+            prevsib = prevsib.previousSibling;
+            if (currcolindex > 30) {
+                alert(_("Recursion detected when counting column position"));
+                return;
+            }
+        }
+
+        return currcolindex;
+    };
+
+    this._getColumnAlign = function(selNode) {
+        /* return the alignment setting of the current column */
+        var align;
+        var td = this.editor.getNearestParentOfType(selNode, 'td');
+        if (!td) {
+            td = this.editor.getNearestParentOfType(selNode, 'th');
+        };
+        if (td) {
+            align = td.getAttribute('align');
+            if (this.editor.config.use_css) {
+                align = td.style.textAlign;
+            };
+        };
+        return align;
+    };
+
+    this.fixTable = function(event) {
+        /* fix the table so it can be processed by Kupu */
+        // since this can be quite a nasty creature we can't just use the
+        // helper methods
+        
+        // first we create a new tbody element
+        var currnode = this.editor.getSelectedNode();
+        var table = this.editor.getNearestParentOfType(currnode, 'TABLE');
+        if (!table) {
+            this.editor.logMessage(_('Not inside a table!'));
+            return;
+        };
+        this._fixTableHelper(table);
+    };
+
+    this._isBodyRow = function(row) {
+        for (var node = row.firstChild; node; node=node.nextSibling) {
+            if (/TD/.test(node.nodeName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    this._cleanCell = function(el) {
+        dump('_cleanCell('+el.innerHTML+')\n');
+        // Remove formatted div or p from a cell
+        var node, nxt, n;
+        for (node = el.firstChild; node;) {
+            if (/DIV|P/.test(node.nodeName)) {
+                for (var n = node.firstChild; n;) {
+                    var nxt = n.nextSibling;
+                    el.insertBefore(n, node); // Move nodes out of div
+                    n = nxt;
+                }
+                nxt = node.nextSibling;
+                el.removeChild(node);
+                node = nxt;
+            } else {
+                node = node.nextSibling;
+            }
+        }
+        var c;
+        while (el.firstChild && (c = el.firstChild).nodeType==3 && /^\s+/.test(c.data)) {
+            c.data = c.data.replace(/^\s+/, '');
+            if (!c.data) {
+                el.removeChild(c);
+            } else {
+                break;
+            };
+        };
+        while (el.lastChild && (c = el.lastChild).nodeType==3 && /\s+$/.test(c.data)) {
+            c.data = c.data.replace(/\s+$/, '');
+            if (!c.data) {
+                el.removeChild(c);
+            } else {
+                break;
+            };
+        };
+        el.removeAttribute('colSpan');
+        el.removeAttribute('rowSpan');
+    }
+    this._countCols = function(rows, numcols) {
+        for (var i=0; i < rows.length; i++) {
+            var row = rows[i];
+            var currnumcols = 0;
+            for (var node = row.firstChild; node; node=node.nextSibling) {
+                if (/td|th/i.test(node.nodeName)) {
+                    currnumcols += parseInt(node.getAttribute('colSpan') || '1');
+                };
+            };
+            if (currnumcols > numcols) {
+                numcols = currnumcols;
+            };
+        };
+        return numcols;
+    }
+
+    this._cleanRows = function(rows, container, numcols) {
+        // now walk through all rows to clean them up
+        for (var i=0; i < rows.length; i++) {
+            dump("row "+i+'\n');
+            var row = rows[i];
+            var doc = this.editor.getInnerDocument();
+            var newrow = doc.createElement('tr');
+            if (row.className) {
+                newrow.className = row.className;
+            }
+            for (var node = row.firstChild; node;) {
+                dump("child\n");
+                var nxt = node.nextSibling;
+                if (/TD|TH/.test(node.nodeName)) {
+                    this._cleanCell(node);
+                    newrow.appendChild(node);
+                };
+                node = nxt;
+            };
+            if (newrow.childNodes.length) {
+                container.appendChild(newrow);
+            };
+        };
+        // now make sure all rows have the correct length
+        for (row = container.firstChild; row; row=row.nextSibling) {
+            var cellname = row.lastChild.nodeName;
+            while (row.childNodes.length < numcols) {
+                var cell = doc.createElement(cellname);
+                var nbsp = doc.createTextNode('\u00a0');
+                cell.appendChild(nbsp);
+                row.appendChild(cell);
+            };
+        };
+    };
+
+    this._fixTableHelper = function(table) {
+        /* the code to actually fix tables */
+        var doc = this.editor.getInnerDocument();
+        var thead = doc.createElement('thead');
+        var tbody = doc.createElement('tbody');
+        var tfoot = doc.createElement('tfoot');
+
+        var table_classes = this.editor.config.table_classes;
+        function cleanClassName(name) {
+            var allowed_classes = table_classes['class'];
+            for (var i = 0; i < allowed_classes.length; i++) {
+                var classname = allowed_classes[i];
+                classname = classname.classname || classname;
+                if (classname==name) return name;
+            };
+            return allowed_classes[0];
+        }
+        if (table_classes) {
+            table.className = cleanClassName(table.className);
+        } else {
+            table.removeAttribute('class');
+            table.removeAttribute('className');
+        };
+        table.removeAttribute('border');
+        table.removeAttribute('cellpadding');
+        table.removeAttribute('cellPadding');
+        table.removeAttribute('cellspacing');
+        table.removeAttribute('cellSpacing');
+
+        // now get all the rows of the table, the rows can either be
+        // direct descendants of the table or inside a 'tbody', 'thead'
+        // or 'tfoot' element
+
+        var hrows = [], brows = [], frows = [];
+        for (var node = table.firstChild; node; node = node.nextSibling) {
+            var nodeName = node.nodeName;
+            if (/TR/.test(node.nodeName)) {
+                brows.push(node);
+            } else if (/THEAD|TBODY|TFOOT/.test(node.nodeName)) {
+                var rows = nodeName=='THEAD' ? hrows : nodeName=='TFOOT' ? frows : brows;
+                for (var inode = node.firstChild; inode; inode = inode.nextSibling) {
+                    if (/TR/.test(inode.nodeName)) {
+                        rows.push(inode);
+                    };
+                };
+            };
+        };
+        /* Extract thead and tfoot from tbody */
+        dump('extract head and foot\n');
+        while (brows.length && !this._isBodyRow(brows[0])) {
+            hrows.push(brows[0]);
+            brows.shift();
+        }
+        while (brows.length && !this._isBodyRow(brows[brows.length-1])) {
+            var last = brows[brows.length-1];
+            brows.length -= 1;
+            frows.unshift(last);
+        }
+        dump('count cols\n');
+        // now find out how many cells our rows should have
+        var numcols = this._countCols(hrows, 0);
+        numcols = this._countCols(brows, numcols);
+        numcols = this._countCols(frows, numcols);
+
+        dump('clean rows\n');
+        // now walk through all rows to clean them up
+        this._cleanRows(hrows, thead);
+        this._cleanRows(brows, tbody);
+        this._cleanRows(frows, tfoot);
+
+        // now remove all the old stuff from the table and add the new
+        // tbody
+        dump('remove old\n');
+        while (table.firstChild) {
+            table.removeChild(table.firstChild);
+        }
+        if (hrows.length)
+            table.appendChild(thead);
+        if (brows.length)
+            table.appendChild(tbody);
+        if (frows.length)
+            table.appendChild(tfoot);
+        dump('finish up\n');
+
+        this.editor.focusDocument();
+        this.editor.logMessage(_('Table cleaned up'));
+    };
+
+    this.fixAllTables = function() {
+        /* fix all the tables in the document at once */
+        var tables = this.editor.getInnerDocument().getElementsByTagName('table');
+        for (var i=0; i < tables.length; i++) {
+            this._fixTableHelper(tables[i]);
+        };
+    };
+};
+
+TableTool.prototype = new KupuTool;
+
+function TableToolBox(addtabledivid, edittabledivid, newrowsinputid, 
+                    newcolsinputid, makeheaderinputid, classselectid, alignselectid, addtablebuttonid,
+                    addrowbuttonid, delrowbuttonid, addcolbuttonid, delcolbuttonid, fixbuttonid,
+                    fixallbuttonid, toolboxid, plainclass, activeclass) {
+    /* The table tool */
+
+    // XXX There are some awfully long methods in here!!
+    
+
+    // a lot of dependencies on html elements here, but most implementations
+    // will use them all I guess
+    this.addtablediv = getFromSelector(addtabledivid);
+    this.edittablediv = getFromSelector(edittabledivid);
+    this.newrowsinput = getFromSelector(newrowsinputid);
+    this.newcolsinput = getFromSelector(newcolsinputid);
+    this.makeheaderinput = getFromSelector(makeheaderinputid);
+    this.classselect = getFromSelector(classselectid);
+    this.alignselect = getFromSelector(alignselectid);
+    this.addtablebutton = getFromSelector(addtablebuttonid);
+    this.addrowbutton = getFromSelector(addrowbuttonid);
+    this.delrowbutton = getFromSelector(delrowbuttonid);
+    this.addcolbutton = getFromSelector(addcolbuttonid);
+    this.delcolbutton = getFromSelector(delcolbuttonid);
+    this.fixbutton = getFromSelector(fixbuttonid);
+    this.fixallbutton = getFromSelector(fixallbuttonid);
+    this.toolboxel = getFromSelector(toolboxid);
+    this.plainclass = plainclass;
+    this.activeclass = activeclass;
+
+    // register event handlers
+    this.initialize = function(tool, editor) {
+        /* attach the event handlers */
+        this.tool = tool;
+        this.editor = editor;
+        // build the select list of table classes if configured
+        if (this.editor.config.table_classes) {
+            var classes = this.editor.config.table_classes['class'];
+            while (this.classselect.hasChildNodes()) {
+                this.classselect.removeChild(this.classselect.firstChild);
+            };
+            for (var i=0; i < classes.length; i++) {
+                var classname = classes[i];
+                classname = classname.classname || classname;
+                var option = document.createElement('option');
+                var content = document.createTextNode(classname);
+                option.appendChild(content);
+                option.setAttribute('value', classname);
+                this.classselect.appendChild(option);
+            };
+        };
+        addEventHandler(this.addtablebutton, "click", this.addTable, this);
+        addEventHandler(this.addrowbutton, "click", this.tool.addTableRow, this.tool);
+        addEventHandler(this.delrowbutton, "click", this.tool.delTableRow, this.tool);
+        addEventHandler(this.addcolbutton, "click", this.tool.addTableColumn, this.tool);
+        addEventHandler(this.delcolbutton, "click", this.tool.delTableColumn, this.tool);
+        addEventHandler(this.alignselect, "change", this.setColumnAlign, this);
+        addEventHandler(this.classselect, "change", this.setTableClass, this);
+        addEventHandler(this.fixbutton, "click", this.tool.fixTable, this.tool);
+        addEventHandler(this.fixallbutton, "click", this.tool.fixAllTables, this.tool);
+        this.addtablediv.style.display = "block";
+        this.edittablediv.style.display = "none";
+        this.editor.logMessage(_('Table tool initialized'));
+    };
+
+    this.updateState = function(selNode) {
+        /* update the state (add/edit) and update the pulldowns (if required) */
+        var table = this.editor.getNearestParentOfType(selNode, 'table');
+        if (table) {
+            this.addtablediv.style.display = "none";
+            this.edittablediv.style.display = "block";
+
+            var align = this.tool._getColumnAlign(selNode);
+            selectSelectItem(this.alignselect, align);
+            selectSelectItem(this.classselect, table.className);
+            if (this.toolboxel) {
+                this.toolboxel.className = this.activeclass;
+            };
+        } else {
+            this.edittablediv.style.display = "none";
+            this.addtablediv.style.display = "block";
+            this.alignselect.selectedIndex = 0;
+            this.classselect.selectedIndex = 0;
+            if (this.toolboxel) {
+                this.toolboxel.className = this.plainclass;
+            };
+        };
+    };
+
+    this.addTable = function() {
+        /* add a table */
+        var rows = this.newrowsinput.value;
+        var cols = this.newcolsinput.value;
+        var makeHeader = this.makeheaderinput.checked;
+        // XXX getFromSelector
+        var classchooser = getFromSelector("kupu-table-classchooser-add");
+        var tableclass = this.classselect.options[this.classselect.selectedIndex].value;
+        
+        this.tool.createTable(rows, cols, makeHeader, tableclass);
+    };
+
+    this.setColumnAlign = function() {
+        /* set the alignment of the current column */
+        var newalign = this.alignselect.options[this.alignselect.selectedIndex].value;
+        this.tool.setColumnAlign(newalign);
+    };
+
+    this.setTableClass = function() {
+        /* set the class for the current table */
+        var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
+        if (sel_class) {
+            this.tool.setTableClass(sel_class);
+        };
+    };
+};
+
+TableToolBox.prototype = new KupuToolBox;
+
+function ListTool(addulbuttonid, addolbuttonid, ulstyleselectid, olstyleselectid) {
+    /* tool to set list styles */
+
+    this.addulbutton = getFromSelector(addulbuttonid);
+    this.addolbutton = getFromSelector(addolbuttonid);
+    this.ulselect = getFromSelector(ulstyleselectid);
+    this.olselect = getFromSelector(olstyleselectid);
+
+    this.style_to_type = {'decimal': '1',
+                            'lower-alpha': 'a',
+                            'upper-alpha': 'A',
+                            'lower-roman': 'i',
+                            'upper-roman': 'I',
+                            'disc': 'disc',
+                            'square': 'square',
+                            'circle': 'circle',
+                            'none': 'none'
+                            };
+    this.type_to_style = {'1': 'decimal',
+                            'a': 'lower-alpha',
+                            'A': 'upper-alpha',
+                            'i': 'lower-roman',
+                            'I': 'upper-roman',
+                            'disc': 'disc',
+                            'square': 'square',
+                            'circle': 'circle',
+                            'none': 'none'
+                            };
+    
+    this.initialize = function(editor) {
+        /* attach event handlers */
+        this.editor = editor;
+        this._fixTabIndex(this.addulbutton);
+        this._fixTabIndex(this.addolbutton);
+        this._fixTabIndex(this.ulselect);
+        this._fixTabIndex(this.olselect);
+
+        addEventHandler(this.addulbutton, "click", this.addUnorderedList, this);
+        addEventHandler(this.addolbutton, "click", this.addOrderedList, this);
+        addEventHandler(this.ulselect, "change", this.setUnorderedListStyle, this);
+        addEventHandler(this.olselect, "change", this.setOrderedListStyle, this);
+        this.ulselect.style.display = "none";
+        this.olselect.style.display = "none";
+
+        this.editor.logMessage(_('List style tool initialized'));
+    };
+
+    this._handleStyles = function(currnode, onselect, offselect) {
+        if (this.editor.config.use_css) {
+            var currstyle = currnode.style.listStyleType;
+        } else {
+            var currstyle = this.type_to_style[currnode.getAttribute('type')];
+        }
+        selectSelectItem(onselect, currstyle);
+        offselect.style.display = "none";
+        onselect.style.display = "inline";
+        offselect.selectedIndex = 0;
+    };
+
+    this.updateState = function(selNode) {
+        /* update the visibility and selection of the list type pulldowns */
+        // we're going to walk through the tree manually since we want to 
+        // check on 2 items at the same time
+        for (var currnode=selNode; currnode; currnode=currnode.parentNode) {
+            var tag = currnode.nodeName.toLowerCase();
+            if (tag == 'ul') {
+                this._handleStyles(currnode, this.ulselect, this.olselect);
+                return;
+            } else if (tag == 'ol') {
+                this._handleStyles(currnode, this.olselect, this.ulselect);
+                return;
+            }
+        }
+        with(this.ulselect) {
+            selectedIndex = 0;
+            style.display = "none";
+        };
+        with(this.olselect) {
+            selectedIndex = 0;
+            style.display = "none";
+        };
+    };
+
+    this.addList = function(command) {
+        this.ulselect.style.display = "inline";
+        this.olselect.style.display = "none";
+        this.editor.execCommand(command);
+        this.editor.focusDocument();
+    };
+    this.addUnorderedList = function() {
+        /* add an unordered list */
+        this.addList("insertunorderedlist");
+    };
+
+    this.addOrderedList = function() {
+        /* add an ordered list */
+        this.addList("insertorderedlist");
+    };
+
+    this.setListStyle = function(tag, select) {
+        /* set the type of an ul */
+        var currnode = this.editor.getSelectedNode();
+        var l = this.editor.getNearestParentOfType(currnode, tag);
+        var style = select.options[select.selectedIndex].value;
+        if (this.editor.config.use_css) {
+            l.style.listStyleType = style;
+        } else {
+            l.setAttribute('type', this.style_to_type[style]);
+        }
+        this.editor.focusDocument();
+        this.editor.logMessage(_('List style changed'));
+    };
+
+    this.setUnorderedListStyle = function() {
+        /* set the type of an ul */
+        this.setListStyle('ul', this.ulselect);
+    };
+
+    this.setOrderedListStyle = function() {
+        /* set the type of an ol */
+        this.setListStyle('ol', this.olselect);
+    };
+
+    this.enable = function() {
+        KupuButtonEnable(this.addulbutton);
+        KupuButtonEnable(this.addolbutton);
+        this.ulselect.disabled = "";
+        this.olselect.disabled = "";
+    }
+    this.disable = function() {
+        KupuButtonDisable(this.addulbutton);
+        KupuButtonDisable(this.addolbutton);
+        this.ulselect.disabled = "disabled";
+        this.olselect.disabled = "disabled";
+    }
+};
+
+ListTool.prototype = new KupuTool;
+
+function ShowPathTool() {
+    /* shows the path to the current element in the status bar */
+
+    this.updateState = function(selNode) {
+        /* calculate and display the path */
+        var path = '';
+        var url = null; // for links we want to display the url too
+        var currnode = selNode;
+        while (currnode != null && currnode.nodeName != '#document') {
+            if (currnode.nodeName.toLowerCase() == 'a') {
+                url = currnode.getAttribute('href');
+            };
+            path = '/' + currnode.nodeName.toLowerCase() + path;
+            currnode = currnode.parentNode;
+        }
+        
+        try {
+            window.status = url ? 
+                    (path.toString() + ' - contains link to \'' + 
+                        url.toString() + '\'') :
+                    path;
+        } catch (e) {
+            this.editor.logMessage(_('Could not set status bar message, ' +
+                                    'check your browser\'s security settings.'
+                                    ), 1);
+        };
+    };
+};
+
+ShowPathTool.prototype = new KupuTool;
+
+function ViewSourceTool() {
+    /* tool to provide a 'show source' context menu option */
+    this.sourceWindow = null;
+    
+    this.viewSource = function() {
+        /* open a window and write the current contents of the iframe to it */
+        if (this.sourceWindow) {
+            this.sourceWindow.close();
+        };
+        this.sourceWindow = window.open('#', 'sourceWindow');
+        
+        //var transform = this.editor._filterContent(this.editor.getInnerDocument().documentElement);
+        //var contents = transform.xml; 
+        var contents = '<html>\n' + this.editor.getInnerDocument().documentElement.innerHTML + '\n</html>';
+        
+        var doc = this.sourceWindow.document;
+        doc.write('\xa0');
+        doc.close();
+        var body = doc.getElementsByTagName("body")[0];
+        while (body.hasChildNodes()) {
+            body.removeChild(body.firstChild);
+        };
+        var pre = doc.createElement('pre');
+        var textNode = doc.createTextNode(contents);
+        body.appendChild(pre);
+        pre.appendChild(textNode);
+    };
+    
+    this.createContextMenuElements = function(selNode, event) {
+        /* create the context menu element */
+        return new Array(new ContextMenuElement(_('View source'), this.viewSource, this));
+    };
+};
+
+ViewSourceTool.prototype = new KupuTool;
+
+function DefinitionListTool(dlbuttonid) {
+    /* a tool for managing definition lists
+
+        the dl elements should behave much like plain lists, and the keypress
+        behaviour should be similar
+    */
+
+    this.dlbutton = getFromSelector(dlbuttonid);
+    
+    this.initialize = function(editor) {
+        /* initialize the tool */
+        this.editor = editor;
+        this._fixTabIndex(this.dlbutton);
+        addEventHandler(this.dlbutton, 'click', this.createDefinitionList, this);
+        addEventHandler(editor.getInnerDocument(), 'keyup', this._keyDownHandler, this);
+        addEventHandler(editor.getInnerDocument(), 'keypress', this._keyPressHandler, this);
+    };
+
+    // even though the following methods may seem view related, they belong 
+    // here, since they describe core functionality rather then view-specific
+    // stuff
+    this.handleEnterPress = function(selNode) {
+        var dl = this.editor.getNearestParentOfType(selNode, 'dl');
+        if (dl) {
+            var dt = this.editor.getNearestParentOfType(selNode, 'dt');
+            if (dt) {
+                if (dt.childNodes.length == 1 && dt.childNodes[0].nodeValue == '\xa0') {
+                    this.escapeFromDefinitionList(dl, dt, selNode);
+                    return;
+                };
+
+                var selection = this.editor.getSelection();
+                var startoffset = selection.startOffset();
+                var endoffset = selection.endOffset(); 
+                if (endoffset > startoffset) {
+                    // throw away any selected stuff
+                    selection.cutChunk(startoffset, endoffset);
+                    selection = this.editor.getSelection();
+                    startoffset = selection.startOffset();
+                };
+                
+                var ellength = selection.getElementLength(selection.parentElement());
+                if (startoffset >= ellength - 1) {
+                    // create a new element
+                    this.createDefinition(dl, dt);
+                } else {
+                    var doc = this.editor.getInnerDocument();
+                    var newdt = selection.splitNodeAtSelection(dt);
+                    var newdd = doc.createElement('dd');
+                    while (newdt.hasChildNodes()) {
+                        if (newdt.firstChild != newdt.lastChild || newdt.firstChild.nodeName.toLowerCase() != 'br') {
+                            newdd.appendChild(newdt.firstChild);
+                        };
+                    };
+                    newdt.parentNode.replaceChild(newdd, newdt);
+                    selection.selectNodeContents(newdd);
+                    selection.collapse();
+                };
+            } else {
+                var dd = this.editor.getNearestParentOfType(selNode, 'dd');
+                if (!dd) {
+                    this.editor.logMessage(_('Not inside a definition list element!'));
+                    return;
+                };
+                if (dd.childNodes.length == 1 && dd.childNodes[0].nodeValue == '\xa0') {
+                    this.escapeFromDefinitionList(dl, dd, selNode);
+                    return;
+                };
+                var selection = this.editor.getSelection();
+                var startoffset = selection.startOffset();
+                var endoffset = selection.endOffset();
+                if (endoffset > startoffset) {
+                    // throw away any selected stuff
+                    selection.cutChunk(startoffset, endoffset);
+                    selection = this.editor.getSelection();
+                    startoffset = selection.startOffset();
+                };
+                var ellength = selection.getElementLength(selection.parentElement());
+                if (startoffset >= ellength - 1) {
+                    // create a new element
+                    this.createDefinitionTerm(dl, dd);
+                } else {
+                    // add a break and continue in this element
+                    var br = this.editor.getInnerDocument().createElement('br');
+                    this.editor.insertNodeAtSelection(br, 1);
+                    //var selection = this.editor.getSelection();
+                    //selection.moveStart(1);
+                    selection.collapse(true);
+                };
+            };
+        };
+    };
+
+    this.handleTabPress = function(selNode) {
+    };
+
+    this._keyDownHandler = function(event) {
+        var selNode = this.editor.getSelectedNode();
+        var dl = this.editor.getNearestParentOfType(selNode, 'dl');
+        if (!dl) {
+            return;
+        };
+        switch (event.keyCode) {
+            case 13:
+                if (event.preventDefault) {
+                    event.preventDefault();
+                } else {
+                    event.returnValue = false;
+                };
+                break;
+        };
+    };
+
+    this._keyPressHandler = function(event) {
+        var selNode = this.editor.getSelectedNode();
+        var dl = this.editor.getNearestParentOfType(selNode, 'dl');
+        if (!dl) {
+            return;
+        };
+        switch (event.keyCode) {
+            case 13:
+                this.handleEnterPress(selNode);
+                if (event.preventDefault) {
+                    event.preventDefault();
+                } else {
+                    event.returnValue = false;
+                };
+                break;
+            case 9:
+                if (event.preventDefault) {
+                    event.preventDefault();
+                } else {
+                    event.returnValue = false;
+                };
+                this.handleTabPress(selNode);
+        };
+    };
+
+    this.createDefinitionList = function() {
+        /* create a new definition list (dl) */
+        var selection = this.editor.getSelection();
+        var doc = this.editor.getInnerDocument();
+
+        var selection = this.editor.getSelection();
+        var cloned = selection.cloneContents();
+        // first get the 'first line' (until the first break) and use it
+        // as the dt's content
+        var iterator = new NodeIterator(cloned);
+        var currnode = null;
+        var remove = false;
+        while (currnode = iterator.next()) {
+            if (currnode.nodeName.toLowerCase() == 'br') {
+                remove = true;
+            };
+            if (remove) {
+                var next = currnode;
+                while (!next.nextSibling) {
+                    next = next.parentNode;
+                };
+                next = next.nextSibling;
+                iterator.setCurrent(next);
+                currnode.parentNode.removeChild(currnode);
+            };
+        };
+
+        var dtcontentcontainer = cloned;
+        var collapsetoend = false;
+        
+        var dl = doc.createElement('dl');
+        this.editor.insertNodeAtSelection(dl);
+        var dt = this.createDefinitionTerm(dl);
+        if (dtcontentcontainer.hasChildNodes()) {
+            collapsetoend = true;
+            while (dt.hasChildNodes()) {
+                dt.removeChild(dt.firstChild);
+            };
+            while (dtcontentcontainer.hasChildNodes()) {
+                dt.appendChild(dtcontentcontainer.firstChild);
+            };
+        };
+
+        var selection = this.editor.getSelection();
+        selection.selectNodeContents(dt);
+        selection.collapse(collapsetoend);
+    };
+
+    this.createDefinitionTerm = function(dl, dd) {
+        /* create a new definition term inside the current dl */
+        var doc = this.editor.getInnerDocument();
+        var dt = doc.createElement('dt');
+        // somehow Mozilla seems to add breaks to all elements...
+        if (dd) {
+            if (dd.lastChild.nodeName.toLowerCase() == 'br') {
+                dd.removeChild(dd.lastChild);
+            };
+        };
+        // dd may be null here, if so we assume this is the first element in 
+        // the dl
+        if (!dd || dl == dd.lastChild) {
+            dl.appendChild(dt);
+        } else {
+            var nextsibling = dd.nextSibling;
+            if (nextsibling) {
+                dl.insertBefore(dt, nextsibling);
+            } else {
+                dl.appendChild(dt);
+            };
+        };
+        var nbsp = doc.createTextNode('\xa0');
+        dt.appendChild(nbsp);
+        var selection = this.editor.getSelection();
+        selection.selectNodeContents(dt);
+        selection.collapse();
+
+        this.editor.focusDocument();
+        return dt;
+    };
+
+    this.createDefinition = function(dl, dt, initial_content) {
+        var doc = this.editor.getInnerDocument();
+        var dd = doc.createElement('dd');
+        var nextsibling = dt.nextSibling;
+        // somehow Mozilla seems to add breaks to all elements...
+        if (dt) {
+            if (dt.lastChild.nodeName.toLowerCase() == 'br') {

[... 222 lines stripped ...]


Mime
View raw message