/*****************************************************************************
 *
 * $Id: ConfigMgr.js,v 1.57 2007/06/29 20:00:21 pspencer Exp $
 *
 * Purpose: Fusion.Lib.ConfigMgr handles communication with the server
 *          during initialization, and triggers loading of the WebLayout
 *
 * Project: Fusion
 *
 * Author: DM Solutions Group Inc 
 *
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 *****************************************************************************/
/**
 * Fusion.Lib.ConfigMgr
 *
 * Configuration class for fusion using a web layout.
 */


Fusion.Lib.ConfigMgr = Class.create();
Object.extend(Fusion.Lib.ConfigMgr.prototype, {
    oApp: null,
    oBroker: null,
    //array of map widgets. For now only the map from the weblayout is used
    //TODO : use the mapconfig to pass other maps.
    aoMapWidget : null,

    oWebLayout: null,
    
    oWebLayout : null,

    aWidgetNames : [],
    
    aWidgets: null,

    /**
     * construct a new configuration manager. 
     *
     * @param app {Object} the application object
     */
    initialize : function(app, sessionid) {
        // console.log('configuration manager initializing');
        this.oApp = app;
        this.aoMapWidget = [];
        this.aWidgets = [];
        
        this.scriptLang = app.getScriptLanguage();
        this.redirectScript = app.getRedirectScript();
        
		this.sessionID = sessionid || null;
        this.createWebLayout();
    },

    /**
     * create an object to manage the web layout.  When it has
     * finished loading and parsing the web layout, it will
     * emit an event.  We load the map definitions when this
     * event is triggered.
     */
    createWebLayout : function()
    {
        // console.log('ConfigMgr::parseWebLayout');
        this.oWebLayout = new Fusion.Lib.WebLayout(this.oApp);
        this.oWebLayout.registerForEvent(Fusion.Event.WEBLAYOUT_PARSED, this.loadWidgets.bind(this));
        this.oWebLayout.parse();
    },

    getListofWidgets : function() {
        var oCommand = null;
        var sTmp;
        var aCommands = [];
        for (var i=0; i<this.oWebLayout.commandObj.length; i++) {
            oCommand = this.oWebLayout.commandObj[i];
            
            if ($(oCommand.getName())) {
                aCommands.push(oCommand);
            }
        }
        return aCommands;
    },
    
    getListofToolbarWidgets: function() {
        var aCommands = [];
        var toolbars = this.oWebLayout.aToolbars;
        for (var i=0; i<toolbars.length; i++) {
            /* test to see if the container for the toolbar is in the page */
            if($(toolbars[i].container)) {
                for (var j=0; j<toolbars[i].buttons.length; j++) {
                    var button = toolbars[i].buttons[j];
                    if (button.func == 'Command') {
                        var oCommand = this.oWebLayout.getCommandByName(toolbars[i].buttons[j].obj.name);
                        if (oCommand) {
                            aCommands.push(oCommand);
                        }
                    } else if (button.func == 'Flyout') {
                        aCommands = aCommands.concat(this.getListofMenuWidgets(button.obj.subItems));
                    }
                }
            }
        }

        return aCommands;
    },
    
    getListofContextMenuWidgets: function() {
        var aCommands = [];
        var menus = this.oWebLayout.aMenus;
        for (var i=0; i<menus.length; i++) {
            /* test to see if the container for the toolbar is in the page */
            if(menus[i].mapName == '' || this.getMapByName(menus[i].mapName)) {
                aCommands = aCommands.concat(this.getListofMenuWidgets(menus[i].items));
            }
        }
        return aCommands;
    },
    
    getListofMenuWidgets: function(menuItems) {
        var aCommands = [];
        for (var i=0; i<menuItems.length; i++) {
            var item = menuItems[i];
            switch (item.func) {
                case 'Separator':
                    break;
                case 'Command':
                    var oCommand = this.oWebLayout.getCommandByName(item.obj.name);
                    if (oCommand) {
                        aCommands.push(oCommand);
                    }
                    break;
                case 'Flyout':
                    aCommands = aCommands.concat(this.getListofMenuWidgets(item.obj.subItems));
                    break;
                default:
                
            }
        }
        return aCommands;
    },


    loadWidgets : function() {
        var aCommands = this.getListofWidgets();
        aCommands = aCommands.concat(this.getListofToolbarWidgets());
        aCommands = aCommands.concat(this.getListofContextMenuWidgets());
        
        for (var i=0; i<aCommands.length; i++) {
			var c = aCommands[i];
			var js = c.sLocation + '/' + c.getAction() + '.js';
            Fusion.require(js);
        }
        
        this.oApp.setLoadState(this.oApp.LOAD_WIDGETS);
    },

    createWidgets :  function() {
        this.createMapWidget();
        this.createAllWidgets();
        this.createToolbars();
        this.createContextMenus();        
    },

    /**convension for the widgets :
     * basic command widgets will be names [Action] with file name being [Action].js
     *  -ex ZoomIn = Zoom()
     *  basic command widgets will be located under base_url/widgets/
     */
    createMapWidget : function() {
        var aCommands = this.oWebLayout.getCommandByType('MapCommandType');
        if (aCommands.length > 0)
        {
            var oCommand = null;
            for (var i=0; i<aCommands.length; i++)
            {
                oCommand = aCommands[i];
                var oElement =  $(oCommand.getName());
                if (oElement != null)
                {
                    var widget;
                    var sID = this.sessionID ? ',"'+this.sessionID+'"': '';
                    var sTmp = 'widget = new Fusion.Widget.' + oCommand.getAction() + '(oCommand'+sID+')';
                    eval(sTmp);
                    if ($(oCommand.getName())) {
                        $(oCommand.getName()).widget = widget;
                    }
                    this.aoMapWidget.push(widget);
                }
            }
        }
    },
    
    getMapInfo: function(resourceId) {
        for (var i=0; i<this.oWebLayout.aMaps.length; i++) {
            if (this.oWebLayout.aMaps[i].resourceId == resourceId) {
                return this.oWebLayout.aMaps[i];
            }
        }
        return null;
    },

    getMapByName : function(sName)
    {
        var nMaps = this.aoMapWidget.length;
        var oMap = null;
        for (var i=0; i<nMaps; i++)
        {
            if (this.aoMapWidget[i].getMapName() == sName){
                oMap = this.aoMapWidget[i];
                break;
            }
        }
        return oMap;
    },

    /**
      get the map object using the html element id used by the map
    */
    getMapById : function(sId)
    {
        var nMaps = this.aoMapWidget.length;
        var oMap;
        for (var i=0; i<nMaps; i++)
        {
            oMap = this.aoMapWidget[i];
            if (oMap.getDomId() == sId)
            {
                return oMap;
            }
        }
        return null;
    },

    getMapByIndice : function(nIndice)
    {
        if (nIndice < this.aoMapWidget.length)
        {
            var oMap = this.aoMapWidget[nIndice];
            return oMap;
        }

        return null;
    },

  
    createAllWidgets : function()
    {
        var aCommands = this.getListofWidgets();
        for (var i=0; i<aCommands.length; i++) {
            var oCommand = aCommands[i];
            if (oCommand.getAction() != '' && 
                oCommand.getAction() != 'MGMap' && /* TODO Remove the need to test these here */
                oCommand.getAction() != 'MSMap') {
                var widget;
                var sTmp = 'widget = new Fusion.Widget.' + oCommand.getAction() + '(oCommand)';
                eval(sTmp);
                this.aWidgets.push(widget);
                if ($(oCommand.getName())) {
                    $(oCommand.getName()).widget = widget;
                }
            }
        }

    },
    
    createToolbars: function() {
        //console.log('searching for toolbars to create');
        var dummyAction = new Jx.Action(null);
        var toolbars = this.oWebLayout.aToolbars;
        if (toolbars.length > 0) {
            for (var i=0; i<toolbars.length; i++) {
                var toolbarObj = $(toolbars[i].container);
                if (toolbarObj) {
                    //console.log('creating toolbar');
                    var toolbar = toolbars[i];
                    var tb = new Jx.Toolbar(toolbarObj);
                    tb.domObj.id = toolbar.name;
                    for (var j=0; j<toolbar.buttons.length; j++) {
                        var id = 'toolbar'+i+'command'+j;
                        var button = toolbar.buttons[j];
                        var tbItem = null;
                        switch (button.func) {
                            case 'Separator':
                                tb.add(new Jx.ToolbarSeparator());
                                break;
                            case 'Command':
                                var oCommand = this.oWebLayout.getCommandByName(button.obj.name);
                                
                                tbItem = new Jx.ToolbarItem();
                                tbItem.domObj.id = id;
                                tb.add(tbItem);

                                var oldName = oCommand.getName();
                                oCommand.setName(id);
                                var widget;
                                var sTmp = 'widget = new Fusion.Widget.' + oCommand.getAction() + '(oCommand)';
                                eval(sTmp);
                                this.aWidgets.push(widget);
                                oCommand.setName(oldName);                                
                                break;
                            case 'Flyout':
                                var options = {};
                                options.label = button.obj.label;
                                options.tooltip = button.obj.tooltip;
                                if (button.obj.imageUrl != '') {
                                    options.imgPath = button.obj.imageUrl;
                                }                                
                                var menu = new Jx.Menu(options);
                                
                                this.processMenuUiItems(button.obj.subItems, menu, null);
                                
                                tb.add(menu);
                                break;
                            default:
                                /* TODO: this could be an exception? */
                                tb.add(new Jx.ToolbarSeparator());
                        }
                    }
                }
                //else { console.log ('toolbar ' + toolbars[i].name + ' not found'); }
            }

        }
    },
    
    createContextMenus: function() {
        var menus = this.oWebLayout.aMenus;
        if (menus.length > 0) {
            for (var i=0; i<menus.length; i++) {
                var oMap = null;
                if (menus[i].mapName != '') {
                    oMap = this.getMapByName(menus[i].mapName);
                } else {
                    oMap = this.getMapByIndice(0);
                }
                var menu = new Jx.ContextMenu();
                this.processMenuUiItems(menus[i].items, menu, oMap);
                oMap.setContextMenu(menu);
            }
        }
    },
    
    processMenuUiItems: function(menuItems, menu, oMap) {
        for (var i=0; i<menuItems.length; i++) {
            var item = menuItems[i];
            switch (item.func) {
                case 'Separator':
                    break;
                case 'Command':
                    var widget = this.createWidgetFromCommandName(item.obj.name, '');
                    if (widget) {
                        if (widget.isMenuWidget) {
                            menu.add(widget.getMenu());
                        } else {
                            var action;
                            if (oMap) {
                                action = new Jx.Action(oMap.executeFromContextMenu.bind(oMap, widget));
                                widget.registerForEvent(Fusion.Event.WIDGET_STATE_CHANGED, 
                                    function(eventID, aWidget) {
                                        this.setEnabled(aWidget.isEnabled());
                                    }.bind(action));                        
                            } else {
                                action = new Jx.Action(widget.activateTool.bind(widget));
                            
                            }

                            var options = {};
                            options.label = widget._oCommand.sLabel;
                            options.image = widget._oCommand.sImageurl;
                            menu.add(new Jx.MenuItem(action, options));
                        }
                    }
                    break;
                case 'Flyout':
                    var options = {};
                    options.label = item.obj.label;
                    if (item.obj.imageUrl != '') {
                        options.image = item.obj.imageUrl;
                    }
                    var subMenu = new Jx.SubMenu(options);
                    this.processMenuUiItems(item.obj.subItems, subMenu, oMap);
                    menu.add(subMenu);
                    break;
                default:
            
            }
        }
    },
    
    createWidgetFromCommandName: function(name, id) {
        var widget = null;
        var oCommand = this.oWebLayout.getCommandByName(name);
        if (oCommand) {
            var oldName = oCommand.getName();
            oCommand.setName('');
            var widget;
            var sTmp = 'widget = new Fusion.Widget.' + oCommand.getAction() + '(oCommand)';
            eval(sTmp);
            this.aWidgets.push(widget);
            oCommand.setName(oldName);
        }
        return widget;
    },
    
    getWidgetById: function(id) {
        return $(id).widget;
    },
    
    getWidgetsByType: function(type) {
        var a = [];
        for (var i=0; i<this.aWidgets.length; i++) {
            if (this.aWidgets[i].sName == type) {
                a.push(this.aWidgets[i]);
            }
        }
        return a;
    }
});/*****************************************************************************
 * $Id: utils.js,v 1.9 2007/05/09 20:49:45 pspencer Exp $
 * Purpose: abstract handling of DOM node structures
 * Project: MapGuide Open Source 
 * Author: DM Solutions Group Inc
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *****************************************************************************/

var DomNode = Class.create();

DomNode.prototype = {
    initialize: function( xmlNode /*, parent */ ) {
        this.textContent = '';
        this.nodeName = xmlNode?xmlNode.nodeName:'';
        this.parentNode = arguments[1]?arguments[1]:null;
        this.childNodes  = [];
        this.attributes = [];
        this.attributeNames = [];
        if (xmlNode) {
            if (xmlNode.attributes) {
                for (var i=0; i<xmlNode.attributes.length; i++) {
                    this.attributeNames.push(xmlNode.attributes[i].name);
                    this.attributes[xmlNode.attributes[i].name] = xmlNode.attributes[i].nodeValue;
                }
            }
            for (var i=0; i<xmlNode.childNodes.length; i++) {
              if (xmlNode.childNodes[i].nodeType != 3) {
                this.childNodes.push(new DomNode(xmlNode.childNodes[i], this));
              } else {
                this.textContent = this.textContent + xmlNode.childNodes[i].nodeValue;
              }
            }
        }
        this._currentNode = 0;
    },
    getAttribute: function(name) {
        return typeof(this.attributes[name]) != 'undefined' ? this.attributes[name] : null;
    },
    removeChild: function(child) {
        var result = null;
        for(var i=0; i<this.childNodes.length; i++) {
            if (this.childNodes[i] == child) {
                this._currentNode = 0;
                this.childNodes.splice(i,1);
                child.parentNode = null;
                result = child;
                break;
            }
        }
        return result;
    },
    appendChild: function(child) {
        if (child.parentNode) {
            child.parentNode.removeChild(child);
        }
        child.parentNode = this;
        this.childNodes.push(child);
    },
    insertBefore: function(newChild,refChild) {
        var bInserted = false;
        if (refChild) {
            for (var i=0; i<this.childNodes.length; i++) {
                if (this.childNodes[i] == refChild) {
                    if (newChild.parentNode) {
                        newChild.parentNode.removeChild(child);
                    }
                    newChild.parentNode = this;
                    this.childNodes.splice(i,0,newChild);
                    bInserted = true;
                    break;
                }
            }
        }
        if (!bInserted) {
            this.appendChild(newChild);
        }
    },
    toString: function(depth) {
        var s = '';
        var spacer = '';
        for (i=0; i<depth; i++) {
            spacer = spacer + '';
        }
        s = spacer + '&lt;' + this.nodeName;
        if (this.attributes.length > 0) {
            for (var name in this.attributes) {
                if (typeof(this.attributes[name]) == 'String' ) {
                    s = s + ' ' + name + '="' + this.attributes[name] + '"';
                }
            }
        }
        s = s + '&gt;'
        if (this.childNodes.length == 0) {
            s = s + this.textContent;
            spacer = '';
        } else {  
            s = s + '\n';
        }
        for (var i=0; i<this.childNodes.length; i++) {
            s = s + this.childNodes[i].toString( depth + 1 );
        }
        s = s + spacer + '&lt;/'+this.nodeName+'&gt;';
        return s;
    },
    toXML: function() {
        var s = this.parentNode?'':'<?xml version="1.0" encoding="UTF-8"?>\n';
        s = s+ '<' + this.nodeName;
        if (this.attributeNames.length > 0) {
            for (var i=0; i<this.attributeNames.length; i++) {
                var name = this.attributeNames[i];
                s = s + ' ' + name + '="' + this.attributes[name] + '"';
            }
        }
        s = s + '>'
        if (this.childNodes.length == 0) {
            var content = this.textContent + ''; //force string value if textContent was automatically made to a number
            content = content.replace('&','&amp;');
            content = content.replace(/</g, encodeURIComponent('&lt;'));
            content = content.replace(/>/g, encodeURIComponent('&gt;'));
            s = s + content;
            
        }
        for (var i=0; i<this.childNodes.length; i++) {
            s = s + this.childNodes[i].toXML();
        }
        s = s + '</'+this.nodeName+'>\n';
        return s;
    },
    getNodeText: function(name) {
        var s = '';
        var n = this.findFirstNode(name);
        if (n) {
            s = n.textContent;
        }
        return s;
    },
    setNodeText: function(name, value) {
        var n = this.findFirstNode(name);
        if (n) {
            n.setTextContent(value);
        }
    },
    setTextContent: function(value) {
        this.textContent = value;
    },
    setAttribute: function(name, value) {
        if (typeof this.attributes[name] == 'undefined') {
            this.attributeNames.push(name);
        }
        this.attributes[name] = value;
    },
    findFirstNode: function( name ) {
        this._currentNode = 0;
        if (this.nodeName == name) {
            return this;
        } else {
            for (var i=0; i<this.childNodes.length; i++) {
                var node = this.childNodes[i].findFirstNode(name);
                if (node) {
                    if (node.parentNode == this) {
                        this._currentNode = i + 1;
                    } else {
                        this._currentNode = i;          
                    }
                    return node;
                }
            }
            return false;
        }
    },
    findNextNode: function( name ) {
        if (this.nodeName == name) {
            return this;
        } else {
            for (var i=this._currentNode; i<this.childNodes.length; i++) {
                var node = this.childNodes[i].findNextNode(name);
                if (node) {
                    if (node.parentNode == this) {
                        this._currentNode = i + 1;
                    } else {
                        this._currentNode = i;          
                    }
                    return node;
                }
            }
            return false;
        } 
    }
};

var DomNodeFactory = {
    create: function( name, value ) {
        var node = new DomNode();
        node.nodeName = name;
        node.textContent = value || '';
        return node;
    }
};/*****************************************************************************
 * $Id: Error.js,v 1.6 2007/06/29 13:33:03 pspencer Exp $
 * Purpose: Fusion.Error - general error class for managing error information
 * Project: Fusion
 * Author: DM Solutions Group Inc 
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *****************************************************************************/
/**
 * Class: Fusion.Error
 * General purpose Error object
 */
Fusion.Error = Class.create();
Fusion.Error.prototype = {
    FATAL: 0,
    WARNING: 1,
    NOTICE: 2,
    type: null,
    message: null,
    initialize: function(type, message) {
        this.type = type;
        this.message = message;
    },
    
    alert: function() {
        var type = this.typeToString(this.type);
        alert('Fusion Error: ' + type + '\n' + this.message);
    },
    
    toString: function() {
        var type = this.typeToString(this.type);
        return type + ": " + this.message;
    },
    
    typeToString: function(type) {
        switch (type) {
            case Fusion.Error.FATAL:
                return 'FATAL';
            case Fusion.Error.WARNING:
                return 'WARNING';
            case Fusion.Error.NOTICE:
                return 'NOTICE';
            default:
                return 'UNKNOWN ('+type+')';
        }
    }
};/*****************************************************************************
 *
 * $Id: ConfigMgr.js,v 1.57 2007/06/29 20:00:21 pspencer Exp $
 *
 * Purpose: Fusion.Lib.ConfigMgr handles communication with the server
 *          during initialization, and triggers loading of the WebLayout
 *
 * Project: Fusion
 *
 * Author: DM Solutions Group Inc 
 *
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 *****************************************************************************/
/**
 * Fusion.Lib.ConfigMgr
 *
 * Configuration class for fusion using a web layout.
 */


Fusion.Lib.ConfigMgr = Class.create();
Object.extend(Fusion.Lib.ConfigMgr.prototype, {
    oApp: null,
    oBroker: null,
    //array of map widgets. For now only the map from the weblayout is used
    //TODO : use the mapconfig to pass other maps.
    aoMapWidget : null,

    oWebLayout: null,
    
    oWebLayout : null,

    aWidgetNames : [],
    
    aWidgets: null,

    /**
     * construct a new configuration manager. 
     *
     * @param app {Object} the application object
     */
    initialize : function(app, sessionid) {
        // console.log('configuration manager initializing');
        this.oApp = app;
        this.aoMapWidget = [];
        this.aWidgets = [];
        
        this.scriptLang = app.getScriptLanguage();
        this.redirectScript = app.getRedirectScript();
        
		this.sessionID = sessionid || null;
        this.createWebLayout();
    },

    /**
     * create an object to manage the web layout.  When it has
     * finished loading and parsing the web layout, it will
     * emit an event.  We load the map definitions when this
     * event is triggered.
     */
    createWebLayout : function()
    {
        // console.log('ConfigMgr::parseWebLayout');
        this.oWebLayout = new Fusion.Lib.WebLayout(this.oApp);
        this.oWebLayout.registerForEvent(Fusion.Event.WEBLAYOUT_PARSED, this.loadWidgets.bind(this));
        this.oWebLayout.parse();
    },

    getListofWidgets : function() {
        var oCommand = null;
        var sTmp;
        var aCommands = [];
        for (var i=0; i<this.oWebLayout.commandObj.length; i++) {
            oCommand = this.oWebLayout.commandObj[i];
            
            if ($(oCommand.getName())) {
                aCommands.push(oCommand);
            }
        }
        return aCommands;
    },
    
    getListofToolbarWidgets: function() {
        var aCommands = [];
        var toolbars = this.oWebLayout.aToolbars;
        for (var i=0; i<toolbars.length; i++) {
            /* test to see if the container for the toolbar is in the page */
            if($(toolbars[i].container)) {
                for (var j=0; j<toolbars[i].buttons.length; j++) {
                    var button = toolbars[i].buttons[j];
                    if (button.func == 'Command') {
                        var oCommand = this.oWebLayout.getCommandByName(toolbars[i].buttons[j].obj.name);
                        if (oCommand) {
                            aCommands.push(oCommand);
                        }
                    } else if (button.func == 'Flyout') {
                        aCommands = aCommands.concat(this.getListofMenuWidgets(button.obj.subItems));
                    }
                }
            }
        }

        return aCommands;
    },
    
    getListofContextMenuWidgets: function() {
        var aCommands = [];
        var menus = this.oWebLayout.aMenus;
        for (var i=0; i<menus.length; i++) {
            /* test to see if the container for the toolbar is in the page */
            if(menus[i].mapName == '' || this.getMapByName(menus[i].mapName)) {
                aCommands = aCommands.concat(this.getListofMenuWidgets(menus[i].items));
            }
        }
        return aCommands;
    },
    
    getListofMenuWidgets: function(menuItems) {
        var aCommands = [];
        for (var i=0; i<menuItems.length; i++) {
            var item = menuItems[i];
            switch (item.func) {
                case 'Separator':
                    break;
                case 'Command':
                    var oCommand = this.oWebLayout.getCommandByName(item.obj.name);
                    if (oCommand) {
                        aCommands.push(oCommand);
                    }
                    break;
                case 'Flyout':
                    aCommands = aCommands.concat(this.getListofMenuWidgets(item.obj.subItems));
                    break;
                default:
                
            }
        }
        return aCommands;
    },


    loadWidgets : function() {
        var aCommands = this.getListofWidgets();
        aCommands = aCommands.concat(this.getListofToolbarWidgets());
        aCommands = aCommands.concat(this.getListofContextMenuWidgets());
        
        for (var i=0; i<aCommands.length; i++) {
			var c = aCommands[i];
			var js = c.sLocation + '/' + c.getAction() + '.js';
            Fusion.require(js);
        }
        
        this.oApp.setLoadState(this.oApp.LOAD_WIDGETS);
    },

    createWidgets :  function() {
        this.createMapWidget();
        this.createAllWidgets();
        this.createToolbars();
        this.createContextMenus();        
    },

    /**convension for the widgets :
     * basic command widgets will be names [Action] with file name being [Action].js
     *  -ex ZoomIn = Zoom()
     *  basic command widgets will be located under base_url/widgets/
     */
    createMapWidget : function() {
        var aCommands = this.oWebLayout.getCommandByType('MapCommandType');
        if (aCommands.length > 0)
        {
            var oCommand = null;
            for (var i=0; i<aCommands.length; i++)
            {
                oCommand = aCommands[i];
                var oElement =  $(oCommand.getName());
                if (oElement != null)
                {
                    var widget;
                    var sID = this.sessionID ? ',"'+this.sessionID+'"': '';
                    var sTmp = 'widget = new Fusion.Widget.' + oCommand.getAction() + '(oCommand'+sID+')';
                    eval(sTmp);
                    if ($(oCommand.getName())) {
                        $(oCommand.getName()).widget = widget;
                    }
                    this.aoMapWidget.push(widget);
                }
            }
        }
    },
    
    getMapInfo: function(resourceId) {
        for (var i=0; i<this.oWebLayout.aMaps.length; i++) {
            if (this.oWebLayout.aMaps[i].resourceId == resourceId) {
                return this.oWebLayout.aMaps[i];
            }
        }
        return null;
    },

    getMapByName : function(sName)
    {
        var nMaps = this.aoMapWidget.length;
        var oMap = null;
        for (var i=0; i<nMaps; i++)
        {
            if (this.aoMapWidget[i].getMapName() == sName){
                oMap = this.aoMapWidget[i];
                break;
            }
        }
        return oMap;
    },

    /**
      get the map object using the html element id used by the map
    */
    getMapById : function(sId)
    {
        var nMaps = this.aoMapWidget.length;
        var oMap;
        for (var i=0; i<nMaps; i++)
        {
            oMap = this.aoMapWidget[i];
            if (oMap.getDomId() == sId)
            {
                return oMap;
            }
        }
        return null;
    },

    getMapByIndice : function(nIndice)
    {
        if (nIndice < this.aoMapWidget.length)
        {
            var oMap = this.aoMapWidget[nIndice];
            return oMap;
        }

        return null;
    },

  
    createAllWidgets : function()
    {
        var aCommands = this.getListofWidgets();
        for (var i=0; i<aCommands.length; i++) {
            var oCommand = aCommands[i];
            if (oCommand.getAction() != '' && 
                oCommand.getAction() != 'MGMap' && /* TODO Remove the need to test these here */
                oCommand.getAction() != 'MSMap') {
                var widget;
                var sTmp = 'widget = new Fusion.Widget.' + oCommand.getAction() + '(oCommand)';
                eval(sTmp);
                this.aWidgets.push(widget);
                if ($(oCommand.getName())) {
                    $(oCommand.getName()).widget = widget;
                }
            }
        }

    },
    
    createToolbars: function() {
        //console.log('searching for toolbars to create');
        var dummyAction = new Jx.Action(null);
        var toolbars = this.oWebLayout.aToolbars;
        if (toolbars.length > 0) {
            for (var i=0; i<toolbars.length; i++) {
                var toolbarObj = $(toolbars[i].container);
                if (toolbarObj) {
                    //console.log('creating toolbar');
                    var toolbar = toolbars[i];
                    var tb = new Jx.Toolbar(toolbarObj);
                    tb.domObj.id = toolbar.name;
                    for (var j=0; j<toolbar.buttons.length; j++) {
                        var id = 'toolbar'+i+'command'+j;
                        var button = toolbar.buttons[j];
                        var tbItem = null;
                        switch (button.func) {
                            case 'Separator':
                                tb.add(new Jx.ToolbarSeparator());
                                break;
                            case 'Command':
                                var oCommand = this.oWebLayout.getCommandByName(button.obj.name);
                                
                                tbItem = new Jx.ToolbarItem();
                                tbItem.domObj.id = id;
                                tb.add(tbItem);

                                var oldName = oCommand.getName();
                                oCommand.setName(id);
                                var widget;
                                var sTmp = 'widget = new Fusion.Widget.' + oCommand.getAction() + '(oCommand)';
                                eval(sTmp);
                                this.aWidgets.push(widget);
                                oCommand.setName(oldName);                                
                                break;
                            case 'Flyout':
                                var options = {};
                                options.label = button.obj.label;
                                options.tooltip = button.obj.tooltip;
                                if (button.obj.imageUrl != '') {
                                    options.imgPath = button.obj.imageUrl;
                                }                                
                                var menu = new Jx.Menu(options);
                                
                                this.processMenuUiItems(button.obj.subItems, menu, null);
                                
                                tb.add(menu);
                                break;
                            default:
                                /* TODO: this could be an exception? */
                                tb.add(new Jx.ToolbarSeparator());
                        }
                    }
                }
                //else { console.log ('toolbar ' + toolbars[i].name + ' not found'); }
            }

        }
    },
    
    createContextMenus: function() {
        var menus = this.oWebLayout.aMenus;
        if (menus.length > 0) {
            for (var i=0; i<menus.length; i++) {
                var oMap = null;
                if (menus[i].mapName != '') {
                    oMap = this.getMapByName(menus[i].mapName);
                } else {
                    oMap = this.getMapByIndice(0);
                }
                var menu = new Jx.ContextMenu();
                this.processMenuUiItems(menus[i].items, menu, oMap);
                oMap.setContextMenu(menu);
            }
        }
    },
    
    processMenuUiItems: function(menuItems, menu, oMap) {
        for (var i=0; i<menuItems.length; i++) {
            var item = menuItems[i];
            switch (item.func) {
                case 'Separator':
                    break;
                case 'Command':
                    var widget = this.createWidgetFromCommandName(item.obj.name, '');
                    if (widget) {
                        if (widget.isMenuWidget) {
                            menu.add(widget.getMenu());
                        } else {
                            var action;
                            if (oMap) {
                                action = new Jx.Action(oMap.executeFromContextMenu.bind(oMap, widget));
                                widget.registerForEvent(Fusion.Event.WIDGET_STATE_CHANGED, 
                                    function(eventID, aWidget) {
                                        this.setEnabled(aWidget.isEnabled());
                                    }.bind(action));                        
                            } else {
                                action = new Jx.Action(widget.activateTool.bind(widget));
                            
                            }

                            var options = {};
                            options.label = widget._oCommand.sLabel;
                            options.image = widget._oCommand.sImageurl;
                            menu.add(new Jx.MenuItem(action, options));
                        }
                    }
                    break;
                case 'Flyout':
                    var options = {};
                    options.label = item.obj.label;
                    if (item.obj.imageUrl != '') {
                        options.image = item.obj.imageUrl;
                    }
                    var subMenu = new Jx.SubMenu(options);
                    this.processMenuUiItems(item.obj.subItems, subMenu, oMap);
                    menu.add(subMenu);
                    break;
                default:
            
            }
        }
    },
    
    createWidgetFromCommandName: function(name, id) {
        var widget = null;
        var oCommand = this.oWebLayout.getCommandByName(name);
        if (oCommand) {
            var oldName = oCommand.getName();
            oCommand.setName('');
            var widget;
            var sTmp = 'widget = new Fusion.Widget.' + oCommand.getAction() + '(oCommand)';
            eval(sTmp);
            this.aWidgets.push(widget);
            oCommand.setName(oldName);
        }
        return widget;
    },
    
    getWidgetById: function(id) {
        return $(id).widget;
    },
    
    getWidgetsByType: function(type) {
        var a = [];
        for (var i=0; i<this.aWidgets.length; i++) {
            if (this.aWidgets[i].sName == type) {
                a.push(this.aWidgets[i]);
            }
        }
        return a;
    }
});/*****************************************************************************
 *
 *
 * Purpose: Fusion
 *
 * Project: Fusion
 *
 * Author: DM Solutions Group Inc 
 *
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 *****************************************************************************/
/*
* $Id: WebCommand.js,v 1.15 2007/06/29 19:33:51 pspencer Exp $
*/

Fusion.Lib.WebCommand = Class.create();
Fusion.Lib.WebCommand.prototype = {
    sName : "",
    sLabel : "",
    sTooltip : "",
    sDescription : "",
    sImageurl : "",
    sDisabledImageURL : "",
    sTargetViewer : "",
    sType : 'CommandType',
    sLocation : 'widgets/',
    jsonNode : null,
    sAction : "",  
    sMapId : '',

    initialize : function(jsonNode) {
        this.jsonNode = Object.extend({},jsonNode);
        this.sType = jsonNode['@type'] ? jsonNode['@type'][0] : 'CommandType';
        if (jsonNode != null) {
            this.sName = jsonNode.Name ? jsonNode.Name[0] : '';
            this.sLabel = jsonNode.Label ? jsonNode.Label[0] : '';
            this.sTooltip = jsonNode.Tooltip ? jsonNode.Tooltip[0] : '';
            this.sDescription = jsonNode.Description ? jsonNode.Description[0] : '';
            this.sLocation = jsonNode.Location ? jsonNode.Location[0] : '';
            if (this.sLocation == '') {
                this.sLocation = 'widgets';
            }

            this.sTargetViewer = jsonNode.TargetViewer ? jsonNode.TargetViewer[0] : '';
            this.sAction = jsonNode.Action ? jsonNode.Action[0] : '';
            this.sMapId = jsonNode.MapId ? jsonNode.MapId[0] : '';

            var sImageurl = jsonNode.ImageURL ? jsonNode.ImageURL[0] : '';
            if (sImageurl.length > 4 && sImageurl.substring(0,4) == "http") {
                this.sImageurl = sImageurl;
            } else {
                if (this.sType == 'BasicCommandType') {
                    this.sImageurl = Fusion.getFusionURL() + sImageurl;
                } else {
                    this.sImageurl = sImageurl;
                }
            }

            var sDisabledImageURL = jsonNode.DisabledImageUrl ? jsonNode.DisabledImageUrl[0] : '';
            if (sImageurl.length > 4 && sImageurl.substring(0,4) == "http") {
                this.sDisabledImageURL = sDisabledImageURL;
            }
            else if (sDisabledImageURL == '') {
                sDisabledImageURL = sImageurl;
            } else {
                if (this.sType == 'BasicCommandType') {
                    this.sDisabledImageURL = Fusion.getFusionURL() + sDisabledImageURL;
                } else {
                    this.sDisabledImageURL = sDisabledImageURL;
                }
            }
        }
    },

    getName : function() {
        return this.sName;
    },

    setName : function(sName) {
        this.sName = sName;
    },

    getImageURL : function() {
        return this.sImageurl;
    },

    getDisabledImageURL : function() {
        return this.sDisabledImageURL;
    },

    getType : function() {
        return this.sType;
    },

    getAction : function() {
        return this.sAction;
    },

    getMap : function() {
        if (this.sMapId != '') {
            return Fusion.getMapById(this.sMapId);
        } else {
            //return first map if map id id not defined.
            return Fusion.getMapByIndice(0);
        }  
    }
};
/*****************************************************************************
 * $Id: WebLayout.js,v 1.39 2007/06/29 19:56:30 pspencer Exp $
 * Purpose: Fusion
 * Project: Fusion
 * Author: DM Solutions Group Inc 
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *****************************************************************************/

/**
 * WebLayout
 *
 * Utility class to parse a web layout
 *
 */
Fusion.Event.WEBLAYOUT_PARSED = Fusion.Event.lastEventId++;

Fusion.Lib.WebLayout = Class.create();
Object.extend(Fusion.Lib.WebLayout.prototype, Fusion.Lib.EventMgr);
Object.extend(Fusion.Lib.WebLayout.prototype, {   
    mapId : '',
    aMaps: null,
    commandObj : null,
    aToolbars: null,
    aMenus: null,
    webLayout : null,
    oBroker: null,
    oConfigManager: null,

    initialize: function(app) {   
        //console.log('WebLayout initialize');
        Fusion.Lib.EventMgr.initialize.apply(this, []);
        this.app = app;
        this.oBroker = app.getBroker();
        this.webLayout =  app.getWebLayout();
        
        this.commandObj = [];
        this.aMaps = [];
        this.registerEventID(Fusion.Event.WEBLAYOUT_PARSED);
    },

    parse : function() {
        /* if the web layout is not in the mapguide server, 
           just load the xml*/
        
        if (this.webLayout.match('Library://') == null) {
            var options = {};
            options.method = 'get';
            options.onSuccess = this.convertXML.bind(this);
            new Ajax.Request( this.webLayout, options);
        } else {
            var r = new Fusion.Lib.MGRequest.MGGetResourceContent(this.webLayout);
            this.oBroker.dispatchRequest(r, this.parseXML.bind(this));
        }
    },
    
    convertXML: function(r, json) {
        var options = {};
        options.onSuccess = this.parseXML.bind(this);
        options.parameters = 'xml='+encodeURIComponent(r.responseText.replace(/\\/g, '\\\\\\\\'))+'&ts='+((new Date()).getTime());
        var sl = Fusion.getScriptLanguage();
        Fusion.ajaxRequest('common/'+sl+'/Xml2JSON.'+sl, options);
    },
    
    parseXML: function(r, json) {
        if (json) {
            var mainNode;
            eval("mainNode="+r.responseText);
            
            var webLayout = mainNode.WebLayout;
            /* process Map nodes */
            
            if (webLayout.Map instanceof Array) {
                var maps = webLayout.Map;
                for (var i=0; i<maps.length; i++) {
                    var map = maps[i];
                    var mapObj = {};
                    mapObj.resourceId = map.ResourceId[0];
                    mapObj.referenceImageUrl = map.ReferenceImageUrl[0];
                    mapObj.links = {layers:[], groups: []};
                    mapObj.layerEvents = {};
                    if (map.Links) {
                        /* process Groups */
                        if (map.Links[0].Group instanceof Array) {
                            for (var j=0; j<map.Links[0].Group.length; j++) {
                                var group = map.Links[0].Group[j];
                                mapObj.links.groups.push({name:group.Name,url:group.Url});
                            }
                        }
                        if (map.Links[0].Layer instanceof Array) {
                            for (var j=0; j<map.Links[0].Layer.length; j++) {
                                var layer = map.Links[0].Layer[j];
                                mapObj.links.layers.push({name:layer.Name,url:layer.Url});
                            }
                        }
                    }
                    /* process layer events */
                    if (map.LayerEvents) {
                        if (map.LayerEvents[0].Layer instanceof Array) {
                            for (var j=0; j<map.LayerEvents[0].Layer.length; j++) {
                                var layer = map.LayerEvents[0].Layer[j];
                                var layerObj = {};
                                layerObj.name = layer.Name[0];
                                layerObj.onEnable = [];
                                layerObj.onDisable = [];
                                
                                if (layer.OnEnable instanceof Array) {
                                    for (var k=0; k<layer.OnEnable[0].Layer.length; k++) {
                                        var kLayer = layer.OnEnable[0].Layer[k];
                                        layerObj.onEnable.push({name:kLayer.Name[0], enable: kLayer.Enable[0] == 'true' ? true : false});
                                    }
                                }
                                if (layer.OnDisable instanceof Array) {
                                    for (var k=0; k<layer.OnDisable[0].Layer.length; k++) {
                                        var kLayer = layer.OnDisable[0].Layer[k];
                                        layerObj.onDisable.push({name:kLayer.Name[0], enable: kLayer.Enable[0] == 'true' ? true : false});
                                    }
                                }
                                mapObj.layerEvents[layerObj.name] = layerObj;
                            }
                        }
                    }
                    this.aMaps.push(mapObj);
                }
            }
            
            var commandSet = webLayout.CommandSet;
            if (commandSet instanceof Array) {
                var commands = commandSet[0].Command;
                if (commands instanceof Array) {
                    for (var i=0; i<commands.length; i++) {
                        this.commandObj.push(new Fusion.Lib.WebCommand(commands[i]));
                    }
                }
            }
            
            /* process toolbars */
            this.aToolbars = [];

            if (webLayout.ToolBar instanceof Array) {
                var toolbars = webLayout.ToolBar;
                for (var i=0; i<toolbars.length; i++) {
                    this.aToolbars.push( new Fusion.Lib.WebLayout.Toolbar(toolbars[i]) );
                }
            }
            
            /* process context menus */
            this.aMenus = [];

            if (webLayout.ContextMenu instanceof Array) {
                var menus = webLayout.ContextMenu;
                for (var i=0; i<menus.length; i++) {
                    this.aMenus.push( new Fusion.Lib.WebLayout.ConextMenu(menus[i]) );
                }
            }
            
            /* process search definitions */
            this.aSearchCategories = {};
            this.aSearchDefinitions = {};
            if (webLayout.SearchDefinitions instanceof Array) {
                var categories = webLayout.SearchDefinitions[0];
                if (categories.SearchCategory instanceof Array) {
                    for (var i=0; i<categories.SearchCategory.length; i++) {
                        var oCategory = {};
                        var category = categories.SearchCategory[i];
                        oCategory.id = category['@id'];
                        oCategory.name = category['@name'];
                        oCategory.layer = category.Layer ? category.Layer[0] : '';
                        oCategory.searchDefinitions = [];
                        var defns = category.SearchDefinition;
                        for (var k=0; k<defns.length; k++) {
                            var defn = new Fusion.Lib.WebLayout.SearchDefinition(defns[k]);
                            defn.category = oCategory;
                            oCategory.searchDefinitions[defn.id] = defn;
                            this.aSearchDefinitions[defn.id] = defn;
                        }
                    }
                }
            }
            this.triggerEvent(Fusion.Event.WEBLAYOUT_PARSED);
        }
    },
    
    getMapResourceId : function() {
        return this.mapId;
    },

    getCommandByName : function(sName) {
        var oCommand;
        for (var i=0; i<this.commandObj.length; i++) {
            oCommand = this.commandObj[i];

            if (oCommand.getName() == sName) {
                return this.commandObj[i];
            }
        }
    },

    getCommandByType : function(sType) {
        var oCommand;
        var aReturn = [];

        for (var i=0; i<this.commandObj.length; i++) {
            oCommand = this.commandObj[i];

            if (oCommand.getType() == sType) {
                aReturn.push(oCommand);
            }
        }

        return aReturn;
    }
});

Fusion.Lib.WebLayout.Toolbar = Class.create();
Fusion.Lib.WebLayout.Toolbar.prototype = {
    buttons: null,
    name: null,
    container: null,
    initialize: function(jsonNode) {
        this.name = jsonNode.Name ? jsonNode.Name[0] : '';
        this.container = jsonNode.Container ? jsonNode.Container[0] : '';
        this.buttons = [];
        if (jsonNode.Button instanceof Array) {
            for (var i=0; i<jsonNode.Button.length; i++) {
                this.buttons.push(new Fusion.Lib.WebLayout.UiItem(jsonNode.Button[i]));
            }
        }
    }
};

Fusion.Lib.WebLayout.ConextMenu = Class.create();
Fusion.Lib.WebLayout.ConextMenu.prototype = {
    items: null,
    mapName: null,
    initialize: function(jsonNode) {
        this.mapName = jsonNode.Map ? jsonNode.Map[0] : '';
        this.items = [];
        if (jsonNode.MenuItem instanceof Array) {
            for (var i=0; i<jsonNode.MenuItem.length; i++) {
                this.items.push(new Fusion.Lib.WebLayout.UiItem(jsonNode.MenuItem[i]));
            }
        }
    }
};

Fusion.Lib.WebLayout.UiItem = Class.create();
Fusion.Lib.WebLayout.UiItem.prototype = {
    func: null,
    obj: null,
    initialize: function(jsonNode) {
        this.func = jsonNode.Function[0];
        switch(this.func) {
            case 'Separator':
                break;
            case 'Command':
                this.obj = new Fusion.Lib.WebLayout.Command(jsonNode);
                break;
            case 'Flyout':
                this.obj = new Fusion.Lib.WebLayout.Flyout(jsonNode);
                break;
            default:
                /* TODO: this could be an exception? */
        }
    }
};

Fusion.Lib.WebLayout.Command = Class.create();
Fusion.Lib.WebLayout.Command.prototype = {
    name: null,
    initialize: function(jsonNode) {
        this.name = jsonNode.Command[0];
    }
};

Fusion.Lib.WebLayout.Flyout = Class.create();
Fusion.Lib.WebLayout.Flyout.prototype = {
    label: null,
    tooltip: null,
    description: null,
    imageUrl: null,
    disabledImageUrl: null,
    /* TODO add active class, disabled class */
    subItems: null,
    
    initialize: function(jsonNode) {
        this.label = jsonNode.Label ? jsonNode.Label[0] : '';
        this.tooltip = jsonNode.Tooltip ? jsonNode.Tooltip[0] : '';
        this.description = jsonNode.Description ? jsonNode.Description[0] : '';
        this.imageUrl = jsonNode.ImageURL ? jsonNode.ImageURL[0] : '';
        this.disabledImageUrl = jsonNode.DisabledImageURL ? jsonNode.DisabledImageURL[0] : '';
        this.subItems = [];
        if (jsonNode.SubItem instanceof Array) {
            for (var i=0; i<jsonNode.SubItem.length; i++) {
                this.subItems.push(new Fusion.Lib.WebLayout.UiItem(jsonNode.SubItem[i]));
            }
        }
    }
};

Fusion.Lib.WebLayout.SearchDefinition = Class.create();
Fusion.Lib.WebLayout.SearchDefinition.prototype = {
    id: null,
    name: null,
    category: null,
    parameters: null,
    join: null,
    rule: null,
    
    initialize: function(json) {
        this.id = json['@id'];
        this.name = json['@name'];
        if (json.Join instanceof Array) {
            this.join = new Fusion.Lib.WebLayout.SearchJoin(json.Join[0]);
        }
        this.parameters = [];
        if (json.Parameter instanceof Array) {
            for (var i=0; i<json.Parameter.length; i++) {
                this.parameters.push(json.Parameter[i]['@name']);
            }
        }
        var rule;
        if (json.SearchAnd instanceof Array) {
            this.rule = new Fusion.Lib.WebLayout.SearchRule('AND');
            rule = json.SearchAnd[0];
        } else if (json.SearchOr instanceof Array) {
            this.rule = new Fusion.Lib.WebLayout.SearchRule('OR');
            rule = json.SearchOr[0];
        }
        if (rule && rule.SearchCondition instanceof Array) {
            for (var i=0; i<rule.SearchCondition.length; i++) {
                this.rule.add(new Fusion.Lib.WebLayout.SearchCondition(rule.SearchCondition[i]));
            }
        }
    },
    
    getJoinUrl: function(params) {
        if (this.join) {
            return '&joinlayer='+this.join.layer+'&joinpk='+this.join.primaryKey+'&joinfk='+this.join.foreignKey;
        } else {
            return '';
        }
    },
    
    getFilterUrl: function(params) {
        return '&filter='+encodeURIComponent(this.rule.toString(params));
    }
};

Fusion.Lib.WebLayout.SearchJoin = Class.create();
Fusion.Lib.WebLayout.SearchJoin.prototype = {
    layer: null,
    primaryKey: null,
    foreignKey: null,
    initialize: function(json) {
        this.layer = json.Layer ? json.Layer[0] : '';
        this.primaryKey = json.PrimaryKey ? json.PrimaryKey[0] : '';
        this.foreignKey = json.ForeignKey ? json.ForeignKey[0] : '';
    }
};

Fusion.Lib.WebLayout.SearchRule = Class.create();
Fusion.Lib.WebLayout.SearchRule.prototype = {
    type: null,
    conditions: null,
    initialize: function(type) {
        this.type = type;
        this.conditions = [];
    },
    
    add: function(condition) {
        this.conditions.push(condition);
    },
    
    remove: function(condition) {
        for (var i=0; i<this.conditions.length; i++) {
            if (this.conditions[i] == condition) {
                this.conditions.splice(i, 1);
                break;
            }
        }
    },
    
    toString: function(params) {
        var conditions = [];
        for (var i=0; i<this.conditions.length; i++) {
            this.conditions[i].setParams(params);
            var c = this.conditions[i].toString();
            if (c != '') {
                conditions.push(c);
            }
        }
        return '(' + conditions.join(') ' + this.type + ' (') + ')';
    }
};

Fusion.Lib.WebLayout.SearchCondition = Class.create();
Fusion.Lib.WebLayout.SearchCondition.prototype = {
    column: null,
    operator: null,
    parameter: null,
    quote: null,
    value: null,
    operators: {eq:'=', like:'like', lt:'<', lte:'<=', gt:'>', gte:'>=', neq:'<>'},
    includeIfEmpty: false,
    
    initialize: function(json) {
        this.column = json.Column[0];
        this.operator = this.operators[json.Operator[0].toLowerCase()];
        this.parameter = json.Parameter[0];
        this.quote = json['@quote'] ? json['@quote'] : '';
        this.wildcard = json['@wildcard'] ? json['@wildcard'] : 'both';
    },
    
    setParams: function(p) {
        if (p[this.parameter]) {
            this.value = p[this.parameter];
        } else {
            this.value = '';
        }
    },
    
    toString: function() {
        var value = this.value ? this.value : '';
        if (value == '' && !this.includeIfEmpty) {
            return '';
        }
        var prewildcard = '';
        var prewildcard = '';
        var postwildcard = '';
        if (this.operator == 'like') {
            if (this.wildcard == 'before' || this.wildcard == 'both') {
                prewildcard = '*';
            }
            if (this.wildcard == 'after' || this.wildcard == 'both') {
                postwildcard = '*';
            }
        }
        var wildcard = this.operator == 'like' ? '*' : '';
        return this.column + ' ' + this.operator + ' ' + this.quote + prewildcard + value + postwildcard + this.quote;
    }
};/**
 * @project         Fusion
 * @revision        $Id: MGBroker.js,v 1.10 2007/06/29 19:33:51 pspencer Exp $
 * @fileoverview    this file contains classes for communicating
 *                  with a MapGuide MapAgent
 * @author          Paul Spencer (pspencer@dmsolutions.ca)
 * @author          Zak James (zjames@dmsolutions.ca)
 * @author          Fred Warnock (fwarnock@dmsolutions.ca)
 * @copyright       &copy; 2006 DM Solutions Group Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of version 2.1 of the GNU Lesser
 * General Public License as published by the Free Software Foundation.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
 
/**
 * MGBroker is used to broker requests to the MapGuide Open Source
 * mapagent interface.  It is a very simple class that is configured
 * with a URL and credentials via the setSiteURL method and can
 * send requests to the server via the dispatchRequest method.
 */
Fusion.Lib.MGBroker = Class.create();
Fusion.Lib.MGBroker.prototype = {
    /**
     * the URL to a MapGuide Open Source installation.  Set this using
     * setSiteURL
     * @type String
     */
    mapGuideURL : '',
    /**
     * the agent URL for the MapGuide Open Source installation.  Set from
     * setSiteURL
     * @type String
     */
    mapAgentURL : '',
    
    /**
     * default method for communicating with the server, if set
     * this overrides the individual request methods
     */
    method: null,
    
    /**
     * @class
     * MGBroker constructor
     *
     * @constructor
     * create a new MGBroker instance
     */
    initialize : function() { 
    },
    /**
     * send a request to the MapGuide Open Source server using
     * XMLHttpRequest and return the result to the specified
     * function.
     * @param r {Object} an MGRequest-subclass instance that
     *        defines the operation to request.
     * @param f {Function} a function object to invoke when the
     *        XMLHttpRequest call completes
     */
    dispatchRequest : function( r, f ) {
        var s = r.encode() + '&ts='+(new Date()).getTime();
        if (this.method) {
            r.options.method = this.method;
        }
        var a = new Ajax.Request( this.mapAgentURL, 
                 Object.extend({ parameters:s, onComplete:f }, r.options ) );
        a.originalRequest = r;
    },
    /**
     * set up a connection to a MapGuide Open Source site.  This function
     * expects that url is in the form http(s)://<address>/path-to-mapguide.
     * Path-to-mapguide is should be the base URL to a MapGuide Open
     * Source install.  It is expected that the mapagent is
     * in the expected place (mapagent/mapagent.fcgi) under that URL.  If
     * (for some strange reason) its not, then you can include the full
     * path to mapagent.fcgi in the URL and this function won't try to
     * guess its location.
     * The user name and password are passed on using basic HTML
     * authentication (http://<user>:<pass>@<server>/path-to-mapguide).
     * @param url {String} a properly formatted universal reverse locator
     *        to a MapGuide Open Source installation.
     * @param user {String} a valid user name
     * @param pass {String} the password for the given user.
     */
    setSiteURL : function(url, user, pass) {
        //url = url.replace('://', '://'+user+':'+pass+'@');
        this.user = user;
        this.pass = pass;
        if (url.indexOf('mapagent.fcgi') == -1) {
            if (url.charAt(url.length - 1) != '/') {
                url = url + '/';
            }
            this.mapGuideURL = url;            
            url = url + 'mapagent/mapagent.fcgi';
        }
        this.mapAgentURL = url;
    },
    /**
     * remove all authentication information from the broker
     */
    clearSiteURL: function() {
        this.user = '';
        this.pass = '';
        this.mapGuideURL = '';
        this.mapAgentURL = '';
    }
};

/**
 * MGRequest is the base class for all broker-compatible requests.  A request
 * is a wrapper around an operation that is supported by the mapagent.
 */
Fusion.Lib.MGRequest = Class.create();
Fusion.Lib.MGRequest.prototype = {
    /**
     * core options shared by all requests
     */
    options : null,
    
    /**
     * core parameters shared by all requests
     */
    parameters : null,
    
    /**
     * @constructor
     * initialize a new instance of MGRequest
     */
    initializeRequest : function() {
        this.options = { method:'post' };
        this.parameters = { version : '1.0.0', locale : 'en' };
    },
    
    /**
     * set the parameters associated with this request.  Parameters are
     * dependent on the specific MGRequest subclass except for two
     * mandatory parameters, version and locale, that are provided by
     * this base class.
     *
     * @param o {Object} an object that contains named key : value pairs 
     * representing parameters to a request
     */
    setParams : function( o ){ Object.extend( this.parameters, (o || {}) ); },

    /**
     * set the options associated with this request
     * @param o {Object} an object that contains named key : value pairs 
     * representing for a request
     */
    setOptions : function( o ){ Object.extend( this.options, (o || {}) ); },
    
    /**
     * returns a string containing all the request parameters in URL form suitable
     * for appending to a URL.
     * @return {String} the parameters in URL form.
     */
    encode : function() {
        var s = sep = '';
        for (var p in this.parameters) {
            if (this.parameters[p]) {
                s = s + sep + p + '=' + encodeURI(this.parameters[p]);
            }
            sep = '&';
        }
        return s;
    }
};

Fusion.Lib.MGRequest.MGEnumerateResources = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGEnumerateResources.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 * @class MGEnumerateResources
 * encapsulate a request to the server to enumerate resources in the library.
 */
Object.extend(  Fusion.Lib.MGRequest.MGEnumerateResources.prototype, {
    /**
     * @constructor
     * initialize a new instance of MGEnumerateResources
     *
     * @param resourceID {String} optional parameter indicating the resource
     * to enumerate.  If not set or null, it defaults to "Library://" which
     * is the root of the library.
     *
     * @param type {String} optional parameter indicating the type of resources
     * to enumerate.  If not set, it will default to an empty string which
     * indicates all types will be returned.
     *
     * @param depth {Integer} optional parameter that controls the depth of the
     * resource tree to enumerate.  If not set, it will default to -1 which
     * means the tree will be fully enumerated.
     *
     * @return {Object} an instance of MGEnumerateResources
     */
    initialize : function( resourceID, type, depth ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'ENUMERATERESOURCES',
            resourceid : (resourceID || "Library://"),
            type : (type || ""),
            depth : (typeof depth == 'undefined' ? -1 : depth) } );
    }
});

Fusion.Lib.MGRequest.MGGetResourceContent = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGGetResourceContent.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to get resource contents from the library.
 */
Object.extend(  Fusion.Lib.MGRequest.MGGetResourceContent.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGGetResourceContent
     *
     * @param resourceID {String} optional parameter indicating the resource
     * to enumerate.  If not set or null, it defaults to "Library://" which
     * is the root of the library.
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGGetResourceContent
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'GETRESOURCECONTENT',
            resourceid : (resourceID || "Library://")
        } );
    }
});

Fusion.Lib.MGRequest.MGGetResourceHeader = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGGetResourceHeader.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to get resource header from the library.
 */
Object.extend(  Fusion.Lib.MGRequest.MGGetResourceHeader.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGGetResourceHeader
     *
     * @param resourceID {String} optional parameter indicating the resource
     * to enumerate.  If not set or null, it defaults to "Library://" which
     * is the root of the library.
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGGetResourceHeader
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'GETRESOURCEHEADER',
            resourceid : (resourceID || "Library://")
        } );
    }
});

Fusion.Lib.MGRequest.MGCreateSession = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGCreateSession.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to create a new session on the server.
 *
 */
Object.extend(  Fusion.Lib.MGRequest.MGCreateSession.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGCreateSession
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGCreateSession
     */
    initialize : function( ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'CREATESESSION'
        } );
    }
});

Fusion.Lib.MGRequest.MGCopyResource = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGCopyResource.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to copy a resource.
 *
 */
Object.extend(  Fusion.Lib.MGRequest.MGCopyResource.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGCopyResource
     *
     * @param sourceID {String} the Resource ID of the source
     * @param destinationID {String} the Resource ID of the destination
     * @param overwrite {Boolean} overwrite the destination if it exists
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGCopyResource
     */
    initialize : function( sourceID, destinationID, overwrite ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'COPYRESOURCE',
            source : sourceID,
            destination: destinationID,
            overwrite : overwrite
        } );
    }
});

Fusion.Lib.MGRequest.MGDeleteResource = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGDeleteResource.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to delete a resource.
 *
 */
Object.extend(  Fusion.Lib.MGRequest.MGDeleteResource.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGDeleteResource
     *
     * @param resourceID {String} the id of the resource to delete
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGDeleteResource
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'DELETERESOURCE',
            resourceid : resourceID
        } );
    }
});

Fusion.Lib.MGRequest.MGMoveResource = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGMoveResource.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to move a resource in the repository.
 *
 */
Object.extend(  Fusion.Lib.MGRequest.MGMoveResource.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGMoveResource
     *
     * @param sourceID {String} the Resource ID of the source
     * @param destinationID {String} the Resource ID of the destination
     * @param overwrite {Boolean} overwrite the destination if it exists
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGMoveResource
     */
    initialize : function( sourceID, destinationID, overwrite ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'MOVERESOURCE',
            source : sourceID,
            destination : destinationID,
            overwrite : overwrite
        } );
    }
});

Fusion.Lib.MGRequest.MGMoveResource = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGMoveResource.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to set the content XML of a resource.
 *
 */
Object.extend(  Fusion.Lib.MGRequest.MGMoveResource.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGMoveResource
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGMoveResource
     */
    initialize : function( resourceID, content, header ) {
        this.initializeRequest();
        this.setParams( {
            method: 'post', /* SetContent requires post method */
            operation : 'SETRESOURCE',
            resourceid : resourceID,
            content : content,
            header : header
        } );
    }
});

Fusion.Lib.MGRequest.MGDescribeSchema = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGDescribeSchema.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to describe the schema of a FeatureSource.
 *
 */
Object.extend(  Fusion.Lib.MGRequest.MGDescribeSchema.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGDescribeSchema
     *
     * @param resourceID {String} the id of the resource to describe the schema for
     * @param schema {String} what does this do?
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGDescribeSchema
     */
    initialize : function( resourceID, schema ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'DESCRIBEFEATURESCHEMA',
            resourceid : resourceID,
            schema : schema
        } );
    }
});

Fusion.Lib.MGRequest.MGGetSpatialContexts = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGGetSpatialContexts.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to retrieve the spatial context of a resource.
 *
 */
Object.extend(  Fusion.Lib.MGRequest.MGGetSpatialContexts.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGGetSpatialContexts
     *
     * @param resourceID {String} the id of the resource to retrieve the spatial context for
     * @param activeonly {Boolean} what does this do?
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGGetSpatialContexts
     */
    initialize : function(resourceID, activeonly) {
        this.initializeRequest();
        this.setParams( {
            operation : 'GETSPATIALCONTEXTS',
            resourceid : resourceID,
            activeonly : activeonly?'1':'0'
        } );
    }
});

Fusion.Lib.MGRequest.MGEnumerateResourceReferences = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGEnumerateResourceReferences.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to enumerate the references to a resource id.
 *
 */
Object.extend(  Fusion.Lib.MGRequest.MGEnumerateResourceReferences.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGEnumerateResourceReferences
     *
     * @param resourceID {String} the id of the resource to retrieve the spatial context for
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGEnumerateResourceReferences
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'ENUMERATERESOURCEREFERENCES',
            resourceid: resourceID
        } );
    }
});

Fusion.Lib.MGRequest.MGEnumerateResourceData = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGEnumerateResourceData.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to enumerate the data associated with
 * a FeatureSource
 * N.B. This does not enumerate resource data for 'unmanaged' FeatureSources
 *      (those referencing files or directories outside the respository)
 *      Fusion.Lib.MGRequest.MGDescribeSchema should be used for those sources.
 */
Object.extend(  Fusion.Lib.MGRequest.MGEnumerateResourceData.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGEnumerateResourceData
     *
     * @param resourceID {String} the id of the FeatureSource to retrieve data for
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGEnumerateResourceData
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'ENUMERATERESOURCEDATA',
            resourceid: resourceID
        } );
    }
});

Fusion.Lib.MGRequest.MGGetVisibleMapExtent = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGGetVisibleMapExtent.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to enumerate the data associated with
 * a FeatureSource
 * N.B. This does not enumerate resource data for 'unmanaged' FeatureSources
 *      (those referencing files or directories outside the respository)
 *      Fusion.Lib.MGRequest.MGDescribeSchema should be used for those sources.
 */
Object.extend(  Fusion.Lib.MGRequest.MGGetVisibleMapExtent.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGGetVisibleMapExtent
     *
     * @param sessionId {String} the id of the session to restore
     * @param mapName {String} the name of the map
     * @param viewCenterX {String} the horizontal center of the view
     * @param viewCenterY {String} the vertical center of the view
     * @param viewScale {String} the scale of the map
     * @param dataExtent {String} the extent of the data 
     * @param displayDpi {String} the DPI of the display
     * @param displayWidth {String} the width of the map
     * @param displayHeight {String} the height of the map
     * @param showLayers {String} a list of layer names to show
     * @param hideLayers {String} a list of layer names to hide
     * @param showGroups {String} a list of group names to show
     * @param hideGroups {String} a list of groupnames to hide
     * @param refreshLayers {String} a list of layers that need to be refreshed
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGGetVisibleMapExtent
     */
    initialize : function( sessionId, mapName, viewCenterX, viewCenterY,
                           viewScale, dataExtent, displayDpi, displayWidth, 
                           displayHeight, showLayers, hideLayers, 
                           showGroups, hideGroups, refreshLayers ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'GETVISIBLEMAPEXTENT',
            session: sessionId,
            mapname: mapName,
            setviewcenterx: viewCenterX,
            setviewcentery: viewCenterY,
            setviewscale: viewScale,
            setdataextent: dataExtent,
            setdisplaydpi: displayDpi,
            setdisplaywidth: displayWidth,
            setdisplayheight: displayHeight,
            showlayers: showLayers,
            hidelayers: hideLayers,
            showgroups: showGroups,
            hidegroups: hideGroups,
            refreshlayers: refreshLayers
        } );
    }
});


Fusion.Lib.MGRequest.MGQueryMapFeatures = Class.create();
Object.extend(Fusion.Lib.MGRequest.MGQueryMapFeatures.prototype, Fusion.Lib.MGRequest.prototype);
/**
 * @extends MGRequest
 *
 * encapsulate a request to the server to query map features on 
 * selectable layers
 */
Object.extend(  Fusion.Lib.MGRequest.MGQueryMapFeatures.prototype, {
    /**
     * @constructor
     * initialize a new instance of Fusion.Lib.MGRequest.MGQueryMapFeatures
     *
     * @param sessionId {String} the id of the session to restore
     * @param mapName {String} the id of the session to restore
     * @param geometry (sting wkt} gemetry to use for selection.  Example : POLYGON(x1 y1, x2,y2)
     * @param maxFeatures {integer} number of maximum results (-1 to indicate no maximum)
     * @param selectionPersist {boolean} save the selection (valid values are 0 and 1) 
     * @param selectionVariant {String} indicates the spatial operation. Valid values are 'INTERSECTS', ...
     *@param layerNames {String} comma separated list of layer names to include in the query
     *
     * @return {Object} an instance of Fusion.Lib.MGRequest.MGQueryMapFeatures
     */
    initialize : function( sessionId, mapName, geometry, maxFeatures, persist, selectionVariant, layerNames ) 
    {
        this.initializeRequest();
        this.setParams( {
            operation : 'QUERYMAPFEATURES',
            session: sessionId,
            mapname: mapName,
            geometry: geometry,
            maxFeatures: maxFeatures,
            persist: persist,
            selectionVariant: selectionVariant,
            layerNames: layerNames
        } );
    }
});
/*****************************************************************************
 * @project Fusion
 * @revision $Id: Widget.js,v 1.6 2007/06/29 19:56:30 pspencer Exp $
 * @purpose Base Class for all widgets
 * @author pspencer@dmsolutions.ca
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * ***************************************************************************
 *
 * This is the base class for all widgets.  It provides some basic
 * functionality that all widgets should need.
 *
 * ***************************************************************************/
 
Fusion.Event.WIDGET_STATE_CHANGED = Fusion.Event.lastEventId++;
 
Fusion.Widget = Class.create();
Fusion.Widget.prototype = {
    bIsMutuallyExclusive: null,
    sName: null,
    oMap: null,
    bEnabled: false,
    mapLoadedWatcher: null,
    groups: [],
    group: null,
    
    /**
     * intialize the widget
     * @param sName {string} the name of the widget
     */
    initialize: function(sName, bMutEx, oCommand) {
        this.bIsMutuallyExclusive = bMutEx;
        this.sName = sName;
        this._oCommand = oCommand;
        Object.inheritFrom(this, Fusion.Lib.EventMgr, []);
        this.registerEventID(Fusion.Event.WIDGET_STATE_CHANGED);
        
        var group = oCommand.jsonNode.Group ? oCommand.jsonNode.Group[0] : '';
        if (group != '') {
            if (!this.groups[group]) {
                this.groups[group] = [];
            }
            this.groups[group].push(this);
            this.group = group;
        }
    },
    /**
     * set the map object that this widget is associated with
     * @param oMap {Object} the map
     */
    setMap: function(oMap) {
        if (this.mapLoadedWatcher) {
            this.oMap.deregisterForEvent(Fusion.Event.MAP_LOADED, this.mapLoadedWatcher);
            this.mapLoadedWatcher = null;
        }
        
        this.oMap = oMap;
        if (oMap) {
            this.mapLoadedWatcher = this.mapLoaded.bind(this);
            oMap.registerForEvent(Fusion.Event.MAP_LOADED, this.mapLoadedWatcher);
        }
        
        if (oMap.isLoaded()) {
            this.enable();
        } else {
            this.disable();
        }
    },
    /**
     * accessor to get the Map object that this widget is associated with
     * @return {object} the map
     */
    getMap: function() {
        return this.oMap;
    },
    
    /**
     */
    mapLoaded: function() {
        if (this.oMap && this.oMap.isLoaded()) {
            //console.log('enable');
            this.enable();
        } else {
            //console.log('disable');
            this.disable();
        }
    },
    
    /** 
     * set whether this widget is mutually exclusive on its map
     * @param bIsMutEx {boolean} is the widget mutually exclusive?
     */
    setMutEx: function(bIsMutEx) {
        this.bIsMutuallyExclusive = bIsMutEx;
    },
    
    /**
     * accessor to determine if the widget should be activated mutually
     * exclusively from other widgets on the map.
     * @return {boolean} true if the widget is mutually exclusive
     */
    isMutEx: function() {
        return this.bIsMutuallyExclusive;
    },
    
    /**
     * accessor to return the name of the widget.  Mostly for debugging
     * @return {string} the name of the widget
     */
    getName: function() {
        return this.sName;
    },
    
    isEnabled: function() { return this.bEnabled; },
    
    enable: function() { this.bEnabled = true; this.triggerEvent(Fusion.Event.WIDGET_STATE_CHANGED, this);},

    disable: function() { this.bEnabled = false; this.triggerEvent(Fusion.Event.WIDGET_STATE_CHANGED, this);},

    setParameter : function(param, value){}
};
/********************************************************************** * 
 * @project Fusion
 * @revision $Id: ButtonBase.js,v 1.2 2007/06/26 16:22:32 pspencer Exp $
 * @purpose Base widget for button type widgets
 * @author yassefa@dmsolutions.ca
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 ********************************************************************
 *
 * Utility base class for a button type widget.
 *
 * Configurations for the buttons come from :
 * - html area : corresponds to the web layout command name
 * - width,height : default are 20,20. Classes should redifine teh
 *   getButtonWidth and getButtonHeight to provide othe values
 * - imageurl : image that will be used when the button is active.
 *   Default value is read from the web layout command imageurl parameter.
 *   Classes can redifine getImageURL.
 * - imageurl : image that will be used when the button is active.
 *   Default value is read from the web layout command disabledimageurl 
 *   parameter.  Classes can redifine getDisabledImageURL.
 * 
 * Clases inheriting should redefine the function activateTool which is
 * called when the button is clicked
 * **********************************************************************/

 
Fusion.Tool.ButtonBase = Class.create();
Fusion.Tool.ButtonBase.prototype = {
    /**
     * constructor
     * @param oCommand (object) map guide web layout command object
     */
    initialize : function() {
        /* overload enable/disable.  Normal inheritance should
         * work but because we use inheritFrom, it doesn't overload
         * Widget's enable/disable functions.  We do it manually
         * here.
         */
        this.enable = Fusion.Tool.ButtonBase.prototype.enable;
        this.disable = Fusion.Tool.ButtonBase.prototype.disable;

        //console.log('Fusion.Tool.ButtonBase.initialize');
        this._oDomObj = $(this._oCommand.getName());

        this._oButton = new Fusion.Tool.Button(this._oCommand);
        if (!this.isEnabled()) {
            this._oButton.disableTool();
        }
        this.clickWatcher = this.clickCB.bind(this);
        this._oButton.observeEvent('click', this.clickWatcher);
    },
    
    clickCB : function(e) {
        //console.log('Fusion.Tool.ButtonBase.clickCB');
        if (this.isEnabled()) {
            this.activateTool();
        }
        /* I put this in to prevent the context menu from activating tools twice but it doesn't seem to be needed now */
        /* Event.stop(e); */
        //remove the focus on the button to prevent a problem in IE with some
        //buttons retaining their background colour when they shouldn't
        this._oButton._oButton.domObj.blur();
    },

    activateTool :  function() {
        //console.log('Fusion.Tool.ButtonBase.activateTool');
        if (this.execute) {
            this.execute();
        }
        if (this.group) {
            this._oButton.activateTool();
            for (var i=0; i<this.groups[this.group].length; i++) {
                if (this.groups[this.group][i] != this) {
                    this.groups[this.group][i]._oButton.deactivateTool();
                }
            }
        }
    },
    
    enable: function() {
        //console.log('button base enable');
        Fusion.Widget.prototype.enable.apply(this,[]);
        this._oButton.enableTool();
    },
    
    disable: function() {
        //console.log('button base disable');
        Fusion.Widget.prototype.disable.apply(this,[]);
        this._oButton.disableTool();
    }
};
/********************************************************************** * 
 * @project Fusion
 * @revision $Id: ButtonTool.js,v 1.2 2007/06/26 16:22:32 pspencer Exp $
 * @purpose Utility class used by button type widgets
 * @author yassefa@dmsolutions.ca
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 ********************************************************************
 *
 * extended description
 *
 ***********************************************************************/
 
Fusion.Tool.Button = Class.create();
Fusion.Tool.Button.prototype = {
    _sHtmlId : null,
    _sImageURL : null,
    _sDisabledImageURL : null,
    _nWidth : -1,
    _nHeight : -1,
    _oDomObj : null,
    _oDomImg : null,
    bActive: null,
    sActiveClass: null,
    sDisabledClass: null,
  
    initialize : function(oCommand)  {
        //console.log('Fusion.Tool.Button.initialize');
        var json = oCommand.jsonNode;
        this._sHtmlId = oCommand.getName();
        this._sDisabledImageURL = json.DisabledImageURL ? json.DisabledImageURL[0]: '';
        this._sImageURL = json.ImageURL ? json.ImageURL[0] : '';
        
        if (this._sDisabledImageURL == '' && this._sImageURL != '') {
            this._sDisabledImageURL = this._sImageURL;
        } else if (this._sImageURL == '' && this._sDisabledImageURL != '') {
            this._sImageURL = this._sDisabledImageURL;
        }
        this._nWidth = json.Width ? parseInt(json.Width[0]) : null;
        this._nHeight = json.Height ? parseInt(json.Height[0]) : null;

        this._sTooltip = json.Tooltip ? json.Tooltip[0] : '';
        this._sDescription = json.Description ? json.Description[0] : '';
        this._sLabel = json.Label ? json.Label[0] : '';
        
        this.sImageClass = json.ImageClass ? json.ImageClass[0] : '';
        this.sActiveClass = json.ActiveClass ? json.ActiveClass[0] : 'jxButtonActive';
        
        this.sDisabledClass = json.DisabledClass ? json.DisabledClass[0] : 'jxButtonDisabled';
        
        this.buttonAction = new Jx.Action(this.buttonClicked.bind(this));
        
        if (oCommand.getName() != '') {
            this._oDomObj = $(this._sHtmlId);
        }
        
        
        if (this._oDomObj) {
            var options = {};
            options.imgPath = this._sDisabledImageURL;
            options.imgClass = this.sImageClass;
            options.tooltip = this._sTooltip;
            options.label = this._sLabel;
            options.disabledClass = this.sDisabledClass;
            this._oButton = new Jx.Button(this.buttonAction, options);
            this._oDomObj.appendChild(this._oButton.domObj);
        }
        
        this.bActive = false;
        this.bEnabled = true;
        
        var startDisabled = json.Disabled ? json.Disabled[0] : 'false';
        if (startDisabled == 'true' || startDisabled == '1') {
            this.disableTool();
        }
    },
    
    buttonClicked: function() {
        
    },

    observeEvent : function(sEvent, fnCB) {
        if (this._oButton) {
             Event.observe(this._oButton.domObj, sEvent, fnCB);
        }
    },
    
    stopObserveEvent : function(sEvent, fnCB) {
        if (this._oButton) {
             Event.stopObserving(this._oButton.domObj, sEvent, fnCB);
        }
    },
    
    enableTool : function() {
        this.buttonAction.setEnabled(true);
    },

    disableTool : function() {
        this.buttonAction.setEnabled(false);
    },

    activateTool : function() {
        //console.log('Fusion.Tool.Button.activateTool');
        this.bActive = true;
        if (!this._oButton) {
            return;
        }
        if (this._sImageURL != null && this._sImageURL.length > 0) {
            this._oButton.setImage(this._sImageURL);
        }
        if (this.sActiveClass != '') {
            Element.addClassName(this._oButton.domA, this.sActiveClass);
        }
    },

    deactivateTool :  function()
    {
        //console.log('Fusion.Tool.Button.deactivateTool');
        this.bActive = false;
        if (!this._oButton) {
            return;
        }
        
        if (this._sDisabledImageURL != null && this._sDisabledImageURL.length > 0) {
            this._oButton.setImage(this._sDisabledImageURL);
        }
        
        if (this.sActiveClass != '') {
            Element.removeClassName(this._oButton.domA, this.sActiveClass);
        }
    },

    clickCB : function(e) { this.activateTool(); },
    isActive: function() {return this.bActive;}
};
/********************************************************************** * 
 * @project Fusion
 * @revision $Id: CanvasTool.js,v 1.5 2007/07/10 21:16:52 pspencer Exp $
 * @purpose Base class for widgets that want to use canvas functions
 * @author pspencer@dmsolutions.ca
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * ********************************************************************
 *
 * 
 * **********************************************************************/

Fusion.Tool.Canvas = Class.create();
Fusion.Tool.Canvas.prototype = 
{
    context: null,
    canvas: null,
    width: null,
    height: null,
    
    initialize : function()
    {
        this.context = null;
        this.canvas = null;
        this.width = null;
        this.height = null;
        
        this.mouseMoveCB = this.mouseMove.bindAsEventListener(this);
        this.mouseUpCB = this.mouseUp.bindAsEventListener(this);
        this.mouseDownCB = this.mouseDown.bindAsEventListener(this);
        this.dblClickCB = this.dblClick.bindAsEventListener(this);
        
        this.resizeCanvasFn = this.resizeCanvas.bind(this);
    },
    
    /**
     * (public) clearContext()
     *
     * wipe the slate clean
     */
    clearContext: function() {
        //console.log('Fusion.Tool.Canvas.clearContext');
        if (this.context) {
            this.context.clearRect(0,0,this.width,this.height);
        }
    },

    activateCanvas: function() {
        var map = this.getMap();
        map.registerForEvent(Fusion.Event.MAP_RESIZED, this.resizeCanvasFn);
        var domObj = map.getDomObj();
        
        var size = Element.getDimensions(domObj);
        this.width = size.width;
        this.height = size.height;
        
        /* create dynamic canvas */
        if (!this.canvas) {
            this.canvas = document.createElement('canvas');
            
            // we need to init this for IE 
            if (typeof G_vmlCanvasManager != "undefined") { 
                document.getElementsByTagName('BODY')[0].appendChild(this.canvas);
                G_vmlCanvasManager.initElement(this.canvas); 
                this.canvas = document.getElementsByTagName('BODY')[0].lastChild;
            } 
            
            this.canvas.id = 'featureDigitizer';
            this.canvas.style.position = 'absolute';
            this.canvas.style.top = '0px';
            this.canvas.style.left = '0px';
            this.canvas.style.width = this.width+'px';
            this.canvas.style.height = this.height+'px';
            this.canvas.width = this.width;
            this.canvas.height = this.height;
            this.canvas.style.zIndex = 99;
            
        }
    
        domObj.appendChild(this.canvas);
        if (!this.context) {
            this.context = this.canvas.getContext('2d');
        }
        this.canvas.style.width = this.width+'px';
        this.canvas.style.height = this.height+'px';
        this.canvas.width = this.width;
        this.canvas.height = this.height;
        
        map.observeEvent('mousemove', this.mouseMoveCB);
        map.observeEvent('mouseup', this.mouseUpCB);
        map.observeEvent('mousedown', this.mouseDownCB);
        map.observeEvent('dblclick', this.dblClickCB);
    },
    
    resizeCanvas: function() {
        var map = this.getMap();
        var domObj = map.getDomObj();
        var size = Element.getDimensions(domObj);
        this.width = size.width;
        this.height = size.height;
        this.canvas.style.width = this.width+'px';
        this.canvas.style.height = this.height+'px';
        this.canvas.width = this.width;
        this.canvas.height = this.height;        
    },
    
    /**
     * (public) deactivate()
     *
     * deactivate the line digitizing tool
     */
    deactivateCanvas: function() {
        //console.log('Fusion.Tool.Canvas.deactivate');
        var map = this.getMap();
        map.deregisterForEvent(Fusion.Event.MAP_RESIZED, this.resizeCanvasFn);
        map.getDomObj().removeChild(this.canvas);
        this.context.clearRect(0,0,this.width,this.height);
        map.stopObserveEvent('mousemove', this.mouseMoveCB);
        map.stopObserveEvent('mouseup', this.mouseUpCB);
        map.stopObserveEvent('mousedown', this.mouseDownCB);
        map.stopObserveEvent('dblclick', this.dblClickCB);
    },
    
    /**
     * (public) mouseDown(e)
     *
     * handle the mouse down event
     *
     * @param e Event the event that happened on the mapObj
     */
    mouseDown: function(e) { },

    /**
     * (public) mouseUp(e)
     *
     * handle the mouse up event
     *
     * @param e Event the event that happened on the mapObj
     */
    mouseUp: function(e) { },

    /**
     * (public) mouseMove(e)
     *
     * handle the mouse move event
     *
     * @param e Event the event that happened on the mapObj
     */
    mouseMove: function(e) { },

    /**
     * (public) dblClick(e)
     *
     * handle the mouse dblclick event
     *
     * @param e Event the event that happened on the mapObj
     */
    dblClick: function(e) { }
};

Fusion.Tool.Canvas.Point = Class.create();
Fusion.Tool.Canvas.Point.prototype = {
    center: null,
    radius: null,
    lineStyle: null,
    fillStyle: null,
    
    initialize: function(map) {
        this.center = new Fusion.Tool.Canvas.Node(0,0, map);
        this.radius = 5;
        this.lineStyle = new Fusion.Tool.Canvas.Style({lineWidth:2,strokeStyle:'rgba(0,0,0,1.0)'});
        this.fillStyle = new Fusion.Tool.Canvas.Style({fillStyle:'rgba(0,0,255, 0.5)'});
        this.segments = [];
        
    },
    
    setPoint: function(x,y) {
        this.center.set(x,y);
    },
    
    getPoint: function() {
        this.center.updateGeo();
        return {x:this.center.x, y:this.center.y};
    },
    
    draw: function( context ) {
        var x = this.center.px;
        var y = this.center.py;
        var radius = this.radius;
        
        this.fillStyle.apply(context);
        this.lineStyle.apply(context);
        
        context.beginPath();
        context.arc(x,y,radius, 0, 2*Math.PI, 1);
        context.closePath();
        context.fill(); 
        context.stroke();
        
    },

    getNodes: function() {
        return [this.center];
    },

    clean: function() {},
    
    updateGeo: function() {
        this.center.updateGeo();
    },
    
    updatePx: function() {
        this.center.updatePx();
    }
};

Fusion.Tool.Canvas.Circle = Class.create();
Fusion.Tool.Canvas.Circle.prototype = {
    map: null,
    center: null,
    radius: null,
    radiusPx: null,
    start: null,
    end: null,
    lineStyle: null,
    fillStyle: null,
    
    initialize: function(map) {
        this.map = map;
        this.center = new Fusion.Tool.Canvas.Node(0,0, map);
        this.radius = 1;
        this.lineStyle = new Fusion.Tool.Canvas.Style({lineWidth:2,strokeStyle:'rgba(0,0,0,1.0)'});
        this.fillStyle = new Fusion.Tool.Canvas.Style({fillStyle:'rgba(0,0,255, 0.5)'});
        this.segments = [];
    },
    
    setCenter: function(x,y) {
        this.center.set(x,y);
    },
    
    setRadius: function(r) {
        if (r > 1 || r < -1) {
            this.radius = Math.abs(r);
            this.radiusPx = this.map.geoToPixMeasure(this.radius);
        } else {
            this.radius = 1;
            this.radiusPx = 1;
        }
    },
    
    draw: function( context ) {
        var x = this.center.px;
        var y = this.center.py;
        var radius = this.radiusPx;
        this.fillStyle.apply(context);
        this.lineStyle.apply(context);
        
        context.beginPath();
        if (this.start && this.end) {
            context.moveTo(x,y);
            var s = this.start;
            var e = this.end;
            if (s < e) {
                var t = s;
                s = e;
                e = t;
            }
            var sx = x + Math.sin(s) * radius;
            var sy = y - Math.cos(s) * radius;
            context.lineTo(sx,sy);
            context.arc(x,y,radius, s - Math.PI/2, e - Math.PI/2, 1);
            context.lineTo(x, y);
        } else {
            context.arc(x,y,radius, 0, 2*Math.PI, 1);
        }
        context.closePath();
        context.fill(); 
        context.stroke();        
    },
    
    getNodes: function() {
        return [this.center];
    },
    
    clean: function() {},
    
    updateGeo: function() {
        this.center.updateGeo();
        this.radius = this.map.pixToGeoMeasure(this.radiusPx);
    },
    
    updatePx: function() {
        this.center.updatePx();
        this.radiusPx = this.map.geoToPixMeasure(this.radius);
    }
};

Fusion.Tool.Canvas.Polygon = Class.create();
Fusion.Tool.Canvas.Polygon.prototype = {
    segments: null,
    lineStyle: null,
    fillStyle: null,
    map: null,
    
    initialize: function(map) {
        this.map = map;
        this.segments = [];
        this.lineStyle = new Fusion.Tool.Canvas.Style({lineWidth:2,strokeStyle:'rgba(0,0,0,1.0)'});
        this.fillStyle = new Fusion.Tool.Canvas.Style({fillStyle:'rgba(0,0,255, 0.5)'});
    },

    clean: function() {
        var nodes = this.getNodes();
        this.segments = [];
        var n1 = nodes[0];
        //console.log('n1: '+ n1);
        var n2 = nodes[1];
        for (var i=1; i<nodes.length;i++) {
            if (n1.x != n2.x || n1.y != n2.y) {
                this.addSegment(new Fusion.Tool.Canvas.Segment(n1,n2));
                //console.log('n2: '+ n2);
                n1 = n2;
            }
            n2 = nodes[i];
        }
        
        this.addSegment(new Fusion.Tool.Canvas.Segment(n1, nodes[0]));
        //console.log(this);
    },

    getNodes: function() {
        var nodes = [];
        nodes.push(this.segments[0].from);
        for (var i=0; i<this.segments.length; i++) {
            nodes.push(this.segments[i].to);
        }
        return nodes;
    },

    /*
     * reverse the nodes in the feature
     * and adjust segments
     */
    reverseNodes: function() {
        var nSegments = this.segments.length;
        if (!nSegments) {
            return;
        }
        //flip nodes on each segment
        for (var i=0; i < nSegments; i++) {
            var seg = this.segments[i];
            var tmp = seg.from;
            seg.from = seg.to;
            seg.to = tmp;
        };
        //reverse segment order
        this.segments.reverse();
    },
    
    /*
     * remove node from the nodes in this feature
     * and adjust segments
     */
    removeNode: function(node) {
        //end cases
        if (node == this.segments[0].from) {
            this.segments[0].from = null;
            this.segments.shift();
            this.segments[0].from = this.segments[this.segments.length - 1].to;
            return;
        }
        if (node == this.segments[this.segments.length -1].from) {
            this.segments[this.segments.length -1].from = null;
            this.segments.pop();
            this.segments[0].from = this.segments[this.segments.length - 1].to;
            return;
        }
        //general case
        for (var i=1; i < this.segments.length; i++) {
            if (node == this.segments[i].from){
                this.segments[i-1].to = this.segments[i].to;
                this.segments[i].from = null;
                this.segments.splice(i, 1);
                return;
            }
        };
        
    },
    
    draw: function( context ) {
        var x = this.segments[0].from.px;
        var y = this.segments[0].from.py;
        if (this.segments.length > 2) {
            /* draw closing line and fill */
        
            this.fillStyle.apply(context);
            context.beginPath();
            context.moveTo(x,y);
            for (var i=0; i<this.segments.length; i++) {
                var s = this.segments[i];
                context.lineTo(s.to.px, s.to.py);         
            }
            context.lineTo(x,y); //closing line
            context.closePath();
            context.fill(); 
        }
        /* draw outline */
        for (var i=0; i<this.segments.length; i++) {
            this.segments[i].draw(context);
        }
    
        var last = this.lastSegment();
        context.beginPath();
        context.moveTo(last.to.px,last.to.py);
        context.lineTo(x,y);
        context.stroke();
    },

    addSegment: function( s ) {
        s.normalStyle = this.lineStyle;
        this.segments[this.segments.length] = s;
        //console.log('add segment ' + s);
    },

    lastSegment: function() {
        return this.segments[this.segments.length-1];
    },

    /* find the segment with the given node as its end
     * @param Object node - the node at the end
     * @param Int tolerance - an optional tolerance in pixels
     * @return the segment or null if nothing is found.
     */
     segmentTo: function(node) {
         var margin = arguments.length > 1?arguments[1]:3;
         for (var i=0; i<this.segments.length; i++) {
             if (this.segments[i].hasTo(node, margin)) {
                 return this.segments[i];
             }
         }
         return null;        
     },

    /* find the segment with the given node as its start
     * @param Object node - the node at the start
     * @param Int tolerance - an optional tolerance in pixels
     * @return the segment or null if there is none.
     */
     segmentFrom: function(node) {
         var margin = arguments.length > 1?arguments[1]:3;
         for (var i=0; i<this.segments.length; i++) {
             if (this.segments[i].hasFrom(node, margin)) {
                 return this.segments[i];
             }
         }
         return null;        
     },

    /* extend an existing line by creating a new segment attached
     * to the last segment
     * @return the new segment
     */
    extendLine: function() {
        var last = this.lastSegment();
        var newNode = new Fusion.Tool.Canvas.Node(last.to.x, last.to.y, this.map);
        var newSegment = new Fusion.Tool.Canvas.Segment( last.to, newNode );
        this.addSegment(newSegment);
        return newSegment;  
    },

    /* determine if the passed pixel coordinate is within this feature
     * @param point Object - {px,py} representation of point
     * @return true if the point is contained
     *
     * uses crossing test (Jordan Curve Theorem) algorithm discussed at
     * http://www.acm.org/tog/editors/erich/ptinpoly/
     */
    contains: function(node) {
        return true;  
    },
    
    
    toString: function() {
        var szFeature = this.segments[0].from.toString();
        for (var i=0; i < this.segments.length; i++) {
            szFeature += ',' + this.segments[i].to.toString();
        }
        return 'POLYGON(' + szFeature + ')';
    },
    
    updateGeo: function() {
        for (var i=0; i < this.segments.length; i++) {
            this.segments[i].updateGeo();
        }
    },
    
    updatePx: function() {
        for (var i=0; i < this.segments.length; i++) {
            this.segments[i].updatePx();
        }
    }
};

Fusion.Tool.Canvas.Line = Class.create();
Fusion.Tool.Canvas.Line.prototype = {
    segments: null,
    lineStyle: null,
    map: null,
    
    initialize: function(map) {
        this.map = map;
        this.segments = [];
        this.lineStyle = new Fusion.Tool.Canvas.Style({strokeStyle:'rgba(0,0,0,1.0)'});
    },

    clean: function() {
        var nodes = this.getNodes();
        this.segments = [];
        var n1 = nodes[0];
        var n2 = nodes[1];
        for (var i=1; i<nodes.length;i++) {
            //console.log('n1: '+ n1);
            //console.log('n2: '+ n2);
            n2 = nodes[i];
            if (n1.x != n2.x || n1.y != n2.y) {
                this.addSegment(new Fusion.Tool.Canvas.Segment(n1,n2));
                n1 = n2;
            }
        }
        //console.log(this);
    },

    getNodes: function() {
        var nodes = [];
        nodes.push(this.segments[0].from);
        for (var i=0; i<this.segments.length; i++) {
            nodes.push(this.segments[i].to);
        }
        return nodes;
    },

    /*
     * reverse the nodes in the feature
     * and adjust segments
     */
    reverseNodes: function() {
        var nSegments = this.segments.length;
        if (!nSegments) {
            return;
        }
        //flip nodes on each segment
        for (var i=0; i < nSegments; i++) {
            var seg = this.segments[i];
            var tmp = seg.from;
            seg.from = seg.to;
            seg.to = tmp;
        };
        //reverse segment order
        this.segments.reverse();
    },
    
    /*
     * remove node from the nodes in this feature
     * and adjust segments
     */
    removeNode: function(node) {
        //end cases
        if (node == this.segments[0].from) {
            this.segments[0].from = null;
            this.segments.shift();
            return;
        }
        if (node == this.segments[this.segments.length -1].from) {
            this.segments[this.segments.length -1].from = null;
            this.segments.pop();
            return;
        }
        //general case
        for (var i=1; i < this.segments.length; i++) {
            if (node == this.segments[i].from){
                this.segments[i-1].to = this.segments[i].to;
                this.segments[i].from = null;
                this.segments.splice(i, 1);
                return;
            }
        };
        
    },

    draw: function( context ) {
        for (var i=0; i<this.segments.length; i++) {
            this.segments[i].draw(context);
        }
    },

    addSegment: function( s ) {
        s.normalStyle = this.lineStyle;
        this.segments[this.segments.length] = s;
    },

    lastSegment: function() {
        return this.segments[this.segments.length-1];
    },

    /* find the segment with the given node as its end
     * @param Object node - the node at the end
     * @param Int tolerance - an optional tolerance in pixels
     * @return the segment or null if nothing is found.
     */
     segmentTo: function(node) {
         var margin = arguments.length > 1?arguments[1]:3;
         for (var i=0; i<this.segments.length; i++) {
             if (this.segments[i].hasTo(node, margin)) {
                 return this.segments[i];
             }
         }
         return null;        
     },

    /* find the segment with the given node as its start
     * @param Object node - the node at the start
     * @param Int tolerance - an optional tolerance in pixels
     * @return the segment or null if there is none.
     */
     segmentFrom: function(node) {
         var margin = arguments.length > 1?arguments[1]:3;
         for (var i=0; i<this.segments.length; i++) {
             if (this.segments[i].hasFrom(node, margin)) {
                 return this.segments[i];
             }
         }
         return null;        
     },

    /* extend an existing line by creating a new segment attached
     * to the last segment
     * @return the new segment
     */
    extendLine: function() {
        var last = this.lastSegment();
        var newNode = new Fusion.Tool.Canvas.Node(last.to.x, last.to.y, this.map);
        var newSegment = new Fusion.Tool.Canvas.Segment( last.to, newNode );
        this.addSegment(newSegment);
        return newSegment;  
    },
    
    updateGeo: function() {
        for (var i=0; i < this.segments.length; i++) {
            this.segments[i].updateGeo();
        }
    },
    
    updatePx: function() {
        for (var i=0; i < this.segments.length; i++) {
            this.segments[i].updatePx();
        }
    }
};

Fusion.Tool.Canvas.Segment = Class.create();
Fusion.Tool.Canvas.Segment.prototype = {
    from: null,
    to: null,
    
    initialize: function(from, to) {
        this.from = from;
        this.to = to;
        this.isEditing = false;
        this.normalStyle = new Fusion.Tool.Canvas.Style({lineWidth:1, strokeStyle:'rgba(0,0,0,1.0)'});
        this.editStyle = new Fusion.Tool.Canvas.Style({lineWidth:1, strokeStyle:'rgba(255,0,0,1.0)'});
    },

    /* returns true if the node is at the end of this segment
     * within the given margin
     * @return Bool true if found within margin, false otherwise
     */
    hasTo: function(node, margin) {
        return this.to.near({x:node.px, y:node.py}, margin);
    },

    /* returns true if the node is at the start of this segment
     * within the given margin
     * @return Bool true if found within margin, false otherwise
     */
    hasFrom: function(node, margin) {
        return this.from.near({x:node.px, y:node.py}, margin);
    },
    
    /* returns true if the given point falls along this segment
     * within the given margin
     * @return Bool true if found within margin, false otherwise
     */
    intersectsPoint: function(point, margin){
        //check bbox
        var minX = Math.min(this.to.px, this.from.px);
        var maxX = Math.max(this.to.px, this.from.px);
        if (point.x > maxX || point.x < minX){return false;};
        var maxY = Math.max(this.to.py, this.from.py);
        var minY = Math.min(this.to.py, this.from.py);
        if (point.y < minY || point.y > maxY){return false;};
        
        //determine slope
        var slope = parseFloat((maxY-minY))/(maxX-minX);
        var segY = slope * (point.x - minX) + minY;
        return (segY - margin < point.y && segY + margin > point.y);

    },
    
    setNormalStyle: function( style ) {
        this.normalStyle = style;
    },

    setEditStyle: function( style ) {
        this.editStyle = style;
    },

    draw: function( context ) {
        /* set up correct style */
        if (this.isEditing) {
            this.editStyle.apply(context);
        } else {
            this.normalStyle.apply(context);
        }
    
        /* draw segment */
        context.beginPath();
        context.moveTo(this.from.px, this.from.py);
        context.lineTo(this.to.px, this.to.py);
        context.closePath();
        context.stroke();
    
        /* draw nodes if editing */
        if (this.isEditing) {
            this.from.draw( context );
            this.to.draw( context );
        }
    },

    /* changes rendering style */
    setEditing: function(bEditing) {
        this.isEditing = bEditing;
    },
    
    toString: function() {
        return this.from.toString() + ', '+ this.to.toString();
    },
    
    updateGeo: function() {
        this.from.updateGeo();
        this.to.updateGeo();
    },
    
    updatePx: function() {
        this.from.updatePx();
        this.to.updatePx();
    }
};

Fusion.Tool.Canvas.Node = Class.create();
Fusion.Tool.Canvas.Node.prototype = {
    x: null,
    y: null,
    px: null,
    py: null,
    uid: null,
    map: null,
    counter: [0],
    isSelected: false,
    
    initialize: function(x,y, map) {
        this.map = map;
        this.set(x,y);
        var p = map.geoToPix(x, y);
        this.setPx(p.x, p.y);
        this.radius = 3;
        this.uid = this.counter[0];
        this.counter[0]++;
        this.normalStyle = new Fusion.Tool.Canvas.Style({lineWidth:1, strokeStyle:'rgba(0,0,0,1.0)'});
        this.selectedStyle = new Fusion.Tool.Canvas.Style({lineWidth:1, fillStyle:'rgba(255,0,0,1.0)',
                                                strokeStyle:'rgba(255,0,0,1.0)'});
    },

    set: function(x,y) {
        this.x = x;
        this.y = y;
        //update px position
        var p = this.map.geoToPix(x, y);
        this.setPx(p.x, p.y);
    },
    
    setPx: function(px, py) {
        this.px = px;
        this.py = py;
    },
    
    updateGeo: function() {
        if (!this.px || !this.py) {return;};
        var g = this.map.pixToGeo(this.px, this.py);
        this.set(g.x, g.y);
    },
    
    updatePx: function() {
        if (!this.x || !this.y) {return;};
        var p = this.map.geoToPix(this.x, this.y);
        this.setPx(p.x, p.y);
    },
    
    /* returns true if the supplied pixel position is
     * within the given tolerance
     * @return Bool true if found within margin, false otherwise
     */
     /*TODO: uses a square envelope for speed but could use radius
      *TODO: should support geographic tolerance
      */
    near: function(point, tolerance) {
        var minX = point.x - tolerance;
        var maxX = point.x + tolerance;
        var maxY = point.y + tolerance;
        var minY = point.y - tolerance;
        return ((this.px > minX && this.px < maxX) && (this.py > minY && this.py < maxY))?true:false;
    },

    /* returns true if this node is
     * within the given bbox
     * @param Array bbox - array of pixel coordinates to search within
     * @return Bool true if found within, false otherwise
     */
    within: function(bbox) {
        //TODO: handle > 2 coord pairs
        var minX = Math.min(bbox[0], bbox[2]);
        var maxX = Math.max(bbox[0], bbox[2]);
        var minY = Math.min(bbox[1], bbox[3]);
        var maxY = Math.max(bbox[1], bbox[3]);
        return ((this.px > minX && this.px < maxX) && (this.py > minY && this.py < maxY))?true:false;
    },

    /* draw a node on a canvas. */
    draw: function( context ) {
        /* set up correct style */
        if (this.isSelected) {
            this.selectedStyle.apply(context);
        } else {
            this.normalStyle.apply(context);
        }

        context.beginPath();
        context.arc(this.px, this.py, this.radius, 0, 2*Math.PI,1);
        context.closePath();
        context.stroke();
        if(this.isSelected){
            context.fill();
        };
    },
    
    /* changes rendering style */
    setSelected: function(bSelected) {
        this.isSelected = bSelected;
    },

    toString: function() {
        return '('+this.uid+') '+ this.x + ' ['+this.px+'px] '+ this.y+ ' ['+this.py+'px] ';
    }
};

/* encapsulate a context style */
Fusion.Tool.Canvas.Style = Class.create();
Fusion.Tool.Canvas.Style.prototype = {
    properties: ['fillStyle',
                 'globalAlpha',
                 'globalCompositeOperation',
                 'lineCap',
                 'lineJoin',
                 'lineWidth',
                 'miterLimit',
                 'shadowBlur',
                 'shadowColor',
                 'shadowOffsetX',
                 'shadowOffsetY',
                 'strokeStyle'],
    
    initialize: function( o ) { 
        for (var i=0; i<this.properties.length; i++) {
            var p = this.properties[i];
            this[p] = o[p] ? o[p]:null;
        }
    },

    set: function( p, v ) {
        this[p] = v;
    },

    apply: function(context) {
        for (var i=0; i<this.properties.length; i++) {
            var p = this.properties[i];
            if (this[p]) {
                context[p] = this[p];
            }
        }
    }
};/********************************************************************** * 
 * @project Fusion
 * @revision $Id: ClickTool.js,v 1.2 2007/06/26 16:22:32 pspencer Exp $
 * @purpose Utility class to do a click on the map
 * @author yassefa@dmsolutions.ca
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 ********************************************************************
 *
 * Utility class to manage a click on the map (mouse up event).
 *
 * All classes using this should redefine the execute function
 * **********************************************************************/
Fusion.Tool.Click = Class.create();
Fusion.Tool.Click.prototype =
{
    /**
     * constructor
     * @param oMap {Object} a map widget
     */
    initialize : function()
    {
        this.mouseUpCB = this.mouseUp.bindAsEventListener(this);
    },

    execute : function(x,y)
    {
    },

    activateClickTool : function()
    {
        //console.log('Fusion.Tool.Click.activateClickTool');
        if (this.oMap) {
            this.oMap.observeEvent('mouseup', this.mouseUpCB);
        }
    },

    deactivateClickTool : function()
    {
        //console.log('Fusion.Tool.Click.deactivateClickTool');
        if (this.oMap) {
            this.oMap.stopObserveEvent('mouseup', this.mouseUpCB);
        }
    },


    mouseUp : function(e)
    {
        //console.log('Fusion.Tool.Click.mouseUp');
        if (Event.isLeftClick(e)) {
            var sPixPoint = this.oMap.getEventPosition(e);
            this.execute(sPixPoint.x, sPixPoint.y);
        }
    }
};
/********************************************************************** * 
 * @project Fusion
 * @revision $Id: RectTool.js,v 1.2 2007/06/26 16:22:32 pspencer Exp $
 * @purpose Utility class to do a rect on the map
 * @author yassefa@dmsolutions.ca
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 ********************************************************************
 *
 * Utility class to draw a rectangle on the map and return the coordinates
 * through the doAction function.
 * All classes should redifine the doAction function
 * **********************************************************************/
Fusion.Tool.Rectangle = Class.create();
Fusion.Tool.Rectangle.prototype = {
    oRectDiv : null,
    _sStartPos : null,
    _bIsActive : null,

    /**
     * constructor
     * @param oMap {Object} a map widget
     */
    initialize : function() {
        //console.log('Fusion.Tool.Rectangle.initialize');
        this.oRectDiv = document.createElement('div');
        this.oRectDiv.style.position = 'absolute';
        this.oRectDiv.style.border = '1px solid red';
        this.oRectDiv.style.top = '0px';
        this.oRectDiv.style.left = '0px';
        this.oRectDiv.style.width = '1px';
        this.oRectDiv.style.height = '1px';
        this.oRectDiv.style.visibility = 'hidden';
        this.oRectDiv.style.lineHeight = '1px'; //for IE
        this.oRectDiv.style.zIndex = 99;

        this._bIsActive = false;

        this.mouseMoveCB = this.mouseMove.bindAsEventListener(this);
        this.mouseUpCB = this.mouseUp.bindAsEventListener(this);
        this.mouseDownCB = this.mouseDown.bindAsEventListener(this);
        this.mouseOutCB = this.mouseOut.bindAsEventListener(this);
    },

    execute : function(l,b,r,t) {},

    activateRectTool : function() {
        //console.log('Fusion.Tool.Rectangle.activateRectTool');
        if (this._bIsActive) {
            //console.log('tool is already active');
            return;
        }
        if (this.oMap) {
            this.oRectDiv.style.left = '0px';
            this.oRectDiv.style.top = '0px';
            this.oRectDiv.style.width = '1px';
            this.oRectDiv.style.height = '1px';
            this.oRectDiv.style.visibility = 'hidden';
            this._bIsActive = true;
            var oDomElem =  this.oMap.getDomObj();
            oDomElem.appendChild(this.oRectDiv);
            this.oMap.observeEvent('mousemove', this.mouseMoveCB);
            this.oMap.observeEvent('mouseup', this.mouseUpCB);
            this.oMap.observeEvent('mousedown', this.mouseDownCB);
        }
    },

    deactivateRectTool : function() {
        this._sStartPos = null;
        //console.log('Fusion.Tool.Rectangle.deactivateRectTool');
        if (!this._bIsActive) {
            //console.log('tool is not active');
            return;
        }
        this._bIsActive = false;
        var oDomElem =  this.oMap.getDomObj();
        oDomElem.removeChild(this.oRectDiv);
        this.oMap.stopObserveEvent('mousemove', this.mouseMoveCB);
        this.oMap.stopObserveEvent('mouseup', this.mouseUpCB);
        this.oMap.stopObserveEvent('mousedown', this.mouseDownCB);
    },

    mouseDown : function (e) {
        if (Event.isLeftClick(e)) {
            var p = this.oMap.getEventPosition(e);
            this._sStartPos = p;

            this.oRectDiv.style.left = p.x + 'px';
            this.oRectDiv.style.top = p.y + 'px';
            this.oRectDiv.style.width = '1px';
            this.oRectDiv.style.height = '1px';
            this.oRectDiv.style.visibility = 'visible';
            Event.observe(document, 'mouseout', this.mouseOutCB);
        }
        Event.stop(e);
    },

    mouseUp : function(e) {
        if (this._sStartPos != null) {
            var t = parseInt(this.oRectDiv.style.top);
            var l = parseInt(this.oRectDiv.style.left);
            var r = l + parseInt(this.oRectDiv.style.width);
            var b = t + parseInt(this.oRectDiv.style.height);
            this.event = e;
            this.execute(l,b,r,t);

            this.oRectDiv.style.left = '0px';
            this.oRectDiv.style.top = '0px';
            this.oRectDiv.style.width = '1px';
            this.oRectDiv.style.height = '1px';
            this.oRectDiv.style.visibility = 'hidden';

            this._sStartPos = null;
        }
        Event.stopObserving(document, 'mouseout', this.mouseOutCB);
        Event.stop(e);
    },

    mouseMove : function(e) {
        if (this._sStartPos != null)
        {
            var p = this.oMap.getEventPosition(e);
            var l = this._sStartPos.x;
            var t = this._sStartPos.y;
            var r = p.x;
            var b = p.y;

            this.oRectDiv.style.left = Math.min(l,r) + 'px';
            this.oRectDiv.style.top = Math.min(t,b) + 'px';
            this.oRectDiv.style.width = Math.abs(r-l) + 'px';
            this.oRectDiv.style.height = Math.abs(t-b) + 'px';
            Event.stop(e);
        }
    },
    
    mouseOut: function(e) {
        var target = e.target || e.srcElement;
        if (target.tagName.toLowerCase() == 'html') {
            this.mouseUp(e);
        }
    }
        
};
/********************************************************************** * 
 * @project Fusion
 * @revision $Id: Map.js,v 1.75 2007/07/17 15:44:44 zak Exp $
 * @purpose Generic Map widget
 * @author yassefa@dmsolutions.ca
 * Copyright (c) 2007 DM Solutions Group Inc.
 *****************************************************************************
 * This code shall not be copied or used without the expressed written consent
 * of DM Solutions Group Inc.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 ********************************************************************
 *
 * Fusion.Widget.Map : generic class for map widgets. Provides common utility classes.
 * Map widgets based on this should define some key functions like drawMap
 * **********************************************************************/

Fusion.Event.MAP_EXTENTS_CHANGED = Fusion.Event.lastEventId++;
Fusion.Event.MAP_BUSY_CHANGED = Fusion.Event.lastEventId++;
Fusion.Event.MAP_GENERIC_EVENT = Fusion.Event.lastEventId++;
Fusion.Event.MAP_RESIZED = Fusion.Event.lastEventId++;

Fusion.Constant.LAYER_POINT_TYPE = 0;
Fusion.Constant.LAYER_LINE_TYPE = 1;
Fusion.Constant.LAYER_POLYGON_TYPE = 2;
Fusion.Constant.LAYER_SOLID_TYPE = 3;
Fusion.Constant.LAYER_RASTER_TYPE = 4;

Fusion.Widget.Map = Class.create();
Fusion.Widget.Map.prototype =
{

    _oDomObj : null,
    _sDomObj : '',
    _oEventDiv : null,
    _sMapname : '',  
    _nWidth : -1,
    _nHeight : -1,  
    _fMetersperunit : -1,
    _fScale : -1,
    _nDpi : 96,
    _afCurrentExtents: null,
    _afInitialExtents: null,
    
    oContextMenu: null,
    bSupressContextMenu: false,
    
    layerRoot: null,
    aLayers: null,
    
    /**
     * construct a new view Fusion.Widget.Map class.  
     */
    initialize : function(oCommand) {    
        //console.log('Fusion.Widget.Map.initialize');

        Object.inheritFrom(this, Fusion.Lib.EventMgr, []);

        this.layerRoot = new Fusion.Widget.Map.Group();
        this.aLayers = [];
        
        this._nCellSize = -1;
        
        var json = oCommand.jsonNode;
        
        this._sDomObj = json.Name ? json.Name[0] : '';
        this._oDomObj = $(this._sDomObj);
        
        
        if (this._oDomObj.jxLayout) {
            this._oDomObj.jxLayout.addSizeChangeListener(this);
        }
        
        var d = Element.getDimensions(this._oDomObj);
        this._nWidth = d.width;
        this._nHeight = d.height;
        
        this._nWorkers = 0;
        
        this.swapImageFn = this.swapImages.bindAsEventListener(this);
        this.imageErrorFn = this._imageError.bindAsEventListener(this);
        
        this._oImg = document.createElement('img');
        this._oImg.className = 'png24';
        this._oImg.id = 'gMapImg';
        this._oImg.style.position = 'absolute';
        this._oImg.style.top = '0px';
        this._oImg.style.left = '0px';
        this._oImg.style.width = this._nWidth + 'px';
        this._oImg.style.height = this._nHeight + 'px';
        this._oImg.src = 'images/a_pixel.png';
        this._oImg.onerror = this. imageErrorFn;

        this._oImgNew = document.createElement('img');
        this._oImgNew.className = 'png24';
        this._oImgNew.id = 'gMapImgNew';
        this._oImgNew.style.position = 'absolute';
        this._oImgNew.style.top = '0px';
        this._oImgNew.style.left = '0px';
        this._oImgNew.style.width = this._nWidth + 'px';
        this._oImgNew.style.height = this._nHeight + 'px';
        this._oImgNew.src = 'images/a_pixel.png';
        //this._oImgNew.onload = this.swapImageFn;
        this._oImgNew.onerror = this.imageErrorFn;
    
        this._oImg.galleryimg = "no"; //turn off image toolbar in IE
        this._oDomObj.appendChild(this._oImg);

        this._oDomObj.oncontextmenu = function() {return false;};
        this._oImg.ondrag = function() {return false;};

        /* this shouldn't be needed because we capture events on the domObj but
         * it seems to be needed to get everything working in all the browsers
         */
        this._oEventDiv = document.createElement('div');
        this._oEventDiv.id = '_oEventDiv_' + this._oDomObj.id;
        this._oEventDiv.style.position = 'absolute';
        this._oEventDiv.style.top = '0px';
        this._oEventDiv.style.left = '0px';
        this._oEventDiv.style.width = '100%';
        this._oEventDiv.style.height = '100%';
        this._oEventDiv.style.zIndex = 100;
        /** hack to make event capturing work properly across all browsers */
        this._oEventDiv.style.backgroundColor = 'white';
        this._oEventDiv.style.opacity = 0;
        this._oEventDiv.style.mozOpacity = 0;
        this._oEventDiv.style.filter = 'Alpha(opacity=0)';
        this._oDomObj.appendChild(this._oEventDiv);
                
        this.registerEventID(Fusion.Event.MAP_EXTENTS_CHANGED);
        this.registerEventID(Fusion.Event.MAP_BUSY_CHANGED);
        this.registerEventID(Fusion.Event.MAP_GENERIC_EVENT);
        this.registerEventID(Fusion.Event.MAP_RESIZED);
        
        Event.observe(this._oDomObj, 'contextmenu', this.onContextMenu.bind(this));
        
        this.bMouseWheelZoom = json.AllowWheelZoom ? (json.AllowWheelZoom[0] == 'true' ? true : false ) : false;
        this.nFactor =json.WheelZoomFactor ? json.WheelZoomFactor[0] : 2.0;
        
        if (this.bMouseWheelZoom) {
            Event.observe(this._oEventDiv, 'mousewheel', this.onMouseWheel.bind(this));
            if (window.addEventListener &&
                navigator.product && navigator.product == "Gecko") {
                this._oEventDiv.addEventListener( "DOMMouseScroll", this.onMouseWheel.bind(this), false );
            }
            
        }
        
        //work around a bug in firefox that incorrectly reports the mouse
        //position in DOMMouseScroll events
        var fn = this.onMouseMove.bind(this);
        var ed = this._oEventDiv;
        Event.observe(ed, 'mousemove', fn);
        Event.observe(window, 'unload', function() {Event.stopObserving(ed, 'mousemove', fn);});
    },
    
    onMouseMove: function(e) {
        this.lastMousePos = this.getEventPosition(e);
    },
    
    onMouseWheel: function(e) {
        var wheelDelta = e.wheelDelta ? e.wheelDelta : e.detail*-1;
        var wheelSet = null;
        
        var factor = this.nFactor;
        var size = Element.getDimensions(this._oImg);

        /* if the mouse hasn't moved yet, zoom on center */
        if (!this.lastMousePos) {
            this.lastMousePos = {x:size.width/2,y:size.height/2};
        }
        
        /* always work from the current image top/left in case the user has
         * more than one zoom before the new image arrives.
         */
        var top = parseInt(this._oImg.style.top);
        var left = parseInt(this._oImg.style.left);
        
        /* image location and dimensions for a temporarily resized version of the
         * current image while we wait for the new image to arrive
         */
        var newLeft, newTop, newWidth, newHeight;
        var newCenterX, newCenterTop;
        
        /* the direction we are zooming - 1 for in, -1 for out */
        var direction = 1;
        
        /* calculate the new image dimensions and zoom factor */
        if (wheelDelta > 0) {
            /* mouse position relative to top left of img */
            var x = this.lastMousePos.x - left;
            var y = this.lastMousePos.y - top;

            /* center the image on the mouse position */
            newLeft = left - x;
            newTop = top - y;
            
            /* increase size of image */
            newWidth = size.width * factor;
            newHeight = size.height * factor;
            
        } else {
            /* reduce size of image */
            newWidth = size.width / factor;
            newHeight = size.height / factor;

            /* mouse position relative to top left of img */
            var x = (this.lastMousePos.x - left)/factor;
            var y = (this.lastMousePos.y - top)/factor;
            
            /* center the image on the mouse position */
            newLeft = left + x;
            newTop = top + y;
            
            /* reverse factor for geographic zoom */
            direction = -1;
        }

        /* move/size the image */
        this._oImg.style.width = newWidth + "px";
        this._oImg.style.height = newHeight + "px";
        this._oImg.style.top = newTop + 'px';
        this._oImg.style.left = newLeft + 'px';
        
        /* figure out what geographic point will be at the new center.
         * Essentially, the geographic location of the mouse has to stay
         * in the same pixel location relative to the top/left.
         */
        var geoPoint = {};
        if (this.lastMousePos) {
            /* multiplier as a ratio of the current width to new width */
            var geoFactor = (size.width / newWidth);
            
            /* current geographic size */
            var curGW = this._afCurrentExtents[2] - this._afCurrentExtents[0];
            var curGH = this._afCurrentExtents[3] - this._afCurrentExtents[1];
            
            /* new geographic size is just a factor of the current one */
            var newGW = curGW * geoFactor;
            var newGH = curGH * geoFactor;
            
            /* geographic location of the mouse */
            var mouseLoc = this.pixToGeo(this.lastMousePos.x, this.lastMousePos.y);
            
            /* new geographic left/top is calculated from current mouse location and
             * taking the geographic distance to the top left in the current view and
             * using the geoFactor to figure out how far (geographically) it will be
             * in the new view
             */
            var newGL = mouseLoc.x - (mouseLoc.x - this._afCurrentExtents[0])*geoFactor;
            var newGT = mouseLoc.y + (this._afCurrentExtents[3] - mouseLoc.y)*geoFactor;

            /* now find the center so we can zoom */
            geoPoint.x = (newGL + newGW/2);
            geoPoint.y = (newGT - newGH/2);
        } else {
            geoPoint = this.getCurrentCenter();
        }
        
        /* finally we can zoom */
        this.zoom(geoPoint.x, geoPoint.y, direction*factor);
    },
    
    /**
     * returns the dom element 
     */
    getDomObj : function() {
        return this._oDomObj;
    },


    /**
     * returns the size of the map image in pixels in an object with
     * two attributes, width and height.
     */
    getPixelSize : function() {
        return {width:parseInt(this._oImg.width), 
                height:parseInt(this._oImg.height)};
    
    },

    getMapName : function() {  
        return this._sMapname;
    },

    getDomId : function() {  
        return this._sDomObj;
    },

    /**
     * indicate that a new asynchronous process has started and make sure the
     * visual indicator is visible for the user.  This is intended to be used
     * internally by gMap but could be used by external tools if appropriate.
     */
    _addWorker : function() {
        this._nWorkers += 1;
        this.triggerEvent(Fusion.Event.MAP_BUSY_CHANGED, this);
    },

    /**
     * indicate that an asynchronous process has completed and hide the
     * visual indicator if no remaining processes are active.  This is 
     * intended to be used internally by gMap but could be used by 
     * external tools if appropriate.  Only call this function if
     * addWorker was previously called
     */
    _removeWorker : function() {
        if (this._nWorkers > 0) {
            this._nWorkers -= 1;
        }
        this.triggerEvent(Fusion.Event.MAP_BUSY_CHANGED, this);
    },
    
    isBusy: function() {
        return this._nWorkers > 0;
    },

    /**
     * This function should be defined by classes inheriting grom Fusion.Widget.Map
     */
    drawMap : function()
    {       
        alert("Fusion.Widget.Map::drawMap");
    },

    sizeChanged: function() {
        this.resize();
    },
    
    resize : function() {
      //console.log('Fusion.Widget.Map.resize');
        var d = Element.getDimensions(this.getDomObj());
        this._nWidth = d.width;
        this._nHeight = d.height;
        if (this._afInitialExtents) {
            this.drawMap();
        }
        this.triggerEvent(Fusion.Event.MAP_RESIZED, this);
    },
    
    setMapImageURL: function(url) {
        console.log('setting map image url: ' + url);
        if (this._oImgNew.width != this._nWidth || this._oImgNew.height != this._nHeight) {
            this._oImgNew.src = 'images/a_pixel.png';
            this._oImgNew.style.width = this._nWidth + 'px';
            this._oImgNew.style.height = this._nHeight + 'px';
        }
        //IE uses a PNG Hack to load 24 bit PNG images, so we need to change the
        //filter and manually call swapImages
        if (this._oImgNew.runtimeStyle && this._oImgNew.runtimeStyle.filter) {
            this._oImgNew.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+url+"',sizingMethod='scale')";
            this.swapImages();
        } else {
            if (this._oImgNew.onload) {
                this._removeWorker();
            }
            this._oImgNew.onload = this.swapImageFn;
            this._oImgNew.src = url;
        }
    },
     
    swapImages : function(e) {
        if (!this._oImg.complete && this._oImg.onload) {
            this._oImg.onload = null;
            this._removeWorker();
        }
        this._oImgNew.onload = null;
        this._oImg = this._oDomObj.replaceChild(this._oImgNew, this._oImg);
        
        var i = this._oImg;
        this._oImg = this._oImgNew;
        this._oImgNew = i;

        this._oImgNew.style.top = '0px';
        this._oImgNew.style.left = '0px';
        this._oImgNew.style.width = this._oImg.style.width;
        this._oImgNew.style.height = this._oImg.style.height;
        
        this._removeWorker();
    },

    _imageError : function() {
        this._removeWorker();
        setTimeout(this.drawMap.bind(this), 1000);
    },

    _calculateScale : function() {
        var fMetersPerPixel = 0.0254 / this._nDpi;
        var fdeltaX = this._afCurrentExtents[2] - this._afCurrentExtents[0];
        var fdeltaY = this._afCurrentExtents[3] - this._afCurrentExtents[1];
    
        var nWidth = this._nWidth;
        var nHeight = this._nHeight;

        if (fdeltaX *  nWidth >  fdeltaY * nHeight) {
            this._fScale =  
               fdeltaX * this._fMetersperunit / (nWidth * fMetersPerPixel);
        } else {
            this._fScale = fdeltaY * this._fMetersperunit / (nHeight * fMetersPerPixel); 
        }
    },
    
    setExtents : function(aExtents) {
        if (!this._afInitialExtents) {
            this._afInitialExtents = aExtents;
        }
        var bResize = false;
        if (this._afCurrentExtents) {
            bResize = true;
            var cXold = (this._afCurrentExtents[2] + this._afCurrentExtents[0])/2;
            var cYold = (this._afCurrentExtents[3] + this._afCurrentExtents[1])/2;
            var cellSizeOld = this._nCellSize;
        }
        
        var gWidth = Math.abs(aExtents[2] - aExtents[0]);
        var gHeight = Math.abs(aExtents[3] - aExtents[1]);
        
        var nWidth = this._nWidth;
        var nHeight = this._nHeight;
        
        this._nCellSize = Math.max(gWidth/nWidth, gHeight/nHeight);
        if (gWidth/nWidth != gHeight/nHeight) {
            var cX = (aExtents[2] + aExtents[0])/2;
            var cY = (aExtents[3] + aExtents[1])/2;
            aExtents[0] = cX - (this._nCellSize * nWidth)/2;
            aExtents[1] = cY - (this._nCellSize * nHeight)/2;
            aExtents[2] = cX + (this._nCellSize * nWidth)/2;
            aExtents[3] = cY + (this._nCellSize * nHeight)/2;
        }
        
        /*
        if (bResize) {
            var dx = ((cXold - cX)/this._nCellSize)/2;
            var dy = ((cYold - cY)/this._nCellSize)/2;
            this._oImg.style.top = 0 + 'px';
            this._oImg.style.left = 0 + 'px';
            this._oImg.style.width = (nWidth * cellSizeOld/this._nCellSize) + 'px';
            this._oImg.style.height = (nHeight * cellSizeOld/this._nCellSize) + 'px';
        } else {
            this._oImg.style.width = nWidth + 'px';
            this._oImg.style.height = nHeight + 'px';
        }
        this._oImg.style.width = nWidth + 'px';
        this._oImg.style.height = nHeight + 'px';
        */

        this._afCurrentExtents = aExtents;
        this._calculateScale();

        this.triggerEvent(Fusion.Event.MAP_EXTENTS_CHANGED);
    },

    fullExtents : function() {
        this.setExtents(this._afInitialExtents); 
    },

    zoom : function(fX, fY, nFactor) {
        var fDeltaX = this._afCurrentExtents[2] - this._afCurrentExtents[0];
        var fDeltaY = this._afCurrentExtents[3] - this._afCurrentExtents[1];
        var fMinX,fMaxX,fMinY,fMaxy;

        if (nFactor == 1 || nFactor == 0) {
            /*recenter*/
            fMinX = fX - (fDeltaX/2);
            fMaxX = fX + (fDeltaX/2);
            fMinY = fY - (fDeltaY/2);
            fMaxY = fY + (fDeltaY/2);
        } else if (nFactor > 0) {
            /*zoomin*/
            fMinX = fX - (fDeltaX/2 / nFactor);
            fMaxX = fX + (fDeltaX/2 / nFactor);
            fMinY = fY - (fDeltaY/2 / nFactor);
            fMaxY = fY + (fDeltaY/2 / nFactor);
        } else if (nFactor < 0) {
            /*zoomout*/
            fMinX = fX - ((fDeltaX/2) * Math.abs(nFactor));
            fMaxX = fX + ((fDeltaX/2) * Math.abs(nFactor));
            fMinY = fY - ((fDeltaY/2) * Math.abs(nFactor));
            fMaxY = fY + ((fDeltaY/2) * Math.abs(nFactor));
        }
        this.setExtents([fMinX, fMinY, fMaxX, fMaxY]);
    },
    
    zoomScale: function(fScale) {
        var fMetersPerPixel = 0.0254 / this._nDpi;
        var nWidth = this._nWidth;
        var nHeight = this._nHeight;
        var fMapUnitsPerPixel = fScale * fMetersPerPixel / this._fMetersperunit;
        var c = this.getCurrentCenter();
        var fMinX = c.x - (fMapUnitsPerPixel * nWidth/2);
        var fMinY = c.y - (fMapUnitsPerPixel * nHeight/2);
        var fMaxX = c.x + (fMapUnitsPerPixel * nWidth/2);
        var fMaxY = c.y + (fMapUnitsPerPixel * nHeight/2);
        
        this.setExtents([fMinX, fMinY, fMaxX, fMaxY]);
    },
    
    /**
     *
     * update the extents to fit the map image size based on the current cellsize.
     * This function preserves the center and scale of the extents when the image
     * is resized.  This function is intended for internal use only and does not
     * call drawMap()
     */
    _updateExtents : function()
    {
        var cx = (this._afCurrentExtents[0] + this._afCurrentExtents[2])/2;
        var cy = (this._afCurrentExtents[1] + this._afCurrentExtents[3])/2;    
        this._afCurrentExtents[0] = cx - (this._nCellSize * parseInt(this.img.width))/2;
        this._afCurrentExtents[1] = cy - (this._nCellSize * parseInt(this.img.height))/2;
        this._afCurrentExtents[2] = cx + (this._nCellSize * parseInt(this.img.width))/2;
        this._afCurrentExtents[3] = cy + (this._nCellSize * parseInt(this.img.height))/2;

    },

    queryRect : function(fMinX, fMinY, fMaxX, fMaxY)
    {
    },
    
    queryPoint : function(fX, fY)
    {
    },

    
    /**
     *
     * convert pixel coordinates into geographic coordinates.
     *
     * @paran pX int the x coordinate in pixel units
     * @param pY int the y coordinate in pixel units
     *
     * @return an object with geographic coordinates in x and y properties of the 
     *         object.
     */
    pixToGeo : function( pX, pY )
    {
        if (!(this._afCurrentExtents)) {
            return null;
        }
        var gX = parseFloat(this._afCurrentExtents[0]) +  (pX * this._nCellSize);
        var gY = parseFloat(this._afCurrentExtents[3]) - (pY * this._nCellSize);

        return {x:gX, y:gY};
    },

    /**
     *
     * convert pixel into geographic : used to measure.
     *
     * @param nPixels int measures in pixel
     *
     * @return geographic measure
     */
    pixToGeoMeasure : function(nPixels)
    {
        return (nPixels*this._nCellSize);
    },
    
    /**
     *
     * convert geographic into pixels.
     *
     * @param fGeo float distance in geographic units
     *
     * @return pixels
     */
    geoToPixMeasure : function(fGeo)
    {
        return parseInt(fGeo/this._nCellSize);
    },
    
    /**
     *
     * convert geographic coordinates into pixel coordinates.
     *
     * @paran gX int the x coordinate in geographic units
     * @param gY int the y coordinate in geographic units
     *
     * @return an object with pixel coordinates in x and y properties of the 
     *         object.
     */
    geoToPix : function( gX, gY )
    {
        if (!(this._afCurrentExtents)) {
            return null;
        }
        var pX = parseFloat(gX - this._afCurrentExtents[0]) / this._nCellSize;
        var pY = -1 * parseFloat(gY - this._afCurrentExtents[3]) / this._nCellSize;
        //console.log("Fusion.Widget.Map::geoToPix coords: g:" +gX+', '+gY+' p:'+pX+', '+pY);
        return {x:Math.floor(pX), y:Math.floor(pY)};
    },

    getCurrentScale: function() {
        return this._fScale;
    },

    /**
     *
     * returns the current center of the map view
     */
    getCurrentCenter : function()
    {
        var cx = (this._afCurrentExtents[0] + this._afCurrentExtents[2])/2;
        var cy = (this._afCurrentExtents[1] + this._afCurrentExtents[3])/2;
        return {x:cx, y:cy};
    },
    /**
     *
     * returns the current extents
     */
    getCurrentExtents : function()
    {
        return this._afCurrentExtents;
    },

    /**
     *
     * returns initial extents
    */
    getInitialExtents : function() 
    {
        return this._afInitialExtents;
    },


    getEventPosition : function(e)
    {
        var posX,posY;
        if (e.target) {
            posX = posY = 0 ;
            var o = e.target;
            while (o.offsetParent)
            {
                posX += o.offsetLeft ;
                posY += o.offsetTop ;
                o = o.offsetParent ;
            }
            posX = e.pageX - posX ;
            posY = e.pageY - posY ;
        } else if (e.offsetX) {
            posX = e.offsetX;
            posY = e.offsetY;
        } else {
            posX = e.clientX;
            posY = e.clientY;
        }
        return {x:posX,y:posY};
    },

    setCursor : function(cursor)
    {
        if (cursor && cursor.length && typeof cursor == 'object') 
        {
            for (var i = 0; i < cursor.length; i++) 
            {
                this._oDomObj.style.cursor = cursor[i];
                if (this._oDomObj.style.cursor == cursor[i]) 
                {
                    break;
                }
            }
        } 
        else if (typeof cursor == 'string') 
        {
            this._oDomObj.style.cursor = cursor;
        } else 
        {
            this._oDomObj.style.cursor = 'auto';  
    }
    },
    /**
     *
     * Observe specified event on the event div of the map
     *
     * @param sEventName string event name (eg : mousemove')
     * @param fnCB function Call back function name
     *
     */
     observeEvent  : function(sEventName, fnCB)
     {
         Event.observe(this._oDomObj, sEventName, fnCB, false);
     },

     /**
     *
     * Stop observing specified event on the event div of the map
     *
     * @param sEventName string event name (eg : mousemove')
     * @param fnCB function Call back function name
     *
     */
     stopObserveEvent : function(sEventName, fnCB)
     {
         Event.stopObserving(this._oDomObj, sEventName, fnCB, false);
     },

     /**
     *
     * call the Activate method on the widget
     * if widgets is set to be mutually exclusive,
     * all other widgets are deactivated
     *
     * @param nId integer widget id
     */
     activateWidget : function(oWidget)
     {
         /*console.log('Fusion.Widget.Map.activateWidget ' + oWidget.getName());*/
         if (oWidget.isMutEx()) {
             if (this.oActiveWidget) {
                 this.deactivateWidget(this.oActiveWidget);
             }
             oWidget.activate();
             this.oActiveWidget = oWidget;
         } else {
             oWidget.activate();
         }
     },

     /**
     *
     * call the Activate method on the widget
     * if widgets is set to be mutually exclusive,
     * all other widgets are deactivated
     *
     * @param oWidget the widget to deactivate
     */
     deactivateWidget : function(oWidget)
     {
         /*console.log('Fusion.Widget.Map.deactivateWidget ' + oWidget.getName());*/
         oWidget.deactivate();
         this.oActiveWidget = null;
     },
     
     /**
      */
     isLoaded: function() {
         return (this._afCurrentExtents != null);
     },
     
     supressContextMenu: function( bSupress ) {
         this.bSupressContextMenu = bSupress;
     },
     
     setContextMenu: function(menu) {
         //console.log('setcontextmenu');
         this.oContextMenu = menu;
     },
     
     onContextMenu: function(e) {
         //console.log('oncontextmenu');
         if (this.oContextMenu && !this.bSupressContextMenu && this.isLoaded()) {
             this.oContextMenu.show(e);
             this.contextMenuPosition = this.getEventPosition(e);
             Event.stop(e);
         }
     },
     
     executeFromContextMenu: function(widget) {
         //console.log('executefromcontextmenu');
         widget.execute(this.contextMenuPosition.x, this.contextMenuPosition.y);
     }
};

Fusion.Event.LAYER_PROPERTY_CHANGED = Fusion.Event.lastEventId++;
Fusion.Widget.Map.Layer = Class.create();
Fusion.Widget.Map.Layer.prototype = {
    name: null,
    initialize: function(name) {
        Object.inheritFrom(this, Fusion.Lib.EventMgr, []);
        this.name = name;
        this.registerEventID(Fusion.Event.LAYER_PROPERTY_CHANGED);
    },
    set: function(property, value) {
        this[property] = value;
        this.triggerEvent(Fusion.Event.LAYER_PROPERTY_CHANGED, this);
    }
};

Fusion.Event.GROUP_PROPERTY_CHANGED = Fusion.Event.lastEventId++;
Fusion.Widget.Map.Group = Class.create();
Fusion.Widget.Map.Group.prototype = {
    name: null,
    groups: null,
    layers: null,
    initialize: function(name) {
        Object.inheritFrom(this, Fusion.Lib.EventMgr, []);
        this.name = name;
        this.groups = [];
        this.layers = [];
        this.registerEventID(Fusion.Event.GROUP_PROPERTY_CHANGED);
    },
    set: function(property, value) {
        this[property] = value;
        this.triggerEvent(Fusion.Event.GROUP_PROPERTY_CHANGED, this);
    },
    addGroup: function(group) {
        group.parentGroup = this;
        this.groups.push(group);
        
    },
    addLayer: function(layer) {
        layer.parentGroup = this;
        this.layers.push(layer);
    },
    findGroup: function(name) {
        return this.findGroupByAttribute('name', name);
    },
    findGroupByAttribute: function(attribute, value) {
        if (this[attribute] == value) {
            return this;
        }
        for (var i=0; i<this.groups.length; i++) {
            var group = this.groups[i].findGroupByAttribute(attribute, value);
            if (group) {
                return group;
            }
        }
        return null;
    },
    findLayer: function(name) {
        return this.findLayerByAttribute('name', name);
    },
    findLayerByAttribute: function(attribute, value) {
        for (var i=0; i<this.layers.length; i++) {
            if (this.layers[i][attribute] == value) {
                return this.layers[i];
            }
        }
        for (var i=0; i<this.groups.length; i++) {
            var layer = this.groups[i].findLayerByAttribute(attribute,value);
            if (layer) {
                return layer;
            }
        }
        return null;
    }
};


/**
 * SelectionObject
 *
 * Utility class to hold slection information
 *
 */
var GxSelectionObject = Class.create();
GxSelectionObject.prototype = 
{
    aLayers : null,

    initialize: function(oXML) 
    {
        this.aLayers = [];
        this.nTotalElements =0;

        var root = new DomNode(oXML);
        
        var node  = root.getNodeText('minx');
        if (node)
        {
            this.fMinX =  parseFloat(root.getNodeText('minx'));
            this.fMinY =  parseFloat(root.getNodeText('miny'));
            this.fMaxX =  parseFloat(root.getNodeText('maxx'));
            this.fMaxY =  parseFloat(root.getNodeText('maxy'));
        }
    
        //this is only available when the property mapping is set
        //on the layer. TODO : review
        var oTmpNode = root.findFirstNode('TotalElementsSelected');
        if (oTmpNode)
        {
            this.nTotalElements = parseInt(oTmpNode.textContent);
            if (this.nTotalElements > 0)
            {
                this.nLayers =  root.getNodeText('NumberOfLayers');
                var layerNode = root.findFirstNode('Layer');
                var iLayer=0;             
                while(layerNode) 
                {
                    this.aLayers[iLayer++] = new GxSelectionObjectLayer(layerNode);
                
                    layerNode =  root.findNextNode('Layer');
                }
            }
        }
    },

    getNumElements : function()
    {
        return this.nTotalElements;
    },

    getLowerLeftCoord : function()
    {
        return {x:this.fMinX, y:this.fMinY};
    },

    getUpperRightCoord : function()
    {
        return {x:this.fMaxX, y:this.fMaxY};
    },

    getNumLayers : function()
    {
        return this.nLayers;
    },
    
    getLayerByName : function(name)
    {
        var oLayer = null;
        for (var i=0; i<this.nLayers; i++)
        {
            if (this.aLayers[i].getName() == name)
            {
                oLayer = this.aLayers[i];
                break;
            }
        }
        return oLayer;
    },

    getLayer : function(iIndice)
    {
        if (iIndice >=0 && iIndice < this.nLayers)
        {
            return this.aLayers[iIndice];
        }
        else
        {
            return null;
        }
            
    }
};


var GxSelectionObjectLayer = Class.create();
GxSelectionObjectLayer.prototype = {

    sName: null,
    nElements: null,
    aElements: null,
    nProperties: null,
    aPropertiesName: null,
    aPropertiesTypes: null,

    type: null,
    area: null,
    distance: null,
    bbox: null,
    center: null,
    
    initialize: function(oNode) 
    {
        this.sName =  oNode.getNodeText('Name');
        this.nElements =  oNode.getNodeText('ElementsSelected');

        this.aElements = [];

        this.nProperties = oNode.getNodeText('PropertiesNumber');

        this.aPropertiesName = [];
        var oTmp = oNode.getNodeText('PropertiesNames');
        this.aPropertiesName = oTmp.split(",");

        this.aPropertiesTypes = [];
        oTmp = oNode.getNodeText('PropertiesTypes');
        this.aPropertiesTypes = oTmp.split(",");
        
        var oValueCollection = oNode.findNextNode('ValueCollection');
        
        this.area = 0;
        this.distance = 0;
        
        var iElement=0;
        while(oValueCollection) 
        {
            this.aElements[iElement] = [];
            for (var i=0; i<oValueCollection.childNodes.length; i++)
            {
                oTmp = oValueCollection.childNodes[i].findFirstNode('v');
                this.aElements[iElement][i] = oTmp.textContent;
                
            }
            var type = oValueCollection.attributes['type'];
            var area = oValueCollection.attributes['area'];
            var distance = oValueCollection.attributes['distance'];
            var bbox = oValueCollection.attributes['bbox'];
            var center = oValueCollection.attributes['center'];
            
            this.aElements[iElement]['attributes'] = {};
            this.aElements[iElement]['attributes'].type = type;
            this.aElements[iElement]['attributes'].bbox = bbox;
            this.aElements[iElement]['attributes'].center = bbox;
            //console.log('type is ' + type);
            if (type > 1) {
                this.area += parseFloat(area);
                this.aElements[iElement]['attributes'].area = area;
            }
            if (type > 0) {
                this.aElements[iElement]['attributes'].distance = distance;
                this.distance += parseFloat(distance);
            }
            oValueCollection = oNode.findNextNode('ValueCollection');
            iElement++;
        }
        //console.log( 'final area is ' + this.area);
        //console.log( 'final distance is ' + this.distance);
        
    },

    getName : function()
    {
        return this.sName;
    },

    getNumElements : function()
    {
        return this.nElements;
    },

    getNumProperties : function()
    {
        return this.nProperties;
    },

    getPropertyNames : function()
    {
        return this.aPropertiesName;
    },

    getPropertyTypes : function()
    {
        return this.aPropertiesTypes;
    },

    getElementValue : function(iElement, iProperty)
    {
        if (iElement >=0 && iElement < this.nElements &&
            iProperty >=0 && iProperty < this.nProperties)
        {
            return this.aElements[iElement][iProperty];
        }
        else
        {
            return null;
        }
    }
};
