/*
  Copyright (C) 2005-2006 by Domenico De Felice

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

  Authors: Domenico De Felice, <dfdcod [at] gmail [dot] com>
*/


var Tratto = {};

Tratto.init = function() {
    Tratto.svgRoot = _('svg-root');

    XMPP4Moz.init();
    Tratto.database.init();
    Tratto.widgets.init();
    Tratto.bindings.init();
    Tratto.coords.init();
    Tratto.palette.init();
    Tratto.tools.init();
    Tratto.xmpp.init();
    Tratto.shapes.init();
};


Tratto.tools = {
    tools: {},
    init: function() {
        this._popup = _("tools-popup");

        Tratto.bindings.esc = function(event) {
            var sel = Tratto.tools.selected;
            if (sel && sel.onCancel) sel.onCancel(event);
        };
        Tratto.bindings.help.esc = "Cancel current operation";

        var s = Tratto.svgRoot;

        // Closures shared objects
        var dragged = false;
        var mousedown = false;
        var mousedown_event;
        var last_drag_x;
        var last_drag_y;
        var shape;
        var group;
        var bar;
        var bar_shape;

        Tratto.tools._removeBar = function() {
            if (! bar) return;
            _r(bar);
            bar = undefined;
            bar_shape = undefined;
        };

        s.addEventListener("mousemove", function(event) {
                               if (mousedown) dragged = true;

                               var sel = Tratto.tools.selected;
                               if (sel && sel.onMouseMove) {
                                   Tratto.svgRoot.style.cursor = "";
                                   sel.onMouseMove(event);
                                   return;
                               }

                               if (!mousedown) {
                                   if (event.target == Tratto.svgRoot) {
                                       Tratto.svgRoot.style.cursor = "";
                                   } else {
                                       Tratto.svgRoot.style.cursor = "pointer";
                                   }
                                   return;
                               }

                               var target = mousedown_event.target;
                               if (target == Tratto.svgRoot) return;

                               var move = function() { return ! (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey); };
                               var resize = function() { return event.ctrlKey && ! (event.altKey || event.shiftKey || event.metaKey); };
                               var rotate = function() { return event.shiftKey && ! (event.altKey || event.ctrlKey || event.metaKey); };
                               var skew = function() { return event.shiftKey && event.ctrlKey && ! (event.altKey || event.metaKey); };

                               var offset_x = Tratto.coords.x - last_drag_x;
                               var offset_y = Tratto.coords.y - last_drag_y;
                               last_drag_x = Tratto.coords.x;
                               last_drag_y = Tratto.coords.y;

                               if (offset_x == 0 && offset_y == 0) return;

                               if (move()) {
                                   Tratto.svgRoot.style.cursor = "move";

                                   var tlist = new TransformList(shape.get("transform"));
                                   tlist.pre_translate(offset_x, offset_y);
                                   shape.set("transform", tlist.toString(), group);
                               } else if (resize()) {
                                   Tratto.svgRoot.style.cursor = "se-resize";

                                   var tlist = new TransformList(shape.get("transform"));

                                   var bbox = shape.dom.getBBox();
                                   var sx = 1 + offset_x / bbox.width;
                                   var sy = 1 + offset_y / bbox.height;
                                   var s = (sx > sy) ? sx : sy;

                                   tlist.scale(s, s);
        
                                   shape.set("transform", tlist.toString(), group);
        
                               } else if (rotate()) {
                                   Tratto.svgRoot.style.cursor = "e-resize";

                                   if (offset_x == 0) return;

                                   var bbox = shape.dom.getBBox();
                                   var px = bbox.x + (bbox.width / 2);
                                   var py = bbox.y + (bbox.height / 2);

                                   var tlist = new TransformList(shape.get("transform"));
                                   tlist.rotate(offset_x, px, py);
                                   shape.set("transform", tlist.toString(), group);
                               } else if (skew()) {
                                   var tlist = new TransformList(shape.get("transform"));

                                   if (offset_x != 0) {
                                       tlist.skewX(offset_x);
                                   }
                                   if (offset_y != 0) {
                                       tlist.skewY(offset_y);
                                   }

                                   shape.set("transform", tlist.toString(), group);
                               }
                           }, false);

        s.addEventListener("mousedown", function(event) {
                               mousedown = true;
                               mousedown_event = event;
                               shape = Tratto.shapes.wrap(event.target);
                               dragged = false;
                               last_drag_x = Tratto.coords.x;
                               last_drag_y = Tratto.coords.y;
                               group = Tratto.shapes.create_commands_group();

                               var sel = Tratto.tools.selected;
                               if (sel && sel.onMouseDown) sel.onMouseDown(event);
                           }, false);

        s.addEventListener("mouseup", function(event) {
                               mousedown = false;

                               Tratto.svgRoot.style.cursor = "";

                               var sel = Tratto.tools.selected;
                               if (sel && sel.onMouseUp) sel.onMouseUp(event);
                           }, false);

        s.addEventListener("click", function(event) {
                               if (dragged) {
                                   dragged = false;
                                   return;
                               }
        
                               var sel = Tratto.tools.selected;

                               var removeBar = Tratto.tools._removeBar;

                               if (sel) {
                                   if (sel.onClick) {
                                       bar && removeBar();
                                       sel.onClick(event);
                                       return;
                                   }

                                   Tratto.tools._putDown();
                               }

                               if (bar) {
                                   if (bar_shape.dom == event.target) return;

                                   removeBar();
                               }

                               if (event.target == Tratto.svgRoot) return;

                               bar_shape = Tratto.shapes.wrap(event.target);
                               bar = _cxul('vbox');

                               _a(_("values-bar"), bar);

                               var del_button = _cxul("button", {
                                   label: "Delete"
                                                              });
                               del_button.addEventListener('command', function(event) {
                                                               bar_shape.remove();
                                                               _r(bar);
                                                               bar = undefined;
                                                               bar_shape = undefined;
                                                           }, false);

                               _a(bar, del_button);

                               var colors_bar = _cxul('vbox');
                               _a(bar, colors_bar);

                               if (bar_shape.dom.hasAttributes()) {
                                   var attrs = bar_shape.dom.attributes;
                                   var info, getter, changer;

                                   for (var i = attrs.length-1; i>=0; i--) {
                                       if (attrs[i].name == "id" ||
                                           attrs[i].name == "xmlns") {
                                           continue;
                                       }

                                       (function (name) {
                                           changer = function(value) {
                                               shape.set(name, value);
                                           }
                                       })(attrs[i].name);

                                       info = Tratto.database.attribute(attrs[i].name);

                                       getter = Tratto.widgets.create("getter", {
                                           onChange: changer,
                                                                              name: info.name || attrs[i].name,
                                                                              desc: info.desc || attrs[i].name,
                                                                              type: info.type || ""
                                                                              });

                                       if (info.type == "color") {
                                           _a(colors_bar, getter.dom);
                                       } else {
                                           _a(bar, getter.dom);
                                       }

                                       getter.set(attrs[i].value);
                                   }
                               }

                           }, false);

        s.addEventListener("mouseover", function(event) {
                               var sel = Tratto.tools.selected;
                               if (sel && sel.onMouseOver) sel.onMouseOver(event);
                           }, false);
        s.addEventListener("mouseout", function(event) {
                               var sel = Tratto.tools.selected;
                               if (sel && sel.onMouseOut) sel.onMouseOut(event);
                           }, false);


        var tools = [ToolSelector, ToolRotator, ToolScale, ToolHorSkew, ToolVerSkew,
                     ToolRectangle, ToolSquare, ToolCircle,
                     ToolEllipse, ToolLine, ToolPolyline,
                     ToolPolygon ];

        for each (var tool in tools) this.add(tool);

    },
    add: function(tool) {
        if (tool.init) tool.init();
        this.tools[tool.name] = tool;
        this._add_to_bar(tool);
        this._add_to_popup(tool);
    },
    _add_to_bar: function(tool) {
        var button = cloneBlueprint(
            tool.type == "transform" ?
            "transform-tool-button" :
            "shape-tool-button");

        var set_value = function(value) {
            var attr = _g(button, value + "-attr");
            if (attr) _s(button, attr, tool[value]);
        };

        var values = [ "description", "icon" ];
        for each (var value in values) set_value(value);

        button.addEventListener('command', function(event) {
                                    Tratto.tools._toggle(tool);
                                }, false);

        _a(_(_g(button, "container")), button);

        tool.__button = button;
    },
    _add_to_popup: function(tool) {
        var menu_item = _cxul("menuitem", {
            label: tool.name,
                                      type: "checkbox"
                                      });
        menu_item.addEventListener('command', function(event) {
                                       Tratto.tools._toggle(tool);
                                   }, false);
        _a(this._popup, menu_item);

        tool.__menu_item = menu_item;
    },
    _toggle: function(tool) {
        Tratto.tools._removeBar();

        var sel = this.selected;

        this._putDown();

        if (sel != tool) {
            if (tool.onPickUp) tool.onPickUp();
            this.selected = tool;

	    if (sel) {sel.__button.style.MozOpacity = 1.0;}
            tool.__button.style.MozOpacity = 0.2;

            _s(tool.__button, 'selected', 'true');
            _s(tool.__menu_item, 'checked', 'true');
        }
    },
    _putDown: function() {  
        var sel = this.selected;

        if (! sel) return;

        if (sel.onPutDown) sel.onPutDown();

        this.selected = undefined;
        _s(sel.__button, 'selected', 'false');
        _s(sel.__menu_item, 'checked', 'false');
    }
};


Tratto.database = {
    _attrs_db: { },
    _shapes_db: { },
    init: function() {
        this.register_attribute({
            name: "stroke-width",
            desc: "Stroke width",
            type: "size",
            defval: "1px"
            });
        this.register_attribute({
            name: "stroke",
            desc: "Stroke color",
            type: "color",
            defval: "black"
            });
        this.register_attribute({
            name: "fill",
            desc: "Fill color",
            type: "color",
            defval: "white"
            });
        this.register_attribute({
            name: "fill-opacity",
            desc: "Fill opacity",
            type: "number",
            defval: "1.0"
            });
        this.register_attribute({
            name: "rx",
            desc: "X-axis radius",
            type: "size",
            defval: "0px"
            });
        this.register_attribute({
            name: "ry",
            desc: "Y-axis radius",
            type: "size",
            defval: "0px"
            });
        this.register_attribute({
            name: "string",
            desc: "Text string",
            type: "text",
            defval: "Modify me"
            });

        this.register_shape(ShapeRectangle);
        this.register_shape(ShapeEllipse);
        this.register_shape(ShapeCircle);
        this.register_shape(ShapeLine);
        this.register_shape(ShapePolyline);
        this.register_shape(ShapePolygon);
    },
    register_attribute: function(attr) {
        this._attrs_db[attr.name] = attr;
    },
    attribute: function(attr_name) {
        return this._attrs_db[attr_name] || {};
    },
    register_shape: function(shape) {
        this._shapes_db[shape.name] = shape;
    },
    shape: function(shape_name) {
        return this._shapes_db[shape_name] || {};
    }
};


Tratto.widgets = {
    init: function() {},
    create: function(widget, info) {
        switch (widget) {
            case "getter":
            return this.create_getter(info);
            break;
        }
        return undefined;
    },
    create_getter: function(info) {
        var grouper = _cxul("groupbox", {
                "grouper-attr-name": info.name,
                                    orient: "vertical"
                                    });
        _a(grouper, _cxul("caption", {
               label: info.desc || info.name,
                                  style: {
                              color: "navy"
                                      }
               }));

        if (info.type == "color") {
            var cpicker = _cxul("colorpicker", {
                type: "button"
                                        });
            var textbox = _cxul("textbox", {
                value: info.defval || ""
                                        });

            cpicker.addEventListener('change', function(event) {
                                         textbox.value = cpicker.color;
                                         if (info.onChange) info.onChange(cpicker.color);
                                     }, false);
            textbox.addEventListener('input', function(event) {
                                         cpicker.color = textbox.value;
                                         if (info.onChange) info.onChange(textbox.value);
                                     }, false);

            _a(grouper, cpicker);
            _a(grouper, textbox);

            // [[TO-DO]] If the colorpicker value is set before the widget
            // is appended it behaves strangely.

            // if (info.defval) cpicker.color = info.defval;

            return {
            dom: grouper,
                    get: function() {
                    return cpicker.color;
                },
                    set: function(value) {
                    cpicker.color = value;
                    textbox.value = value;
                }
            };
        } else { // so far we handle in a particular way only colors
            var textbox = _cxul("textbox", {
                value: info.defval || ""
                                        });
            textbox.addEventListener('input', function(event) {
                                         if (info.onChange) info.onChange(textbox.value);
                                     }, false);

            _a(grouper, textbox);

            return {
            dom: grouper,
                    get: function() {
                    return textbox.value;
                },
                    set: function(value) {
                    textbox.value = value;
                }
            };
        }
    }
};


Tratto.coords = {
    _x_snap: 8,
    _y_snap: 8,
    set snap_x(snap) {
        this._snap_x = snap;
        this._txt_x.value = snap;
    },
    set snap_y(snap) {
        this._snap_y = snap;
        this._txt_y.value = snap;
    },
    get snap_x() {
        return this._snap_x;
    },
    get snap_y() {
        return this._snap_y;
    },
    init: function() {
        this._bar = _("coords-bar");

        this._coords = _cxul("label", {
            value: "X:  Y:",
                                     popup: "grid-snaps",
                                     tooltiptext: "Click to change the grid snaps",
                                     style: "cursor: pointer"
                                     });
        _a(this._bar, this._coords);

        var popup = _cxul("popup", {
            id: "grid-snaps",
                                  ignorekeys: "true"
                                  });

        _a(popup, _cxul("label", {
               value: "X snap"
                                }));

        this._txt_x = _cxul("textbox", {
            value: this._x_snap
                                    });
        _a(popup, this._txt_x);

        _a(popup, _cxul("label", {
               value: "Y snap"
                                }));

        this._txt_y = _cxul("textbox", {
            value: this._y_snap
                                    });
        _a(popup, this._txt_y);

        Tratto.svgRoot.addEventListener('mousemove', function(event) {
                                            Tratto.coords.x = Tratto.coords._x_snap * Math.round(event.layerX / Tratto.coords._x_snap);
                                            Tratto.coords.y = Tratto.coords._y_snap * Math.round(event.layerY / Tratto.coords._y_snap);
                                        }, false);
        window.setInterval(function() {
                               if (Tratto.coords.x)
                                   Tratto.coords._coords.value = 'X: ' + Tratto.coords.x + '  Y: ' + Tratto.coords.y;
                           }, 150);

        this._txt_x.addEventListener('input', function(event) {
                                         Tratto.coords._x_snap = Number(event.target.value);
                                     }, false);

        this._txt_y.addEventListener('input', function(event) {
                                         Tratto.coords._y_snap = Number(event.target.value);
                                     }, false);

        _a(_("popups"), popup);
    }
};

Tratto.overlay = {
    highlight: function(shape) {
        var bbox = Tratto.shapes.getScreenBBox(shape);

        _a(Tratto.svgRoot, _csvg("rect", {
               x: bbox.x,
                                         y: bbox.y,
                                         height: bbox.height,
                                         width: bbox.width,
                                         fill: "none",
                                         stroke: "blue",
                                         overlay: "true"
                                         }));
    }
};


Tratto.palette = {
    _getters: {},
    init: function() {
        this._bar = _("values-bar");
        this._colors_bar = _('colors-bar');
    },
    show: function(name) {
        if (this._getters[name]) {
            this._getters[name].dom.style.display = "inherit";
        } else {
            var attr = Tratto.database.attribute(name);
            this._getters[name] = Tratto.widgets.create("getter", attr);

            if (attr.type == "color") {
                _a(this._colors_bar, this._getters[name].dom);
                // [[TO-DO]] We need this hack to make cpicker work correctly
                this._getters[name].set(attr.defval);
            } else {
                _a(this._bar, this._getters[name].dom);
            }
        }
    },
    hide: function(name) {
        this._getters[name].dom.style.display = "none";
    },
    valueOf: function(name) {
        return this._getters[name].get();
    },
    set: function(name, value) {
        this._getters[name].set(value);
    }
};

//---------------------------------------------------------
// Tools
var ToolRectangle = {
    name: "Rectangle",
    type: "shape",
    description: "Rectangle",
    icon: "images/rect.png",
    _drawing: false,
    onMouseDown: function(event) {
        this._drawing = true;
        this._cmd_group = Tratto.shapes.create_commands_group();
        this._start_x = Tratto.coords.x;
        this._start_y = Tratto.coords.y;
        this._shape = Tratto.shapes.create("rect", this._cmd_group);

        var shape = this._shape, group = this._cmd_group;

        shape.set("stroke-width", Tratto.palette.valueOf("stroke-width"), group);
        shape.set("stroke", Tratto.palette.valueOf("stroke"), group);
        shape.set("fill", Tratto.palette.valueOf("fill"), group);
        shape.set("fill-opacity", Tratto.palette.valueOf("fill-opacity"), group);
        shape.set("rx", Tratto.palette.valueOf("rx"), group);
        shape.set("ry", Tratto.palette.valueOf("ry"), group);
    },
    onMouseUp: function(event) {
        this._drawing = false;
        this._coords_to_translate();
    },
    onCancel: function() {
        if (!this._drawing) return;

        this._drawing = false;
        this._shape.remove();
    },
    onMouseMove: function(event) {
        if (!this._drawing) return;

        var x, y, group = this._cmd_group;
        x = Tratto.coords.x;
        y = Tratto.coords.y;

        if (x < this._start_x) {
            this._shape.set('x', x + 'px', group);
            this._shape.set('width', (this._start_x - x) + 'px', group);
        } else {
            this._shape.set('x', this._start_x + 'px', group);
            this._shape.set('width', (x - this._start_x) + 'px', group);
        }

        if (y < this._start_y) {
            this._shape.set('y', y + 'px', group);
            this._shape.set('height', (this._start_y - y) + 'px', group);
        } else {
            this._shape.set('y', this._start_y + 'px', group);
            this._shape.set('height', (y - this._start_y) + 'px', group);
        }
    },
    _coords_to_translate: function() {
        if (!this._shape) return;

        var shape = this._shape;
        var x = parseInt(shape.get("x"));
        var y = parseInt(shape.get("y"));

        shape.set("x", "0", this._cmd_group);
        shape.set("y", "0", this._cmd_group);

        var tlist = new TransformList(shape.get("transform"));
        tlist.translate(x, y);
        shape.set("transform", tlist.toString(), this._cmd_group);
    },
    onPickUp: function() {
        with (Tratto.palette) {
            show('fill');
            show('fill-opacity');
            show('stroke'); 
            show('stroke-width');
            show('rx'); 
            show('ry');
        };
    },
    onPutDown: function() {
        with (Tratto.palette) {
            hide('fill');
            hide('fill-opacity');
            hide('stroke');
            hide('stroke-width');
            hide('rx'); 
            hide('ry');
        };

        this._coords_to_translate();
    }
};
var ToolSquare = {
    name: "Square",
    type: "shape",
    description: "Square",
    icon: "images/square.png",
    _drawing: false,
    onMouseDown: function(event) {
        this._drawing = true;
        this._cmd_group = Tratto.shapes.create_commands_group();
        this._start_x = Tratto.coords.x;
        this._start_y = Tratto.coords.y;
        this._shape = Tratto.shapes.create("rect", this._cmd_group);

        var shape = this._shape, group = this._cmd_group;

        shape.set("stroke-width", Tratto.palette.valueOf("stroke-width"), group);
        shape.set("stroke", Tratto.palette.valueOf("stroke"), group);
        shape.set("fill", Tratto.palette.valueOf("fill"), group);
        shape.set("fill-opacity", Tratto.palette.valueOf("fill-opacity"), group);
        shape.set("rx", Tratto.palette.valueOf("rx"), group);
        shape.set("ry", Tratto.palette.valueOf("ry"), group);
    },
    onMouseUp: function(event) {
        this._drawing = false;
        this._coords_to_translate();
    },
    onCancel: function() {
        if (!this._drawing) return;

        this._drawing = false;
        this._shape.remove();
    },
    onMouseMove: function(event) {
        if (!this._drawing) return;

        var x1, y1, x2, y2;
        var group = this._cmd_group;

        x1 = this._start_x;
        y1 = this._start_y;
        x2 = Tratto.coords.x;
        y2 = Tratto.coords.y;

        var sidex, sidey, side;
        sidex = Math.abs(x1 - x2);
        sidey = Math.abs(y1 - y2);
        if (sidex > sidey) {
            side = sidex;
        } else {
            side = sidey;
        }

        if (x1 < x2) {
            this._shape.set('x', x1 + "px", group);
        } else {
            this._shape.set('x', (x1-side) + "px", group);
        }

        if (y1 < y2) {
            this._shape.set('y', y1 + "px", group);
        } else {
            this._shape.set('y', (y1-side) + "px", group);
        }

        this._shape.set('width', side + "px", group);
        this._shape.set('height', side + "px", group);
    },
    _coords_to_translate: function() {
        if (!this._shape) return;

        var shape = this._shape;
        var x = parseInt(shape.get("x"));
        var y = parseInt(shape.get("y"));

        shape.set("x", "0", this._cmd_group);
        shape.set("y", "0", this._cmd_group);

        var tlist = new TransformList(shape.get("transform"));
        tlist.translate(x, y);
        shape.set("transform", tlist.toString(), this._cmd_group);
    },
    onPickUp: function() {
        with (Tratto.palette) {
            show('fill');
            show('fill-opacity');
            show('stroke'); 
            show('stroke-width');
            show('rx'); 
            show('ry');
        };
    },
    onPutDown: function() {
        with (Tratto.palette) {
            hide('fill');
            hide('fill-opacity');
            hide('stroke');
            hide('stroke-width');
            hide('rx'); 
            hide('ry');
        };

        this._coords_to_translate();
    }
};
var ToolEllipse = {
    name: "Ellipse",
    type: "shape",
    description: "Ellipse",
    icon: "images/ellipse.png",
    _drawing: false,
    onMouseDown: function(event) {
        this._drawing = true;
        this._cmd_group = Tratto.shapes.create_commands_group();
        this._start_x = Tratto.coords.x;
        this._start_y = Tratto.coords.y;
        this._shape = Tratto.shapes.create("ellipse", this._cmd_group);

        var shape = this._shape, group = this._cmd_group;

        shape.set("stroke-width", Tratto.palette.valueOf("stroke-width"), group);
        shape.set("stroke", Tratto.palette.valueOf("stroke"), group);
        shape.set("fill", Tratto.palette.valueOf("fill"), group);
        shape.set("fill-opacity", Tratto.palette.valueOf("fill-opacity"), group);

        var tlist = new TransformList(shape.get("transform"));
        tlist.translate(this._start_x, this._start_y);
        shape.set("transform", tlist.toString(), group);
    },
    onMouseUp: function(event) {
        this._drawing = false;
    },
    onCancel: function() {
        if (!this._drawing) return;

        this._drawing = false;
        this._shape.remove();
    },
    onMouseMove: function(event) {
        if (!this._drawing) return;
        var x, y;
        x = Tratto.coords.x;
        y = Tratto.coords.y;
        this._shape.set('rx', Math.abs(this._start_x - x) + 'px', this._cmd_group);
        this._shape.set('ry', Math.abs(this._start_y - y) + 'px', this._cmd_group);
    },
    onPickUp: function() {
        with (Tratto.palette) {
            show('fill');
            show('fill-opacity');
            show('stroke'); 
            show('stroke-width');
        };
    },
    onPutDown: function() {
        with (Tratto.palette) {
            hide('fill');
            hide('fill-opacity');
            hide('stroke');
            hide('stroke-width');
        };
    }
};
var ToolCircle = {
    name: "Circle",
    type: "shape",
    description: "Circle",
    icon: "images/circle.png",
    _drawing: false,
    onMouseDown: function(event) {
        this._drawing = true;
        this._cmd_group = Tratto.shapes.create_commands_group();
        this._start_x = Tratto.coords.x;
        this._start_y = Tratto.coords.y;
        this._shape = Tratto.shapes.create("circle");

        var shape = this._shape, group = this._cmd_group;

        shape.set("stroke-width", Tratto.palette.valueOf("stroke-width"), group);
        shape.set("stroke", Tratto.palette.valueOf("stroke"), group);
        shape.set("fill", Tratto.palette.valueOf("fill"), group);
        shape.set("fill-opacity", Tratto.palette.valueOf("fill-opacity"), group);

        var tlist = new TransformList(shape.get("transform"));
        tlist.translate(this._start_x, this._start_y);
        shape.set("transform", tlist.toString(), group);
    },
    onMouseUp: function(event) {
        this._drawing = false;
    },
    onCancel: function() {
        if (!this._drawing) return;

        this._drawing = false;
        this._shape.remove();
    },
    onMouseMove: function(event) {
        if (!this._drawing) return;

        var x, y, rx, ry, r;
        x = Tratto.coords.x;
        y = Tratto.coords.y;
        rx = Math.abs(this._start_x - x);
        ry = Math.abs(this._start_y - y);

        if (rx > ry) {
            r = rx;
        } else {
            r = ry;
        }

        this._shape.set('r', r + 'px', this._cmd_group);
    },
    onPickUp: function() {
        with (Tratto.palette) {
            show('fill');
            show('fill-opacity');
            show('stroke'); 
            show('stroke-width');
        };
    },
    onPutDown: function() {
        with (Tratto.palette) {
            hide('fill');
            hide('fill-opacity');
            hide('stroke');
            hide('stroke-width');
        };
    }
};
var ToolLine = {
    name: "Line",
    type: "shape",
    description: "Draws lines",
    icon: "images/line.png",
    _drawing: false,
    onMouseDown: function(event) {
        this._drawing = true;
        this._cmd_group = Tratto.shapes.create_commands_group();
        this._shape = Tratto.shapes.create("line", this._cmd_group);

        var shape = this._shape, group = this._cmd_group;

        shape.set("stroke-width", Tratto.palette.valueOf("stroke-width"), group);
        shape.set("stroke", Tratto.palette.valueOf("stroke"), group);
        shape.set("x1", Tratto.coords.x, group);
        shape.set("y1", Tratto.coords.y, group);
        shape.set("x2", Tratto.coords.x, group);
        shape.set("y2", Tratto.coords.y, group);
    },
    onMouseUp: function(event) {
        this._drawing = false;
    },
    onCancel: function() {
        if (!this._drawing) return;

        this._drawing = false;
        this._shape.remove();
    },
    onMouseMove: function(event) {
        if (!this._drawing) return;
        this._shape.set("x2", Tratto.coords.x, this._cmd_group);
        this._shape.set("y2", Tratto.coords.y, this._cmd_group);
    },
    onPickUp: function() {
        with (Tratto.palette) {
            show('stroke'); 
            show('stroke-width');
        };
    },
    onPutDown: function() {
        with (Tratto.palette) {
            hide('stroke');
            hide('stroke-width');
        };
    }
};
var ToolPolyline = {
    name: "Polyline",
    type: "shape",
    description: "Draws polylines",
    icon: "images/polyline.png",
    _drawing: false,
    onClick: function(event) {
        if (this._drawing == false) {
            this._drawing = true;

            var group = Tratto.shapes.create_commands_group();
            this._shape = Tratto.shapes.create("polyline", group);

            var shape = this._shape;
            shape.set("stroke-width", Tratto.palette.valueOf("stroke-width"), group);
            shape.set("fill", Tratto.palette.valueOf("fill"), group);
            shape.set("fill-opacity", Tratto.palette.valueOf("fill-opacity"), group);
            shape.set("stroke", Tratto.palette.valueOf("stroke"), group);

            this._last_x = Tratto.coords.x;
            this._last_y = Tratto.coords.y;

            var points = new Points();
            points.push({
                x: Tratto.coords.x,
                y: Tratto.coords.y
                });

            shape.set("points", points.toString(), group);
        } else {
            if (this._last_x == Tratto.coords.x &&
                this._last_y == Tratto.coords.y) {
                this._drawing = false;
                return;
            }

            this._last_x = Tratto.coords.x;
            this._last_y = Tratto.coords.y;

            var points = new Points(this._shape.get("points"));

            points.push({
                x: Tratto.coords.x,
                y: Tratto.coords.y
                });

            this._shape.set("points", points.toString());
        }
    },
    onCancel: function() {
        if (!this._drawing) return;

        this._drawing = false;
        this._shape.remove();
    },
    onPickUp: function() {
        with (Tratto.palette) {
            show('fill');
            show('fill-opacity');
            show('stroke'); 
            show('stroke-width');
        };
    },
    onPutDown: function() {
        with (Tratto.palette) {
            hide('fill');
            hide('fill-opacity');
            hide('stroke');
            hide('stroke-width');
        };

        this._drawing = false;
    }
};
var ToolPolygon = {
    name: "Polygon",
    type: "shape",
    description: "Polygon",
    icon: "images/polygon.png",
    _drawing: false,
    onClick: function(event) {
        if (this._drawing == false) {
            this._drawing = true;

            var group = Tratto.shapes.create_commands_group();
            this._shape = Tratto.shapes.create("polygon", group);

            var shape = this._shape;
            shape.set("stroke-width", Tratto.palette.valueOf("stroke-width"), group);
            shape.set("fill", Tratto.palette.valueOf("fill"), group);
            shape.set("fill-opacity", Tratto.palette.valueOf("fill-opacity"), group);
            shape.set("stroke", Tratto.palette.valueOf("stroke"), group);

            this._last_x = Tratto.coords.x;
            this._last_y = Tratto.coords.y;

            var points = new Points();
            points.push({
                x: Tratto.coords.x,
                y: Tratto.coords.y
                });

            shape.set("points", points.toString(), group);
        } else {
            if (this._last_x == Tratto.coords.x &&
                this._last_y == Tratto.coords.y) {
                this._drawing = false;
                return;
            }

            this._last_x = Tratto.coords.x;
            this._last_y = Tratto.coords.y;

            var points = new Points(this._shape.get("points"));

            points.push({
                x: Tratto.coords.x,
                y: Tratto.coords.y
                });

            this._shape.set("points", points.toString());
        }
    },
    onCancel: function() {
        if (!this._drawing) return;

        this._drawing = false;
        this._shape.remove();
    },
    onPickUp: function() {
        with (Tratto.palette) {
            show('fill');
            show('fill-opacity');
            show('stroke');
            show('stroke-width');
        };
    },
    onPutDown: function() {
        with (Tratto.palette) {
            hide('fill');
            hide('fill-opacity');
            hide('stroke');
            hide('stroke-width');
        };

        this._drawing = false;
    }
};
var ToolSelect = {
    name: "Select",
    type: "transform",
    description: "Select",
    icon: "images/",
    onClick: function(event) {
        if (event.target != Tratto.svgRoot) Tratto.overlay.highlight(event.target);
    },
    onPickUp: function() {
    },
    onPutDown: function() {
    }
};
var ToolSelector = {
    name: "Edit/move",
    type: "transform",
    description: "Move",
    icon: "images/move.png",
    onClick: function(event) {
        if (this._moved) return;

        if (event.target == Tratto.svgRoot) {
            if (this._bar) {
                _r(this._bar);
                this._bar = undefined;
                this._selected_shape = undefined;
            }
            return;
        }

        if ((!this._selected_shape) || (this._selected_shape.dom != event.target)) {
            if (this._bar) {
                _r(this._bar);
                this._bar = undefined;
                this._selected_shape = undefined;
            }

            this._selected_shape = Tratto.shapes.wrap(event.target);
            this._bar = _cxul('vbox', {
                    "class": "pincopal"
                                      });

            _a(_("values-bar"), this._bar);

            var shape = this._selected_shape;

            var del_button = _cxul("button", {
                label: "Delete"
                                           });
            del_button.addEventListener('command', function(event) {
                                            shape.remove();
                                            _r(ToolSelector._bar);
                                            ToolSelector._bar = undefined;
                                            ToolSelector._selected_shape = undefined;
                                        }, false);

            _a(ToolSelector._bar, del_button);

            this._colors_bar = _cxul('vbox');
            _a(this._bar, this._colors_bar);

            if (shape.dom.hasAttributes()) {
                var attrs = shape.dom.attributes;
                var info, getter, changer;

                for (var i = attrs.length-1; i>=0; i--) {
                    if (attrs[i].name == "id" ||
                        attrs[i].name == "xmlns") {
                        continue;
                    }

                    (function (name) {
                        changer = function(value) {
                            shape.set(name, value);
                        }
                    })(attrs[i].name);

                    info = Tratto.database.attribute(attrs[i].name);

                    getter = Tratto.widgets.create("getter", {
                        onChange: changer,
                                                           name: info.name || attrs[i].name,
                                                           desc: info.desc || attrs[i].name,
                                                           type: info.type || ""
                                                           });

                    if (info.type == "color") {
                        _a(this._colors_bar, getter.dom);
                    } else {
                        _a(this._bar, getter.dom);
                    }

                    getter.set(attrs[i].value);
                }
            }
        }
    },
    onMouseDown: function(event) {
        this._moved = false;

        if (event.target == Tratto.svgRoot) return;

        this._moving = true;
        Tratto.svgRoot.style.cursor = "move";
        this._last_drag_x = Tratto.coords.x;
        this._last_drag_y = Tratto.coords.y;
        this._dragged_shape = Tratto.shapes.wrap(event.target);
        this._group = Tratto.shapes.create_commands_group();
    },
    onMouseUp: function(event) {
        this._moving = false;

        Tratto.svgRoot.style.cursor = "";
    },
    onMouseMove: function(event) {
        if (this._moving == true) {
            Tratto.svgRoot.style.cursor = "move";

            this._moved = true;

            var offset_x = Tratto.coords.x - this._last_drag_x;
            var offset_y = Tratto.coords.y - this._last_drag_y;
            this._last_drag_x = Tratto.coords.x;
            this._last_drag_y = Tratto.coords.y;

            if (offset_x == 0 && offset_y == 0) {
                return;
            }

            var tlist = new TransformList(this._dragged_shape.get("transform"));

            tlist.pre_translate(offset_x, offset_y);

            this._dragged_shape.set("transform", tlist.toString(), this._group);
        } else {
            if (event.target != Tratto.svgRoot &&
                _g(event.target, "overlay") != "true") {
                Tratto.svgRoot.style.cursor = "pointer";
            } else {
                Tratto.svgRoot.style.cursor = "";
            }
        }
    },
    onCancel: function() {
        if (this._bar) _r(this._bar);
        this._bar = undefined;
        this._selected_shape = undefined;
        this._moving = false;
    },
    onPickUp: function() {
    },
    onPutDown: function() {
        if (this._bar) _r(this._bar);
        this._bar = undefined;
        this._selected_shape = undefined;
        this._moving = false;
        Tratto.svgRoot.style.cursor = "";
    }
};
var ToolRotator = {
    name: "Rotate",
    type: "transform",
    description: "Rotate",
    icon: "images/rotate.png",
    onMouseDown: function(event) {
        if (event.target == Tratto.svgRoot) return;

        this._rotating = true;
        this._shape = Tratto.shapes.wrap(event.target);
        this._group = Tratto.shapes.create_commands_group();
        this._last_x = Tratto.coords.x;
    },
    onMouseUp: function(event) {
        this._rotating = false;
    },
    onMouseMove: function(event) {
        if (this._rotating == true) {
            if (Tratto.coords.x == this._last_x) {
                return;
            }

            var tlist = new TransformList(this._shape.get("transform"));

            var diff_x = Tratto.coords.x - this._last_x;
            this._last_x = Tratto.coords.x;

            var bbox = this._shape.dom.getBBox();
            var px = bbox.x + (bbox.width / 2);
            var py = bbox.y + (bbox.height / 2);

            tlist.rotate(diff_x, px, py);

            this._shape.set("transform", tlist.toString(), this._group);
        }
    },
    onCancel: function() {
        this._rotating = false;
    },
    onPickUp: function() {
    },
    onPutDown: function() {
        this._rotating = false;
    }
};
var ToolScale = {
    name: "Scale",
    type: "transform",
    description: "Scale",
    icon: "images/scale.png",
    onMouseDown: function(event) {
        if (event.target == Tratto.svgRoot) return;

        this._scaling = true;
        this._shape = Tratto.shapes.wrap(event.target);
        this._group = Tratto.shapes.create_commands_group();
        this._last_x = Tratto.coords.x;
        this._last_y = Tratto.coords.y;
    },
    onMouseUp: function(event) {
        this._scaling = false;
    },
    onMouseMove: function(event) {
        if (this._scaling == true) {
            var diff_x = Tratto.coords.x - this._last_x;
            var diff_y = Tratto.coords.y - this._last_y;

            this._last_x = Tratto.coords.x;
            this._last_y = Tratto.coords.y;

            if (diff_x == 0 && diff_y == 0) {
                return;
            }

            var tlist = new TransformList(this._shape.get("transform"));

            var shape = this._shape.dom;
            var bbox = shape.getBBox();

            var sx = 1 + diff_x / bbox.width;
            var sy = 1 + diff_y / bbox.height;

            tlist.scale(sx, sy);

            this._shape.set("transform", tlist.toString(), this._group);
        }
    },
    onCancel: function() {
        this._scaling = false;
    },
    onPickUp: function() {
    },
    onPutDown: function() {
        this._scaling = false;
    }
};
var ToolHorSkew = {
    name: "Hor. Skew",
    type: "transform",
    description: "Horizontal skew",
    icon: "images/skewx.png",
    onMouseDown: function(event) {
        if (event.target == Tratto.svgRoot) return;

        this._skewing = true;
        this._shape = Tratto.shapes.wrap(event.target);
        this._group = Tratto.shapes.create_commands_group();
        this._last_x = Tratto.coords.x;
    },
    onMouseUp: function(event) {
        this._skewing = false;
    },
    onMouseMove: function(event) {
        if (this._skewing == true) {
            if (Tratto.coords.x == this._last_x) {
                return;
            }

            var tlist = new TransformList(this._shape.get("transform"));

            var diff_x = Tratto.coords.x - this._last_x;
            this._last_x = Tratto.coords.x;

            tlist.skewX(diff_x);

            this._shape.set("transform", tlist.toString(), this._group);
        }
    },
    onCancel: function() {
        this._skewing = false;
    },
    onPickUp: function() {
    },
    onPutDown: function() {
        this._skewing = false;
    }
};
var ToolVerSkew = {
    name: "Ver. Skew",
    type: "transform",
    description: "Vertical skew",
    icon: "images/skewy.png",
    onMouseDown: function(event) {
        if (event.target == Tratto.svgRoot) return;

        this._skewing = true;
        this._shape = Tratto.shapes.wrap(event.target);
        this._group = Tratto.shapes.create_commands_group();
        this._last_y = Tratto.coords.y;
    },
    onMouseUp: function(event) {
        this._skewing = false;
    },
    onMouseMove: function(event) {
        if (this._skewing == true) {
            if (Tratto.coords.y == this._last_y) {
                return;
            }

            var tlist = new TransformList(this._shape.get("transform"));

            var diff_y = Tratto.coords.y - this._last_y;
            this._last_y = Tratto.coords.y;

            tlist.skewY(diff_y);

            this._shape.set("transform", tlist.toString(), this._group);
        }
    },
    onCancel: function() {
        this._skewing = false;
    },
    onPickUp: function() {
    },
    onPutDown: function() {
        this._skewing = false;
    }
};

//---------------------------------------------------------
// Shapes
var ShapeRectangle = {
    name: "rect",
    move: function(shape, x_offset, y_offset, group) {
        var x = parseInt(shape.get('x'));
        var y = parseInt(shape.get('y'));
        shape.set('x', (x + x_offset) + "px", group);
        shape.set('y', (y + y_offset) + "px", group);
    }
};
var ShapeEllipse = {
    name: "ellipse",
    move: function(shape, x_offset, y_offset, group) {
        var x = parseInt(shape.get('cx'));
        var y = parseInt(shape.get('cy'));
        shape.set('cx', (x + x_offset) + "px", group);
        shape.set('cy', (y + y_offset) + "px", group);
    }
};
var ShapeCircle = {
    name: "circle",
    move: function(shape, x_offset, y_offset, group) {
        var x = parseInt(shape.get('cx'));
        var y = parseInt(shape.get('cy'));
        shape.set('cx', (x + x_offset) + "px", group);
        shape.set('cy', (y + y_offset) + "px", group);
    }
};
var ShapeLine = {
    name: "line",
    move: function(shape, x_offset, y_offset, group) {
        var x1 = parseInt(shape.get('x1'));
        var y1 = parseInt(shape.get('y1'));
        var x2 = parseInt(shape.get('x2'));
        var y2 = parseInt(shape.get('y2'));
        shape.set('x1', (x1 + x_offset) + "px", group);
        shape.set('y1', (y1 + y_offset) + "px", group);
        shape.set('x2', (x2 + x_offset) + "px", group);
        shape.set('y2', (y2 + y_offset) + "px", group);
    }
};
var ShapePolyline = {
    name: "polyline",
    move: function(shape, x_offset, y_offset, group) {
        var points = new Points(shape.get('points'));
        points.move(x_offset, y_offset);
        shape.set('points', points.toString(), group);
    }
};
var ShapePolygon = {
    name: "polygon",
    move: function(shape, x_offset, y_offset, group) {
        var points = new Points(shape.get('points'));
        points.move(x_offset, y_offset);
        shape.set('points', points.toString(), group);
    }
};


//---------------------------------------------------------
// HELPERS

function Points(points, allow_duplicates) {
    this._points = [];

    if (!points) return;

    var coords = points.split(" ");

    for each (var coord in coords) {
        var comma = coord.indexOf(",");
        this.push({
            x: coord.substring(0, comma),
            y: coord.substring(comma+1)
            }, allow_duplicates);
    }
}

Points.prototype = {
    push: function(point, force) {
        point.x = Number(point.x);
        point.y = Number(point.y);

        var last = this._points.length - 1;
        if (force != true &&
            last != -1 &&
            this._points[last].x == point.x &&
            this._points[last].y == point.y ) {
            return;
        }

        this._points.push(point);
    },
    pop: function() {
        return this._points.pop();
    },
    move: function(x_offset, y_offset) {
        var point;
        for each (point in this._points) {
            point.x += x_offset;
            point.y += y_offset;
        }
    },
    toString: function() {
        var point;
        var string = "";
        var first = true;
        for each (point in this._points) {
            if (first != true) {
                string += " ";
            } else {
                first = false;
            }

            string += point.x + "," + point.y;
        }
        return string;
    }
};


function TransformList(str_tlist)  {
    this._matrix = undefined;

    if (!str_tlist) return;

    eval(str_tlist.replace(")", ");"), this);
};

TransformList.prototype = {
    _mul_matrix: function(a, b) {
        if (a == undefined) {
            return b;
        }
        if (b == undefined) {
            return a;
        }

        var res = [];
        var i, j, r;

        var index_of = function(row, col) {
            return 3 * row + col;
        }

        for (i = 0; i < 3; i++) {
            for (j = 0; j < 3; j++) {
                res[index_of(i, j)] = 
                (a[index_of(i, 0)] * b[index_of(0, j)]) +
                (a[index_of(i, 1)] * b[index_of(1, j)]) +
                (a[index_of(i, 2)] * b[index_of(2, j)]);
            }
        }

        return res;
    },
    _vec2mat: function(vec) {
        return [vec[0], vec[2], vec[4],
                vec[1], vec[3], vec[5],
                0.0,  0.0,  1.0];
    },
    _mat2vec: function(mat) {
        return [mat[0], mat[3], mat[1],
                mat[4], mat[2], mat[5]];
    },
    matrix: function() {
        this._matrix = this._mul_matrix(this._matrix, this._vec2mat(arguments));
    },
    pre_matrix: function() {
        this._matrix = this._mul_matrix(this._vec2mat(arguments), this._matrix);
    },
    translate: function() {
        var a = arguments;
        var tx = a[0];
        var ty = a[1] || 0;

        this.matrix(1.0, 0.0, 0.0,
                    1.0,  tx,  ty);
//  this._translate.x += tx;
//  this._translate.y += ty;
    },
    pre_translate: function() {
        var a = arguments;
        var tx = a[0];
        var ty = a[1] || 0;

        this.pre_matrix(1.0, 0.0, 0.0,
                        1.0,  tx,  ty);
    },
    _apply_translate: function() {
        this.pre_matrix(1.0, 0.0, 0.0,
                        1.0,  this._translate.x, this._translate.y);
    },
    rotate: function() {
        var a = arguments;
        var angle = a[0];

        if (a.length == 1) {
            angle = angle * Math.PI / 180.0;
            var sin = Math.sin(angle);
            var cos = Math.cos(angle);
            this.matrix(cos, sin, -sin,
                        cos,   0,    0);
        } else {
            var cx = a[1];
            var cy = a[2];
            this.translate(cx, cy);
            this.rotate(angle);
            this.translate(-cx, -cy);
        }
    },
    pre_rotate: function() {
        var a = arguments;
        var angle = a[0];

        if (a.length == 1) {
            angle = angle * Math.PI / 180.0;
            var sin = Math.sin(angle);
            var cos = Math.cos(angle);
            this.pre_matrix(cos, sin, -sin,
                            cos,   0,    0);
        } else {
            var cx = a[1];
            var cy = a[2];
            this.translate(cx, cy);
            this.pre_rotate(angle);
            this.translate(-cx, -cy);
        }
    },
    scale: function() {
        var a = arguments;
        var sx = a[0];
        var sy = a[1] || sx;

        this.matrix(sx, 0.0, 0.0,
                    sy, 0.0, 0.0);
    },
    pre_scale: function() {
        var a = arguments;
        var sx = a[0];
        var sy = a[1] || sx;

        this.pre_matrix(sx, 0.0, 0.0,
                        sy, 0.0, 0.0);
    },
    skewX: function() {
        var a = arguments;
        var angle = a[0] * Math.PI / 180.0;

        this.matrix(1.0, 0.0, Math.tan(angle),
                    1.0, 0.0, 0.0);
    },
    pre_skewX: function() {
        var a = arguments;
        var angle = a[0] * Math.PI / 180.0;

        this.pre_matrix(1.0, 0.0, Math.tan(angle),
                        1.0, 0.0, 0.0);
    },
    skewY: function() {
        var a = arguments;
        var angle = a[0] * Math.PI / 180.0;

        this.matrix(1.0, Math.tan(angle), 0.0,
                    1.0, 0.0, 0.0);
    },
    pre_skewY: function() {
        var a = arguments;
        var angle = a[0] * Math.PI / 180.0;

        this.pre_matrix(1.0, Math.tan(angle), 0.0,
                        1.0, 0.0, 0.0);
    },
    toString: function() {
        // this._apply_translate();

        var m = this._matrix;
        var res;

        if (m) {
            res = "matrix(" + m[0] + ", " + m[3] + ", " + m[1] +
                ", " + m[4] + ", " + m[2] + ", " + m[5] + ")";
        } else {
            res = "";
        }

        return res;
    }
};
