/*!
 * Tine 2.0 - Tinebase 
 * Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 * http://www.gnu.org/licenses/agpl.html AGPL Version 3
 */
Ext.override(Ext.data.Store, {
    /**
     * String cast the id, as otherwise the entries are not found
     */
    indexOfId : function(id){
        return this.data.indexOfKey(String(id));
    }
});

/**
 * for some reasons the original fix insertes two <br>'s on enter for webkit. But this is one to much
 */
Ext.apply(Ext.form.HtmlEditor.prototype, {
        fixKeys : function(){ // load time branching for fastest keydown performance
        if(Ext.isIE){
            return function(e){
                var k = e.getKey(),
                    doc = this.getDoc(),
                        r;
                if(k == e.TAB){
                    e.stopEvent();
                    r = doc.selection.createRange();
                    if(r){
                        r.collapse(true);
                        r.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
                        this.deferFocus();
                    }
                }else if(k == e.ENTER){
                    r = doc.selection.createRange();
                    if(r){
                        var target = r.parentElement();
                        if(!target || target.tagName.toLowerCase() != 'li'){
                            e.stopEvent();
                            r.pasteHTML('<br />');
                            r.collapse(false);
                            r.select();
                        }
                    }
                }
            };
        }else if(Ext.isOpera){
            return function(e){
                var k = e.getKey();
                if(k == e.TAB){
                    e.stopEvent();
                    this.win.focus();
                    this.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
                    this.deferFocus();
                }
            };
        }else if(Ext.isWebKit){
            return function(e){
                var k = e.getKey();
                if(k == e.TAB){
                    e.stopEvent();
                    this.execCmd('InsertText','\t');
                    this.deferFocus();
                }
             };
        }
    }()
});

/**
 * fix broken ext email validator
 * 
 * @type RegExp
 */
Ext.apply(Ext.form.VTypes, {
    // 2011-01-05 replace \w with [^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F] to allow idn's
    emailFixed:  /^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:([^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]|-)+\.)*[^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]([^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]|-){0,66})\.([^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]{2,6}?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i,
    
    urlFixed: /(((^https?)|(^ftp)):\/\/(([^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]|-)+\.)+[^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]{2,3}(\/([^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]|-|%)+(\.[^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]{2,})?)*((([^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]|[\-\.\?\\\/+@&#;`~=%!])*)(\.[^\s,\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7F]{2,})?)*\/?)/i,
    
    email:  function(v) {
        return this.emailFixed.test(v);
    },
    
    url: function(v) {
        return this.urlFixed.test(v);
    }
});


/**
 * fix textfield allowBlank validation
 */
Ext.override(Ext.form.TextField, {
    validator:function(text){
        return this.allowBlank!==false || Ext.util.Format.trim(text).length > 0;
    }
});

Ext.applyIf(Ext.tree.MultiSelectionModel.prototype, {
    /**
     * implement convinience function as expected from grid selection models
     * 
     * @namespace {Ext.tree}
     * @return {Node}
     */
    getSelectedNode: function() {
        var selection = this.getSelectedNodes();
        return Ext.isArray(selection) ? selection[0] : null
    }
});

/**
 * fix timezone handling for date picker
 * 
 * The getValue function always returns 00:00:00 as time. So if a form got filled
 * with a date like 2008-10-01T21:00:00 the form returns 2008-10-01T00:00:00 although 
 * the user did not change the fieled.
 * 
 * In a multi timezone context this is fatal! When a user in a certain timezone set
 * a date (just a date and no time information), this means in his timezone the 
 * time range from 2008-10-01T00:00:00 to 2008-10-01T23:59:59. 
 * _BUT_ for an other user sitting in a different timezone it means e.g. the 
 * time range from 2008-10-01T02:00:00 to 2008-10-02T21:59:59.
 * 
 * So on the one hand we need to make sure, that the date picker only returns 
 * changed datetime information when the user did a change. 
 * 
 * @todo On the other hand we
 * need adjust the day +/- one day according to the timeshift. 
 */
/**
 * @private
 */
 Ext.form.DateField.prototype.setValue = function(date){
    // get value must not return a string representation, so we convert this always here
    // before memorisation
    if (Ext.isString(date)) {
        var v = Date.parseDate(date, Date.patterns.ISO8601Long);
        if (Ext.isDate(v)) {
            date = v;
        } else {
            date = Ext.form.DateField.prototype.parseDate.call(this, date);
        }
    }
    
    // preserve original datetime information
    this.fullDateTime = date;
    
    Ext.form.DateField.superclass.setValue.call(this, this.formatDate(this.parseDate(date)));
};
/**
 * @private
 */
Ext.form.DateField.prototype.getValue = function(){
    //var value = this.parseDate(Ext.form.DateField.superclass.getValue.call(this));
    
    // return the value that was set (has time information when unchanged in client) 
    // and not just the date part!
    var value =  this.fullDateTime;
    return value || "";
};

/**
 * We need to overwrite to preserve original time information because 
 * Ext.form.TimeField does not support seconds
 * 
 * @param {} time
 * @private
 */
 Ext.form.TimeField.prototype.setValue = function(time){
    this.fullDateTime = time;
    Ext.form.TimeField.superclass.setValue.call(this, this.formatDate(this.parseDate(time)));
};
/**
 * @private
 */
Ext.form.TimeField.prototype.getValue = function(){
    // return the value that was set (has time information when unchanged in client) 
    // and not just the date part!
    var value =  this.fullDateTime;
    
    return value ? this.parseDate(value).dateFormat('H:i') : "";
};

/**
 * check if sort field is in column list to avoid server exceptions
 */
Ext.grid.GridPanel.prototype.applyState  = Ext.grid.GridPanel.prototype.applyState.createInterceptor(function(state){
    var cm = this.colModel,
        s = state.sort;
    
    if (s && cm.getIndexById(s.field) < 0) {
        delete state.sort;
    }
});

/**
 * fix interpretation of ISO-8601  formatcode (Date.patterns.ISO8601Long) 
 * 
 * Browsers do not support timezones and also javascripts Date object has no 
 * support for it.  All Date Objects are in _one_ timezone which may ore may 
 * not be the operating systems timezone the browser runs on.
 * 
 * parsing dates in ISO format having the timeshift appended (Date.patterns.ISO8601Long) lead to 
 * correctly converted Date Objects in the browsers timezone. This timezone 
 * conversion changes the the Date Parts and as such, javascipt widget 
 * representing date time information print values of the browsers timezone 
 * and _not_ the values send by the server!
 * 
 * So in a multi timezone envireonment, datetime information in the browser 
 * _must not_ be parsed including the offset. Just the values of the server 
 * side converted datetime information are allowed to be parsed.
 */
Date.parseIso = function(isoString) {
    return Date.parseDate(isoString.replace(/\+\d{2}\d{2}/, ''), 'Y-m-d\\Th:i:s');
};

/**
 * rename window
 */
Ext.Window.prototype.rename = function(newId) {
    // Note PopupWindows are identified by name, whereas Ext.windows
    // get identified by id this should be solved some time ;-)
    var manager = this.manager || Ext.WindowMgr;
    manager.unregister(this);
    this.id = newId;
    manager.register(this);
};

/**
 * utility class used by Button
 * 
 * Fix: http://yui-ext.com/forum/showthread.php?p=142049
 * adds the ButtonToggleMgr.getSelected(toggleGroup, handler, scope) function
 */
Ext.ButtonToggleMgr = function(){
   var groups = {};
   
   function toggleGroup(btn, state){
       if(state){
           var g = groups[btn.toggleGroup];
           for(var i = 0, l = g.length; i < l; i++){
               if(g[i] != btn){
                   g[i].toggle(false);
               }
           }
       }
   }
   
   return {
       register : function(btn){
           if(!btn.toggleGroup){
               return;
           }
           var g = groups[btn.toggleGroup];
           if(!g){
               g = groups[btn.toggleGroup] = [];
           }
           g.push(btn);
           btn.on("toggle", toggleGroup);
       },
       
       unregister : function(btn){
           if(!btn.toggleGroup){
               return;
           }
           var g = groups[btn.toggleGroup];
           if(g){
               g.remove(btn);
               btn.un("toggle", toggleGroup);
           }
       },
       
       getSelected : function(toggleGroup, handler, scope){
           var g = groups[toggleGroup];
           for(var i = 0, l = g.length; i < l; i++){
               if(g[i].pressed === true){
                   if(handler) {
                        handler.call(scope || g[i], g[i]);
                   }
                   return g[i];
               }
           }
           return;
       }
   };
}();

/**
 * add beforeloadrecords event
 */
Ext.data.Store.prototype.loadRecords = Ext.data.Store.prototype.loadRecords.createInterceptor(function(o, options, success) {
    return this.fireEvent('beforeloadrecords', o, options, success, this);
});
/*!
 * Ext JS Library 3.2.1
 * Copyright(c) 2006-2010 Ext JS, Inc.
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ux.Portal = Ext.extend(Ext.Panel, {
    layout : 'column',
    autoScroll : true,
    cls : 'x-portal',
    defaultType : 'portalcolumn',
    
    initComponent : function(){
        Ext.ux.Portal.superclass.initComponent.call(this);
        this.addEvents({
            validatedrop:true,
            beforedragover:true,
            dragover:true,
            beforedrop:true,
            drop:true
        });
    },

    initEvents : function(){
        Ext.ux.Portal.superclass.initEvents.call(this);
        this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);
    },
    
    beforeDestroy : function() {
        if(this.dd){
            this.dd.unreg();
        }
        Ext.ux.Portal.superclass.beforeDestroy.call(this);
    }
});

Ext.reg('portal', Ext.ux.Portal);

Ext.ux.Portal.DropZone = Ext.extend(Ext.dd.DropTarget, {
    
    constructor : function(portal, cfg){
        this.portal = portal;
        Ext.dd.ScrollManager.register(portal.body);
        Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);
        portal.body.ddScrollConfig = this.ddScrollConfig;
    },
    
    ddScrollConfig : {
        vthresh: 50,
        hthresh: -1,
        animate: true,
        increment: 200
    },

    createEvent : function(dd, e, data, col, c, pos){
        return {
            portal: this.portal,
            panel: data.panel,
            columnIndex: col,
            column: c,
            position: pos,
            data: data,
            source: dd,
            rawEvent: e,
            status: this.dropAllowed
        };
    },

    notifyOver : function(dd, e, data){
        var xy = e.getXY(), portal = this.portal, px = dd.proxy;

        // case column widths
        if(!this.grid){
            this.grid = this.getGrid();
        }

        // handle case scroll where scrollbars appear during drag
        var cw = portal.body.dom.clientWidth;
        if(!this.lastCW){
            this.lastCW = cw;
        }else if(this.lastCW != cw){
            this.lastCW = cw;
            portal.doLayout();
            this.grid = this.getGrid();
        }

        // determine column
        var col = 0, xs = this.grid.columnX, cmatch = false;
        for(var len = xs.length; col < len; col++){
            if(xy[0] < (xs[col].x + xs[col].w)){
                cmatch = true;
                break;
            }
        }
        // no match, fix last index
        if(!cmatch){
            col--;
        }

        // find insert position
        var p, match = false, pos = 0,
            c = portal.items.itemAt(col),
            items = c.items.items, overSelf = false;

        for(var len = items.length; pos < len; pos++){
            p = items[pos];
            var h = p.el.getHeight();
            if(h === 0){
                overSelf = true;
            }
            else if((p.el.getY()+(h/2)) > xy[1]){
                match = true;
                break;
            }
        }

        pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);
        var overEvent = this.createEvent(dd, e, data, col, c, pos);

        if(portal.fireEvent('validatedrop', overEvent) !== false &&
           portal.fireEvent('beforedragover', overEvent) !== false){

            // make sure proxy width is fluid
            px.getProxy().setWidth('auto');

            if(p){
                px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);
            }else{
                px.moveProxy(c.el.dom, null);
            }

            this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false};
            this.scrollPos = portal.body.getScroll();

            portal.fireEvent('dragover', overEvent);

            return overEvent.status;
        }else{
            return overEvent.status;
        }

    },

    notifyOut : function(){
        delete this.grid;
    },

    notifyDrop : function(dd, e, data){
        delete this.grid;
        if(!this.lastPos){
            return;
        }
        var c = this.lastPos.c, 
            col = this.lastPos.col, 
            pos = this.lastPos.p,
            panel = dd.panel,
            dropEvent = this.createEvent(dd, e, data, col, c,
                pos !== false ? pos : c.items.getCount());

        if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&
           this.portal.fireEvent('beforedrop', dropEvent) !== false){

            dd.proxy.getProxy().remove();
            panel.el.dom.parentNode.removeChild(dd.panel.el.dom);
            
            if(pos !== false){
                c.insert(pos, panel);
            }else{
                c.add(panel);
            }
            
            c.doLayout();

            this.portal.fireEvent('drop', dropEvent);

            // scroll position is lost on drop, fix it
            var st = this.scrollPos.top;
            if(st){
                var d = this.portal.body.dom;
                setTimeout(function(){
                    d.scrollTop = st;
                }, 10);
            }

        }
        delete this.lastPos;
    },

    // internal cache of body and column coords
    getGrid : function(){
        var box = this.portal.bwrap.getBox();
        box.columnX = [];
        this.portal.items.each(function(c){
             box.columnX.push({x: c.el.getX(), w: c.el.getWidth()});
        });
        return box;
    },

    // unregister the dropzone from ScrollManager
    unreg: function() {
        Ext.dd.ScrollManager.unregister(this.portal.body);
        Ext.ux.Portal.DropZone.superclass.unreg.call(this);
    }
});
/*!
 * Ext JS Library 3.2.1
 * Copyright(c) 2006-2010 Ext JS, Inc.
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ux.PortalColumn = Ext.extend(Ext.Container, {
    layout : 'anchor',
    //autoEl : 'div',//already defined by Ext.Component
    defaultType : 'portlet',
    cls : 'x-portal-column'
});

Ext.reg('portalcolumn', Ext.ux.PortalColumn);
/*!
 * Ext JS Library 3.2.1
 * Copyright(c) 2006-2010 Ext JS, Inc.
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ux.Portlet = Ext.extend(Ext.Panel, {
    anchor : '100%',
    frame : true,
    collapsible : true,
    draggable : true,
    cls : 'x-portlet'
});

Ext.reg('portlet', Ext.ux.Portlet);
/*
 * Tine 2.0
 * 
 * @package     Tine
 * @subpackage  Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */

/**
 * NOTE: init.js is included before the tine2.0 code!
 */
 
/** --------------------- Ultra Generic JavaScript Stuff --------------------- **/

/**
 * create console pseudo object when firebug is disabled/not installed
 */
if (! window.console) window.console = {};
for (fn in {
        // maximum possible console functions based on firebug
        log: null , debug: null, info: null, warn: null, error: null, assert: null, dir: null, dirxml: null, group: null,
        groupEnd: null, time: null, timeEnd: null, count: null, trace: null, profile: null, profileEnd: null
    }) {
    window.console[fn] = window.console[fn] || function() {};
}

/** -------------------- Extjs Framework Initialisation -------------------- **/

/**
 * don't fill the ext stats
 */
Ext.BLANK_IMAGE_URL = "library/ExtJS/resources/images/default/s.gif";

/**
 * use empty image as secure url
 */
Ext.SSL_SECURE_URL = "library/ExtJS/resources/images/default/s.gif";

/**
 * don't fill yahoo stats
 */
Ext.chart.Chart.CHART_URL = 'library/ExtJS/resources/charts.swf';

/**
 * use native json implementation because we had problems with utf8 linebreaks (\u2028 for example)
 * @see http://www.tine20.org/bugtracker/view.php?id=3356
 * @note IE is principally capable to use native json, but for *some reason* it's not working properly
 *       so we don't use it for IE
 * @type Boolean
 */
Ext.USE_NATIVE_JSON = !Ext.isIE;

/**
 * init ext quick tips
 */
Ext.QuickTips.init();

/**
 * html encode all grid columns per default and convert spaces to &nbsp;
 */
Ext.grid.ColumnModel.defaultRenderer = Ext.util.Format.htmlEncode;
Ext.grid.Column.prototype.renderer = function(value) {
    var result = Ext.util.Format.htmlEncode(value);
    return result;
};

Ext.apply(Ext.data.JsonStore.prototype, {
    url:  'index.php',
    root: 'results',
    idProperty: 'id',
    totalProperty: 'totalcount'
});

/**
 * add more options to Ext.form.ComboBox
 */
Ext.form.ComboBox.prototype.initComponent = Ext.form.ComboBox.prototype.initComponent.createSequence(function() {
    if (this.expandOnFocus) {
        this.lazyInit = false;
        this.on('focus', function(){
            this.onTriggerClick();
        });
    }
    
    if (this.blurOnSelect){
        this.on('select', function(){
            this.blur(true);
            this.fireEvent('blur', this);
        }, this);
    }
});
Ext.form.ComboBox.prototype.triggerAction = 'all';


/**
 * additional date patterns
 * @see{Date}
 */
Date.patterns = {
    ISO8601Long:"Y-m-d H:i:s",
    ISO8601Short:"Y-m-d",
    ISO8601Time:"H:i:s",
    ShortDate: "n/j/Y",
    LongDate: "l, F d, Y",
    FullDateTime: "l, F d, Y g:i:s A",
    MonthDay: "F d",
    ShortTime: "g:i A",
    LongTime: "g:i:s A",
    SortableDateTime: "Y-m-d\\TH:i:s",
    UniversalSortableDateTime: "Y-m-d H:i:sO",
    YearMonth: "F, Y"
};

Ext.util.JSON.encodeDate = function(o){
    var pad = function(n) {
        return n < 10 ? "0" + n : n;
    };
    return '"' + o.getFullYear() + "-" +
        pad(o.getMonth() + 1) + "-" +
        pad(o.getDate()) + " " +
        pad(o.getHours()) + ":" +
        pad(o.getMinutes()) + ":" +
        pad(o.getSeconds()) + '"';
};

Date.prototype.toJSON = function(key) {
    var pad = function(n) {
        return n < 10 ? "0" + n : n;
    };
    return this.getFullYear() + "-" +
        pad(this.getMonth() + 1) + "-" +
        pad(this.getDate()) + " " +
        pad(this.getHours()) + ":" +
        pad(this.getMinutes()) + ":" +
        pad(this.getSeconds());
};

/**
 * additional formats
 */
Ext.util.Format = Ext.apply(Ext.util.Format, {
    euMoney: function(v){
        v.toString().replace(/,/, '.');
        
        v = (Math.round(parseFloat(v)*100))/100;
        
        v = (v == Math.floor(v)) ? v + ".00" : ((v*10 == Math.floor(v*10)) ? v + "0" : v);
        v = String(v);
        var ps = v.split('.');
        var whole = ps[0];
        var sub = ps[1] ? '.'+ ps[1] : '.00';
        var r = /(\d+)(\d{3})/;
        while (r.test(whole)) {
            whole = whole.replace(r, '$1' + '.' + '$2');
        }
        v = whole + sub;
        if(v.charAt(0) == '-'){
            return v.substr(1) + ' -€';
        }  
        return v + " €";
    },
    percentage: function(v){
        if(v === null) {
            return 'none';
        }
        if(!isNaN(v)) {
            return v + " %";
        } 
    },
    pad: function(v,l,s){
        if (!s) {
            s = '&nbsp;';
        }
        var plen = l-v.length;
        for (var i=0;i<plen;i++) {
            v += s;
        }
        return v;
   }
});
/**
 * @license     http://creativecommons.org/licenses/publicdomain Public Domain
 * @author      Koji Horaguchi <horaguchi@horaguchi.net>
 */
if (typeof Locale == 'undefined') {
  var Locale = function (category, locale) {
    this._instance = true;
    this.LC_ALL =      'C';
    this.LC_COLLATE =  'C';
    this.LC_CTYPE =    'C';
    this.LC_MESSAGES = 'C';
    this.LC_MONETARY = 'C';
    this.LC_NUMERIC =  'C';
    this.LC_TIME =     'C';
    this.setlocale(category, locale);
  };
}

Locale.VERSION = '0.0.3';
Locale.EXPORT = [
  'LC_ALL',
  'LC_COLLATE',
  'LC_CTYPE',
  'LC_MESSAGES',
  'LC_MONETARY',
  'LC_NUMERIC',
  'LC_TIME'
];
Locale.EXPORT_OK = [
  'setlocale'
];
Locale.EXPORT_TAGS = {
  ':common': Locale.EXPORT,
  ':all': Locale.EXPORT.concat(Locale.EXPORT_OK)
};

Locale.prototype.TranslationLists = {};

Locale.LC_ALL =      'LC_ALL';
Locale.LC_COLLATE =  'LC_COLLATE';
Locale.LC_CTYPE =    'LC_CTYPE';
Locale.LC_MESSAGES = 'LC_MESSAGES';
Locale.LC_MONETARY = 'LC_MONETARY';
Locale.LC_NUMERIC =  'LC_NUMERIC';
Locale.LC_TIME =     'LC_TIME';

Locale.setlocale = Locale.prototype.setlocale = function (category, locale) {
  return function () {
    if (locale === null || typeof locale == 'undefined') {
      return this[category];
    }

    if (locale == '') {
      locale = (window.navigator.browserLanguage || window.navigator.language || 'C')
        .replace(/^(.{2}).?(.{2})?.*$/, function (match, lang, terr) {
          return lang.toLowerCase() + (terr ? '_' + terr.toUpperCase() : '');
        });
    }

    switch (category) {
      case Locale.LC_ALL:
        this.LC_ALL      = locale;
        this.LC_COLLATE  = locale;
        this.LC_CTYPE    = locale;
        this.LC_MESSAGES = locale;
        this.LC_MONETARY = locale;
        this.LC_NUMERIC  = locale;
        this.LC_TIME     = locale;
        break;
      case Locale.LC_COLLATE:
      case Locale.LC_CTYPE:
      case Locale.LC_MESSAGES:
      case Locale.LC_MONETARY:
      case Locale.LC_NUMERIC:
      case Locale.LC_TIME:
        this[category] = locale;
        break;
      default:
        return false;
    }
    return locale;
  }.call(this._instance ? this : arguments.callee);
};

Locale.setlocale.LC_ALL =      'C';
Locale.setlocale.LC_COLLATE =  'C';
Locale.setlocale.LC_CTYPE =    'C';
Locale.setlocale.LC_MESSAGES = 'C';
Locale.setlocale.LC_MONETARY = 'C';
Locale.setlocale.LC_NUMERIC =  'C';
Locale.setlocale.LC_TIME =     'C';

/**
 * get translation data from generic locale object (partial clone of Zend Frameworks locale data)
 * 
 * @param   type (Date, Symbol, ...)
 * @param   key  the key
 * @return  translation data
 */
Locale.getTranslationData = function(type, key) {
    
    var value = '';
 
    if ( Locale.prototype.TranslationLists[type] && Locale.prototype.TranslationLists[type][key] ) {
        value = Locale.prototype.TranslationLists[type][key];
    }
    
    return value;
};

/**
 * get translation list from generic locale object (partial clone of Zend Frameworks locale data)
 * 
 * @param {String} type
 * @return {Object}
 */
Locale.getTranslationList = function(type) {
    return Locale.prototype.TranslationLists[type];
}
/**
 * @license     http://creativecommons.org/licenses/publicdomain Public Domain
 * @author      Koji Horaguchi <horaguchi@horaguchi.net>
 */
 
/* We don't support dynamic inclusion of po files any longer!
if (typeof ActiveXObject != 'undefined' && typeof XMLHttpRequest == 'undefined') {
  XMLHttpRequest = function () { try { return new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) { return new ActiveXObject("Microsoft.XMLHTTP"); } };
}
*/

/**
 * @constuctor
 */
Locale.Gettext = function (locale) {
    this.locale = typeof locale == 'string' ? new Locale(Locale.LC_ALL, locale) : locale || Locale;
    this.domain = 'messages';
    this.category = Locale.LC_MESSAGES;
    this.suffix = 'po';
    this.dir = '.';
};

Locale.Gettext.prototype.bindtextdomain = function (domain, dir) {
  this.dir = dir;
  this.domain = domain;
};

Locale.Gettext.prototype.textdomain = function (domain) {
  this.domain = domain;
};

Locale.Gettext.prototype.getmsg = function (domain, category, reload) {
  var key = this._getkey(category, domain);
  return Locale.Gettext.prototype._msgs[key];
};

Locale.Gettext.prototype._msgs = {};

Locale.Gettext.prototype._getkey = function(category, domain) {
    return this.dir + '/' + category + '/' + domain; // expect category is str
};

/*
Locale.Gettext.prototype._url = function (category, domain) {
 try {
    var req = new XMLHttpRequest;

    req.open('POST', 'index.php', false);
    req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    req.setRequestHeader('X-Tine20-Request-Type', 'JSON');
    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.send('method=Tinebase.getTranslations&requestType=JSON&application=' + domain + '&jsonKey=' + Tine.Tinebase.registry.get('jsonKey'));
    if (req.status == 200 || req.status == 304 || req.status == 0 || req.status == null) {
      return Ext.util.JSON.decode(req.responseText);
    }
  } catch (e) {
    return '';
  }
};
*/

Locale.Gettext.prototype.dcgettext = function (domain, msgid, category) {
  var msg = this.getmsg(domain, category);
  
  return msg ? msg.get(msgid) || msgid : msgid;
};

Locale.Gettext.prototype.dcngettext = function (domain, msgid, msgid_plural, n, category) {
  var msg = this.getmsg(domain, category);
  
  if (msg) {
    return (msg.get(msgid, msgid_plural) || [msgid, msgid_plural])[msg.plural(n)] || (n > 1 ? msgid_plural : msgid);
  } else {
    // fallback if cataloge is not available
    return n > 1 ? msgid_plural : msgid;
  }
};

Locale.Gettext.prototype.dgettext = function (domain, msgid) {
  return this.dcgettext(domain, msgid, this.category);
};

Locale.Gettext.prototype.dngettext = function (domain, msgid, msgid_plural, n) {
  return this.dcngettext(domain, msgid, msgid_plural, n, this.category);
};

Locale.Gettext.prototype.gettext = Locale.Gettext.prototype._ = Locale.Gettext.prototype._hidden = function (msgid) {
  return this.dcgettext(this.domain, msgid, this.category);
};

Locale.Gettext.prototype.ngettext = Locale.Gettext.prototype.n_ = Locale.Gettext.prototype.n_hidden = function (msgid, msgid_plural, n) {
  return this.dcngettext(this.domain, msgid, msgid_plural, n, this.category);
};

Locale.Gettext.prototype.gettext_noop = Locale.Gettext.prototype.N_ = function (msgid) {
  return msgid;
};

// extend object
(function () {
  for (var i in Locale.Gettext.prototype) {
    Locale.Gettext[i] = function (func) {
      return function () {
        return func.apply(Locale.Gettext, arguments);
      };
    }(Locale.Gettext.prototype[i]);
  }
})();

// Locale.Gettext.PO

if (typeof Locale.Gettext.PO == 'undefined') {
  Locale.Gettext.PO = function (object) {
    if (typeof object == 'string' || object instanceof String) {
      this.msg = Locale.Gettext.PO.po2object(object);
    } else if (object instanceof Object) {
      this.msg = object;
    } else {
      this.msg = {};
    }
  };
}

/*
Locale.Gettext.PO.VERSION = '0.0.4';
Locale.Gettext.PO.EXPORT_OK = [
  'po2object',
  'po2json'
];

Locale.Gettext.PO.po2object = function (po) {
  return eval(Locale.Gettext.PO.po2json(po));
};


Locale.Gettext.PO.po2json = function (po) {
  var first = true, plural = false;
  return '({\n' + po
    .replace(/\r?\n/g, '\n')
    .replace(/#.*\n/g, '')
    .replace(/"(\n+)"/g, '')
    .replace(/msgid "(.*?)"\nmsgid_plural "(.*?)"/g, 'msgid "$1, $2"')
    .replace(/msg(\S+) /g, function (match, op) {
      switch (op) {
      case 'id':
        return first
          ? (first = false, '')
          : plural
            ? (plural = false, ']\n, ')
            : ', ';
      case 'str':
        return ': ';
      case 'str[0]':
        return plural = true, ': [\n  ';
      default:
        return ' ,';
      }
    }) + (plural ? ']\n})' : '\n})');
};
*/

Locale.Gettext.PO.prototype.get = function (msgid, msgid_plural) {
  // for msgid_plural == ""
  return typeof msgid_plural != 'undefined' ? this.msg[msgid + ', ' + msgid_plural] : this.msg[msgid];
};

Locale.Gettext.PO.prototype.plural = function (n) {
  var nplurals, plural;
  eval((this.msg[''] + 'Plural-Forms: nplurals=2; plural=n != 1\n').match(/Plural-Forms:(.*)\n/)[1]);
  return plural === true
    ? 1
    : plural === false
      ? 0
      : plural;
};

// create dummy domain
Locale.Gettext.prototype._msgs.emptyDomain = new Locale.Gettext.PO(({}));
/*

  OpenLayers.js -- OpenLayers Map Viewer Library

  Copyright 2005-2008 MetaCarta, Inc., released under the Clear BSD license.
  Please see http://svn.openlayers.org/trunk/openlayers/license.txt
  for the full text of the license.

  Includes compressed code under the following licenses:

  (For uncompressed versions of the code used please see the
  OpenLayers SVN repository: <http://openlayers.org/>)

*/

/* Contains portions of Prototype.js:
 *
 * Prototype JavaScript framework, version 1.4.0
 *  (c) 2005 Sam Stephenson <sam@conio.net>
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://prototype.conio.net/
 *
 *--------------------------------------------------------------------------*/

/**  
*  
*  Contains portions of Rico <http://openrico.org/>
* 
*  Copyright 2005 Sabre Airline Solutions  
*  
*  Licensed under the Apache License, Version 2.0 (the "License"); you
*  may not use this file except in compliance with the License. You
*  may obtain a copy of the License at
*  
*         http://www.apache.org/licenses/LICENSE-2.0  
*  
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
*  implied. See the License for the specific language governing
*  permissions and limitations under the License. 
*
**/

/**
 * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
 * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 */

/**
 * Contains portions of Gears <http://code.google.com/apis/gears/>
 *
 * Copyright 2007, Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. Neither the name of Google Inc. nor the names of its contributors may be
 *     used to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Sets up google.gears.*, which is *the only* supported way to access Gears.
 *
 * Circumvent this file at your own risk!
 *
 * In the future, Gears may automatically define google.gears.* without this
 * file. Gears may use these objects to transparently fix bugs and compatibility
 * issues. Applications that use the code below will continue to work seamlessly
 * when that happens.
 */
var OpenLayers={singleFile:true};(function(){var singleFile=(typeof OpenLayers=="object"&&OpenLayers.singleFile);window.OpenLayers={_scriptName:(!singleFile)?"lib/OpenLayers.js":"OpenLayers.js",_getScriptLocation:function(){var scriptLocation="";var isOL=new RegExp("(^|(.*?\\/))("+OpenLayers._scriptName+")(\\?|$)");var scripts=document.getElementsByTagName('script');for(var i=0,len=scripts.length;i<len;i++){var src=scripts[i].getAttribute('src');if(src){var match=src.match(isOL);if(match){scriptLocation=match[1];break;}}}
return scriptLocation;}};if(!singleFile){var jsfiles=new Array("OpenLayers/Util.js","OpenLayers/BaseTypes.js","OpenLayers/BaseTypes/Class.js","OpenLayers/BaseTypes/Bounds.js","OpenLayers/BaseTypes/Element.js","OpenLayers/BaseTypes/LonLat.js","OpenLayers/BaseTypes/Pixel.js","OpenLayers/BaseTypes/Size.js","OpenLayers/Console.js","OpenLayers/Tween.js","Rico/Corner.js","Rico/Color.js","OpenLayers/Ajax.js","OpenLayers/Events.js","OpenLayers/Request.js","OpenLayers/Request/XMLHttpRequest.js","OpenLayers/Projection.js","OpenLayers/Map.js","OpenLayers/Layer.js","OpenLayers/Icon.js","OpenLayers/Marker.js","OpenLayers/Marker/Box.js","OpenLayers/Popup.js","OpenLayers/Tile.js","OpenLayers/Tile/Image.js","OpenLayers/Tile/WFS.js","OpenLayers/Layer/Image.js","OpenLayers/Layer/SphericalMercator.js","OpenLayers/Layer/EventPane.js","OpenLayers/Layer/FixedZoomLevels.js","OpenLayers/Layer/Google.js","OpenLayers/Layer/VirtualEarth.js","OpenLayers/Layer/Yahoo.js","OpenLayers/Layer/HTTPRequest.js","OpenLayers/Layer/Grid.js","OpenLayers/Layer/MapGuide.js","OpenLayers/Layer/MapServer.js","OpenLayers/Layer/MapServer/Untiled.js","OpenLayers/Layer/KaMap.js","OpenLayers/Layer/KaMapCache.js","OpenLayers/Layer/MultiMap.js","OpenLayers/Layer/Markers.js","OpenLayers/Layer/Text.js","OpenLayers/Layer/WorldWind.js","OpenLayers/Layer/ArcGIS93Rest.js","OpenLayers/Layer/WMS.js","OpenLayers/Layer/WMS/Untiled.js","OpenLayers/Layer/ArcIMS.js","OpenLayers/Layer/GeoRSS.js","OpenLayers/Layer/Boxes.js","OpenLayers/Layer/XYZ.js","OpenLayers/Layer/TMS.js","OpenLayers/Layer/TileCache.js","OpenLayers/Popup/Anchored.js","OpenLayers/Popup/AnchoredBubble.js","OpenLayers/Popup/Framed.js","OpenLayers/Popup/FramedCloud.js","OpenLayers/Feature.js","OpenLayers/Feature/Vector.js","OpenLayers/Feature/WFS.js","OpenLayers/Handler.js","OpenLayers/Handler/Click.js","OpenLayers/Handler/Hover.js","OpenLayers/Handler/Point.js","OpenLayers/Handler/Path.js","OpenLayers/Handler/Polygon.js","OpenLayers/Handler/Feature.js","OpenLayers/Handler/Drag.js","OpenLayers/Handler/RegularPolygon.js","OpenLayers/Handler/Box.js","OpenLayers/Handler/MouseWheel.js","OpenLayers/Handler/Keyboard.js","OpenLayers/Control.js","OpenLayers/Control/Attribution.js","OpenLayers/Control/Button.js","OpenLayers/Control/ZoomBox.js","OpenLayers/Control/ZoomToMaxExtent.js","OpenLayers/Control/DragPan.js","OpenLayers/Control/Navigation.js","OpenLayers/Control/MouseDefaults.js","OpenLayers/Control/MousePosition.js","OpenLayers/Control/OverviewMap.js","OpenLayers/Control/KeyboardDefaults.js","OpenLayers/Control/PanZoom.js","OpenLayers/Control/PanZoomBar.js","OpenLayers/Control/ArgParser.js","OpenLayers/Control/Permalink.js","OpenLayers/Control/Scale.js","OpenLayers/Control/ScaleLine.js","OpenLayers/Control/Snapping.js","OpenLayers/Control/Split.js","OpenLayers/Control/LayerSwitcher.js","OpenLayers/Control/DrawFeature.js","OpenLayers/Control/DragFeature.js","OpenLayers/Control/ModifyFeature.js","OpenLayers/Control/Panel.js","OpenLayers/Control/SelectFeature.js","OpenLayers/Control/NavigationHistory.js","OpenLayers/Control/Measure.js","OpenLayers/Control/WMSGetFeatureInfo.js","OpenLayers/Geometry.js","OpenLayers/Geometry/Rectangle.js","OpenLayers/Geometry/Collection.js","OpenLayers/Geometry/Point.js","OpenLayers/Geometry/MultiPoint.js","OpenLayers/Geometry/Curve.js","OpenLayers/Geometry/LineString.js","OpenLayers/Geometry/LinearRing.js","OpenLayers/Geometry/Polygon.js","OpenLayers/Geometry/MultiLineString.js","OpenLayers/Geometry/MultiPolygon.js","OpenLayers/Geometry/Surface.js","OpenLayers/Renderer.js","OpenLayers/Renderer/Elements.js","OpenLayers/Renderer/SVG.js","OpenLayers/Renderer/Canvas.js","OpenLayers/Renderer/VML.js","OpenLayers/Layer/Vector.js","OpenLayers/Layer/Vector/RootContainer.js","OpenLayers/Strategy.js","OpenLayers/Strategy/Fixed.js","OpenLayers/Strategy/Cluster.js","OpenLayers/Strategy/Paging.js","OpenLayers/Strategy/BBOX.js","OpenLayers/Strategy/Save.js","OpenLayers/Protocol.js","OpenLayers/Protocol/HTTP.js","OpenLayers/Protocol/SQL.js","OpenLayers/Protocol/SQL/Gears.js","OpenLayers/Protocol/WFS.js","OpenLayers/Protocol/WFS/v1.js","OpenLayers/Protocol/WFS/v1_0_0.js","OpenLayers/Protocol/WFS/v1_1_0.js","OpenLayers/Layer/PointTrack.js","OpenLayers/Layer/GML.js","OpenLayers/Style.js","OpenLayers/StyleMap.js","OpenLayers/Rule.js","OpenLayers/Filter.js","OpenLayers/Filter/FeatureId.js","OpenLayers/Filter/Logical.js","OpenLayers/Filter/Comparison.js","OpenLayers/Filter/Spatial.js","OpenLayers/Format.js","OpenLayers/Format/XML.js","OpenLayers/Format/ArcXML.js","OpenLayers/Format/ArcXML/Features.js","OpenLayers/Format/GML.js","OpenLayers/Format/GML/Base.js","OpenLayers/Format/GML/v2.js","OpenLayers/Format/GML/v3.js","OpenLayers/Format/KML.js","OpenLayers/Format/GeoRSS.js","OpenLayers/Format/WFS.js","OpenLayers/Format/WFSCapabilities.js","OpenLayers/Format/WFSCapabilities/v1.js","OpenLayers/Format/WFSCapabilities/v1_0_0.js","OpenLayers/Format/WFSCapabilities/v1_1_0.js","OpenLayers/Format/WFSDescribeFeatureType.js","OpenLayers/Format/WMSDescribeLayer.js","OpenLayers/Format/WMSDescribeLayer/v1_1.js","OpenLayers/Format/WKT.js","OpenLayers/Format/OSM.js","OpenLayers/Format/GPX.js","OpenLayers/Format/Filter.js","OpenLayers/Format/Filter/v1.js","OpenLayers/Format/Filter/v1_0_0.js","OpenLayers/Format/Filter/v1_1_0.js","OpenLayers/Format/SLD.js","OpenLayers/Format/SLD/v1.js","OpenLayers/Format/SLD/v1_0_0.js","OpenLayers/Format/SLD/v1.js","OpenLayers/Format/WFST.js","OpenLayers/Format/WFST/v1.js","OpenLayers/Format/WFST/v1_0_0.js","OpenLayers/Format/WFST/v1_1_0.js","OpenLayers/Format/Text.js","OpenLayers/Format/JSON.js","OpenLayers/Format/GeoJSON.js","OpenLayers/Format/WMC.js","OpenLayers/Format/WMC/v1.js","OpenLayers/Format/WMC/v1_0_0.js","OpenLayers/Format/WMC/v1_1_0.js","OpenLayers/Format/WMSCapabilities.js","OpenLayers/Format/WMSCapabilities/v1_1.js","OpenLayers/Format/WMSCapabilities/v1_1_0.js","OpenLayers/Format/WMSCapabilities/v1_1_1.js","OpenLayers/Format/WMSGetFeatureInfo.js","OpenLayers/Layer/WFS.js","OpenLayers/Control/GetFeature.js","OpenLayers/Control/MouseToolbar.js","OpenLayers/Control/NavToolbar.js","OpenLayers/Control/PanPanel.js","OpenLayers/Control/Pan.js","OpenLayers/Control/ZoomIn.js","OpenLayers/Control/ZoomOut.js","OpenLayers/Control/ZoomPanel.js","OpenLayers/Control/EditingToolbar.js","OpenLayers/Lang.js","OpenLayers/Lang/en.js");var agent=navigator.userAgent;var docWrite=(agent.match("MSIE")||agent.match("Safari"));if(docWrite){var allScriptTags=new Array(jsfiles.length);}
var host=OpenLayers._getScriptLocation()+"lib/";for(var i=0,len=jsfiles.length;i<len;i++){if(docWrite){allScriptTags[i]="<script src='"+host+jsfiles[i]+"'></script>";}else{var s=document.createElement("script");s.src=host+jsfiles[i];var h=document.getElementsByTagName("head").length?document.getElementsByTagName("head")[0]:document.body;h.appendChild(s);}}
if(docWrite){document.write(allScriptTags.join(""));}}})();OpenLayers.VERSION_NUMBER="OpenLayers 2.8 -- $Revision: 9492 $";OpenLayers.String={startsWith:function(str,sub){return(str.indexOf(sub)==0);},contains:function(str,sub){return(str.indexOf(sub)!=-1);},trim:function(str){return str.replace(/^\s\s*/,'').replace(/\s\s*$/,'');},camelize:function(str){var oStringList=str.split('-');var camelizedString=oStringList[0];for(var i=1,len=oStringList.length;i<len;i++){var s=oStringList[i];camelizedString+=s.charAt(0).toUpperCase()+s.substring(1);}
return camelizedString;},format:function(template,context,args){if(!context){context=window;}
var replacer=function(str,match){var replacement;var subs=match.split(/\.+/);for(var i=0;i<subs.length;i++){if(i==0){replacement=context;}
replacement=replacement[subs[i]];}
if(typeof replacement=="function"){replacement=args?replacement.apply(null,args):replacement();}
if(typeof replacement=='undefined'){return'undefined';}else{return replacement;}};return template.replace(OpenLayers.String.tokenRegEx,replacer);},tokenRegEx:/\$\{([\w.]+?)\}/g,numberRegEx:/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,isNumeric:function(value){return OpenLayers.String.numberRegEx.test(value);},numericIf:function(value){return OpenLayers.String.isNumeric(value)?parseFloat(value):value;}};if(!String.prototype.startsWith){String.prototype.startsWith=function(sStart){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.startsWith'}));return OpenLayers.String.startsWith(this,sStart);};}
if(!String.prototype.contains){String.prototype.contains=function(str){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.contains'}));return OpenLayers.String.contains(this,str);};}
if(!String.prototype.trim){String.prototype.trim=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.trim'}));return OpenLayers.String.trim(this);};}
if(!String.prototype.camelize){String.prototype.camelize=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.camelize'}));return OpenLayers.String.camelize(this);};}
OpenLayers.Number={decimalSeparator:".",thousandsSeparator:",",limitSigDigs:function(num,sig){var fig=0;if(sig>0){fig=parseFloat(num.toPrecision(sig));}
return fig;},format:function(num,dec,tsep,dsep){dec=(typeof dec!="undefined")?dec:0;tsep=(typeof tsep!="undefined")?tsep:OpenLayers.Number.thousandsSeparator;dsep=(typeof dsep!="undefined")?dsep:OpenLayers.Number.decimalSeparator;if(dec!=null){num=parseFloat(num.toFixed(dec));}
var parts=num.toString().split(".");if(parts.length==1&&dec==null){dec=0;}
var integer=parts[0];if(tsep){var thousands=/(-?[0-9]+)([0-9]{3})/;while(thousands.test(integer)){integer=integer.replace(thousands,"$1"+tsep+"$2");}}
var str;if(dec==0){str=integer;}else{var rem=parts.length>1?parts[1]:"0";if(dec!=null){rem=rem+new Array(dec-rem.length+1).join("0");}
str=integer+dsep+rem;}
return str;}};if(!Number.prototype.limitSigDigs){Number.prototype.limitSigDigs=function(sig){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Number.limitSigDigs'}));return OpenLayers.Number.limitSigDigs(this,sig);};}
OpenLayers.Function={bind:function(func,object){var args=Array.prototype.slice.apply(arguments,[2]);return function(){var newArgs=args.concat(Array.prototype.slice.apply(arguments,[0]));return func.apply(object,newArgs);};},bindAsEventListener:function(func,object){return function(event){return func.call(object,event||window.event);};}};if(!Function.prototype.bind){Function.prototype.bind=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Function.bind'}));Array.prototype.unshift.apply(arguments,[this]);return OpenLayers.Function.bind.apply(null,arguments);};}
if(!Function.prototype.bindAsEventListener){Function.prototype.bindAsEventListener=function(object){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Function.bindAsEventListener'}));return OpenLayers.Function.bindAsEventListener(this,object);};}
OpenLayers.Array={filter:function(array,callback,caller){var selected=[];if(Array.prototype.filter){selected=array.filter(callback,caller);}else{var len=array.length;if(typeof callback!="function"){throw new TypeError();}
for(var i=0;i<len;i++){if(i in array){var val=array[i];if(callback.call(caller,val,i,array)){selected.push(val);}}}}
return selected;}};OpenLayers.Class=function(){var Class=function(){if(arguments&&arguments[0]!=OpenLayers.Class.isPrototype){this.initialize.apply(this,arguments);}};var extended={};var parent,initialize;for(var i=0,len=arguments.length;i<len;++i){if(typeof arguments[i]=="function"){if(i==0&&len>1){initialize=arguments[i].prototype.initialize;arguments[i].prototype.initialize=function(){};extended=new arguments[i];if(initialize===undefined){delete arguments[i].prototype.initialize;}else{arguments[i].prototype.initialize=initialize;}}
parent=arguments[i].prototype;}else{parent=arguments[i];}
OpenLayers.Util.extend(extended,parent);}
Class.prototype=extended;return Class;};OpenLayers.Class.isPrototype=function(){};OpenLayers.Class.create=function(){return function(){if(arguments&&arguments[0]!=OpenLayers.Class.isPrototype){this.initialize.apply(this,arguments);}};};OpenLayers.Class.inherit=function(){var superClass=arguments[0];var proto=new superClass(OpenLayers.Class.isPrototype);for(var i=1,len=arguments.length;i<len;i++){if(typeof arguments[i]=="function"){var mixin=arguments[i];arguments[i]=new mixin(OpenLayers.Class.isPrototype);}
OpenLayers.Util.extend(proto,arguments[i]);}
return proto;};OpenLayers.Util={};OpenLayers.Util.getElement=function(){var elements=[];for(var i=0,len=arguments.length;i<len;i++){var element=arguments[i];if(typeof element=='string'){element=document.getElementById(element);}
if(arguments.length==1){return element;}
elements.push(element);}
return elements;};if(typeof window.$==="undefined"){window.$=OpenLayers.Util.getElement;}
OpenLayers.Util.extend=function(destination,source){destination=destination||{};if(source){for(var property in source){var value=source[property];if(value!==undefined){destination[property]=value;}}
var sourceIsEvt=typeof window.Event=="function"&&source instanceof window.Event;if(!sourceIsEvt&&source.hasOwnProperty&&source.hasOwnProperty('toString')){destination.toString=source.toString;}}
return destination;};OpenLayers.Util.removeItem=function(array,item){for(var i=array.length-1;i>=0;i--){if(array[i]==item){array.splice(i,1);}}
return array;};OpenLayers.Util.clearArray=function(array){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'array = []'}));array.length=0;};OpenLayers.Util.indexOf=function(array,obj){for(var i=0,len=array.length;i<len;i++){if(array[i]==obj){return i;}}
return-1;};OpenLayers.Util.modifyDOMElement=function(element,id,px,sz,position,border,overflow,opacity){if(id){element.id=id;}
if(px){element.style.left=px.x+"px";element.style.top=px.y+"px";}
if(sz){element.style.width=sz.w+"px";element.style.height=sz.h+"px";}
if(position){element.style.position=position;}
if(border){element.style.border=border;}
if(overflow){element.style.overflow=overflow;}
if(parseFloat(opacity)>=0.0&&parseFloat(opacity)<1.0){element.style.filter='alpha(opacity='+(opacity*100)+')';element.style.opacity=opacity;}else if(parseFloat(opacity)==1.0){element.style.filter='';element.style.opacity='';}};OpenLayers.Util.createDiv=function(id,px,sz,imgURL,position,border,overflow,opacity){var dom=document.createElement('div');if(imgURL){dom.style.backgroundImage='url('+imgURL+')';}
if(!id){id=OpenLayers.Util.createUniqueID("OpenLayersDiv");}
if(!position){position="absolute";}
OpenLayers.Util.modifyDOMElement(dom,id,px,sz,position,border,overflow,opacity);return dom;};OpenLayers.Util.createImage=function(id,px,sz,imgURL,position,border,opacity,delayDisplay){var image=document.createElement("img");if(!id){id=OpenLayers.Util.createUniqueID("OpenLayersDiv");}
if(!position){position="relative";}
OpenLayers.Util.modifyDOMElement(image,id,px,sz,position,border,null,opacity);if(delayDisplay){image.style.display="none";OpenLayers.Event.observe(image,"load",OpenLayers.Function.bind(OpenLayers.Util.onImageLoad,image));OpenLayers.Event.observe(image,"error",OpenLayers.Function.bind(OpenLayers.Util.onImageLoadError,image));}
image.style.alt=id;image.galleryImg="no";if(imgURL){image.src=imgURL;}
return image;};OpenLayers.Util.setOpacity=function(element,opacity){OpenLayers.Util.modifyDOMElement(element,null,null,null,null,null,null,opacity);};OpenLayers.Util.onImageLoad=function(){if(!this.viewRequestID||(this.map&&this.viewRequestID==this.map.viewRequestID)){this.style.backgroundColor="transparent";this.style.display="";}};OpenLayers.Util.onImageLoadErrorColor="pink";OpenLayers.IMAGE_RELOAD_ATTEMPTS=0;OpenLayers.Util.onImageLoadError=function(){this._attempts=(this._attempts)?(this._attempts+1):1;if(this._attempts<=OpenLayers.IMAGE_RELOAD_ATTEMPTS){var urls=this.urls;if(urls&&urls instanceof Array&&urls.length>1){var src=this.src.toString();var current_url,k;for(k=0;current_url=urls[k];k++){if(src.indexOf(current_url)!=-1){break;}}
var guess=Math.floor(urls.length*Math.random());var new_url=urls[guess];k=0;while(new_url==current_url&&k++<4){guess=Math.floor(urls.length*Math.random());new_url=urls[guess];}
this.src=src.replace(current_url,new_url);}else{this.src=this.src;}}else{this.style.backgroundColor=OpenLayers.Util.onImageLoadErrorColor;}
this.style.display="";};OpenLayers.Util.alphaHackNeeded=null;OpenLayers.Util.alphaHack=function(){if(OpenLayers.Util.alphaHackNeeded==null){var arVersion=navigator.appVersion.split("MSIE");var version=parseFloat(arVersion[1]);var filter=false;try{filter=!!(document.body.filters);}catch(e){}
OpenLayers.Util.alphaHackNeeded=(filter&&(version>=5.5)&&(version<7));}
return OpenLayers.Util.alphaHackNeeded;};OpenLayers.Util.modifyAlphaImageDiv=function(div,id,px,sz,imgURL,position,border,sizing,opacity){OpenLayers.Util.modifyDOMElement(div,id,px,sz,position,null,null,opacity);var img=div.childNodes[0];if(imgURL){img.src=imgURL;}
OpenLayers.Util.modifyDOMElement(img,div.id+"_innerImage",null,sz,"relative",border);if(OpenLayers.Util.alphaHack()){if(div.style.display!="none"){div.style.display="inline-block";}
if(sizing==null){sizing="scale";}
div.style.filter="progid:DXImageTransform.Microsoft"+".AlphaImageLoader(src='"+img.src+"', "+"sizingMethod='"+sizing+"')";if(parseFloat(div.style.opacity)>=0.0&&parseFloat(div.style.opacity)<1.0){div.style.filter+=" alpha(opacity="+div.style.opacity*100+")";}
img.style.filter="alpha(opacity=0)";}};OpenLayers.Util.createAlphaImageDiv=function(id,px,sz,imgURL,position,border,sizing,opacity,delayDisplay){var div=OpenLayers.Util.createDiv();var img=OpenLayers.Util.createImage(null,null,null,null,null,null,null,false);div.appendChild(img);if(delayDisplay){img.style.display="none";OpenLayers.Event.observe(img,"load",OpenLayers.Function.bind(OpenLayers.Util.onImageLoad,div));OpenLayers.Event.observe(img,"error",OpenLayers.Function.bind(OpenLayers.Util.onImageLoadError,div));}
OpenLayers.Util.modifyAlphaImageDiv(div,id,px,sz,imgURL,position,border,sizing,opacity);return div;};OpenLayers.Util.upperCaseObject=function(object){var uObject={};for(var key in object){uObject[key.toUpperCase()]=object[key];}
return uObject;};OpenLayers.Util.applyDefaults=function(to,from){to=to||{};var fromIsEvt=typeof window.Event=="function"&&from instanceof window.Event;for(var key in from){if(to[key]===undefined||(!fromIsEvt&&from.hasOwnProperty&&from.hasOwnProperty(key)&&!to.hasOwnProperty(key))){to[key]=from[key];}}
if(!fromIsEvt&&from&&from.hasOwnProperty&&from.hasOwnProperty('toString')&&!to.hasOwnProperty('toString')){to.toString=from.toString;}
return to;};OpenLayers.Util.getParameterString=function(params){var paramsArray=[];for(var key in params){var value=params[key];if((value!=null)&&(typeof value!='function')){var encodedValue;if(typeof value=='object'&&value.constructor==Array){var encodedItemArray=[];for(var itemIndex=0,len=value.length;itemIndex<len;itemIndex++){encodedItemArray.push(encodeURIComponent(value[itemIndex]));}
encodedValue=encodedItemArray.join(",");}
else{encodedValue=encodeURIComponent(value);}
paramsArray.push(encodeURIComponent(key)+"="+encodedValue);}}
return paramsArray.join("&");};OpenLayers.ImgPath='';OpenLayers.Util.getImagesLocation=function(){return OpenLayers.ImgPath||(OpenLayers._getScriptLocation()+"img/");};OpenLayers.Util.Try=function(){var returnValue=null;for(var i=0,len=arguments.length;i<len;i++){var lambda=arguments[i];try{returnValue=lambda();break;}catch(e){}}
return returnValue;};OpenLayers.Util.getNodes=function(p,tagName){var nodes=OpenLayers.Util.Try(function(){return OpenLayers.Util._getNodes(p.documentElement.childNodes,tagName);},function(){return OpenLayers.Util._getNodes(p.childNodes,tagName);});return nodes;};OpenLayers.Util._getNodes=function(nodes,tagName){var retArray=[];for(var i=0,len=nodes.length;i<len;i++){if(nodes[i].nodeName==tagName){retArray.push(nodes[i]);}}
return retArray;};OpenLayers.Util.getTagText=function(parent,item,index){var result=OpenLayers.Util.getNodes(parent,item);if(result&&(result.length>0))
{if(!index){index=0;}
if(result[index].childNodes.length>1){return result.childNodes[1].nodeValue;}
else if(result[index].childNodes.length==1){return result[index].firstChild.nodeValue;}}else{return"";}};OpenLayers.Util.getXmlNodeValue=function(node){var val=null;OpenLayers.Util.Try(function(){val=node.text;if(!val){val=node.textContent;}
if(!val){val=node.firstChild.nodeValue;}},function(){val=node.textContent;});return val;};OpenLayers.Util.mouseLeft=function(evt,div){var target=(evt.relatedTarget)?evt.relatedTarget:evt.toElement;while(target!=div&&target!=null){target=target.parentNode;}
return(target!=div);};OpenLayers.Util.DEFAULT_PRECISION=14;OpenLayers.Util.toFloat=function(number,precision){if(precision==null){precision=OpenLayers.Util.DEFAULT_PRECISION;}
var number;if(precision==0){number=parseFloat(number);}else{number=parseFloat(parseFloat(number).toPrecision(precision));}
return number;};OpenLayers.Util.rad=function(x){return x*Math.PI/180;};OpenLayers.Util.distVincenty=function(p1,p2){var a=6378137,b=6356752.3142,f=1/298.257223563;var L=OpenLayers.Util.rad(p2.lon-p1.lon);var U1=Math.atan((1-f)*Math.tan(OpenLayers.Util.rad(p1.lat)));var U2=Math.atan((1-f)*Math.tan(OpenLayers.Util.rad(p2.lat)));var sinU1=Math.sin(U1),cosU1=Math.cos(U1);var sinU2=Math.sin(U2),cosU2=Math.cos(U2);var lambda=L,lambdaP=2*Math.PI;var iterLimit=20;while(Math.abs(lambda-lambdaP)>1e-12&&--iterLimit>0){var sinLambda=Math.sin(lambda),cosLambda=Math.cos(lambda);var sinSigma=Math.sqrt((cosU2*sinLambda)*(cosU2*sinLambda)+
(cosU1*sinU2-sinU1*cosU2*cosLambda)*(cosU1*sinU2-sinU1*cosU2*cosLambda));if(sinSigma==0){return 0;}
var cosSigma=sinU1*sinU2+cosU1*cosU2*cosLambda;var sigma=Math.atan2(sinSigma,cosSigma);var alpha=Math.asin(cosU1*cosU2*sinLambda/sinSigma);var cosSqAlpha=Math.cos(alpha)*Math.cos(alpha);var cos2SigmaM=cosSigma-2*sinU1*sinU2/cosSqAlpha;var C=f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));lambdaP=lambda;lambda=L+(1-C)*f*Math.sin(alpha)*(sigma+C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));}
if(iterLimit==0){return NaN;}
var uSq=cosSqAlpha*(a*a-b*b)/(b*b);var A=1+uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));var B=uSq/1024*(256+uSq*(-128+uSq*(74-47*uSq)));var deltaSigma=B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));var s=b*A*(sigma-deltaSigma);var d=s.toFixed(3)/1000;return d;};OpenLayers.Util.getParameters=function(url){url=url||window.location.href;var paramsString="";if(OpenLayers.String.contains(url,'?')){var start=url.indexOf('?')+1;var end=OpenLayers.String.contains(url,"#")?url.indexOf('#'):url.length;paramsString=url.substring(start,end);}
var parameters={};var pairs=paramsString.split(/[&;]/);for(var i=0,len=pairs.length;i<len;++i){var keyValue=pairs[i].split('=');if(keyValue[0]){var key=decodeURIComponent(keyValue[0]);var value=keyValue[1]||'';value=value.split(",");for(var j=0,jlen=value.length;j<jlen;j++){value[j]=decodeURIComponent(value[j]);}
if(value.length==1){value=value[0];}
parameters[key]=value;}}
return parameters;};OpenLayers.Util.getArgs=function(url){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Util.getParameters'}));return OpenLayers.Util.getParameters(url);};OpenLayers.Util.lastSeqID=0;OpenLayers.Util.createUniqueID=function(prefix){if(prefix==null){prefix="id_";}
OpenLayers.Util.lastSeqID+=1;return prefix+OpenLayers.Util.lastSeqID;};OpenLayers.INCHES_PER_UNIT={'inches':1.0,'ft':12.0,'mi':63360.0,'m':39.3701,'km':39370.1,'dd':4374754,'yd':36};OpenLayers.INCHES_PER_UNIT["in"]=OpenLayers.INCHES_PER_UNIT.inches;OpenLayers.INCHES_PER_UNIT["degrees"]=OpenLayers.INCHES_PER_UNIT.dd;OpenLayers.INCHES_PER_UNIT["nmi"]=1852*OpenLayers.INCHES_PER_UNIT.m;OpenLayers.METERS_PER_INCH=0.02540005080010160020;OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{"Inch":OpenLayers.INCHES_PER_UNIT.inches,"Meter":1.0/OpenLayers.METERS_PER_INCH,"Foot":0.30480060960121920243/OpenLayers.METERS_PER_INCH,"IFoot":0.30480000000000000000/OpenLayers.METERS_PER_INCH,"ClarkeFoot":0.3047972651151/OpenLayers.METERS_PER_INCH,"SearsFoot":0.30479947153867624624/OpenLayers.METERS_PER_INCH,"GoldCoastFoot":0.30479971018150881758/OpenLayers.METERS_PER_INCH,"IInch":0.02540000000000000000/OpenLayers.METERS_PER_INCH,"MicroInch":0.00002540000000000000/OpenLayers.METERS_PER_INCH,"Mil":0.00000002540000000000/OpenLayers.METERS_PER_INCH,"Centimeter":0.01000000000000000000/OpenLayers.METERS_PER_INCH,"Kilometer":1000.00000000000000000000/OpenLayers.METERS_PER_INCH,"Yard":0.91440182880365760731/OpenLayers.METERS_PER_INCH,"SearsYard":0.914398414616029/OpenLayers.METERS_PER_INCH,"IndianYard":0.91439853074444079983/OpenLayers.METERS_PER_INCH,"IndianYd37":0.91439523/OpenLayers.METERS_PER_INCH,"IndianYd62":0.9143988/OpenLayers.METERS_PER_INCH,"IndianYd75":0.9143985/OpenLayers.METERS_PER_INCH,"IndianFoot":0.30479951/OpenLayers.METERS_PER_INCH,"IndianFt37":0.30479841/OpenLayers.METERS_PER_INCH,"IndianFt62":0.3047996/OpenLayers.METERS_PER_INCH,"IndianFt75":0.3047995/OpenLayers.METERS_PER_INCH,"Mile":1609.34721869443738887477/OpenLayers.METERS_PER_INCH,"IYard":0.91440000000000000000/OpenLayers.METERS_PER_INCH,"IMile":1609.34400000000000000000/OpenLayers.METERS_PER_INCH,"NautM":1852.00000000000000000000/OpenLayers.METERS_PER_INCH,"Lat-66":110943.316488932731/OpenLayers.METERS_PER_INCH,"Lat-83":110946.25736872234125/OpenLayers.METERS_PER_INCH,"Decimeter":0.10000000000000000000/OpenLayers.METERS_PER_INCH,"Millimeter":0.00100000000000000000/OpenLayers.METERS_PER_INCH,"Dekameter":10.00000000000000000000/OpenLayers.METERS_PER_INCH,"Decameter":10.00000000000000000000/OpenLayers.METERS_PER_INCH,"Hectometer":100.00000000000000000000/OpenLayers.METERS_PER_INCH,"GermanMeter":1.0000135965/OpenLayers.METERS_PER_INCH,"CaGrid":0.999738/OpenLayers.METERS_PER_INCH,"ClarkeChain":20.1166194976/OpenLayers.METERS_PER_INCH,"GunterChain":20.11684023368047/OpenLayers.METERS_PER_INCH,"BenoitChain":20.116782494375872/OpenLayers.METERS_PER_INCH,"SearsChain":20.11676512155/OpenLayers.METERS_PER_INCH,"ClarkeLink":0.201166194976/OpenLayers.METERS_PER_INCH,"GunterLink":0.2011684023368047/OpenLayers.METERS_PER_INCH,"BenoitLink":0.20116782494375872/OpenLayers.METERS_PER_INCH,"SearsLink":0.2011676512155/OpenLayers.METERS_PER_INCH,"Rod":5.02921005842012/OpenLayers.METERS_PER_INCH,"IntnlChain":20.1168/OpenLayers.METERS_PER_INCH,"IntnlLink":0.201168/OpenLayers.METERS_PER_INCH,"Perch":5.02921005842012/OpenLayers.METERS_PER_INCH,"Pole":5.02921005842012/OpenLayers.METERS_PER_INCH,"Furlong":201.1684023368046/OpenLayers.METERS_PER_INCH,"Rood":3.778266898/OpenLayers.METERS_PER_INCH,"CapeFoot":0.3047972615/OpenLayers.METERS_PER_INCH,"Brealey":375.00000000000000000000/OpenLayers.METERS_PER_INCH,"ModAmFt":0.304812252984505969011938/OpenLayers.METERS_PER_INCH,"Fathom":1.8288/OpenLayers.METERS_PER_INCH,"NautM-UK":1853.184/OpenLayers.METERS_PER_INCH,"50kilometers":50000.0/OpenLayers.METERS_PER_INCH,"150kilometers":150000.0/OpenLayers.METERS_PER_INCH});OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{"mm":OpenLayers.INCHES_PER_UNIT["Meter"]/1000.0,"cm":OpenLayers.INCHES_PER_UNIT["Meter"]/100.0,"dm":OpenLayers.INCHES_PER_UNIT["Meter"]*100.0,"km":OpenLayers.INCHES_PER_UNIT["Meter"]*1000.0,"kmi":OpenLayers.INCHES_PER_UNIT["nmi"],"fath":OpenLayers.INCHES_PER_UNIT["Fathom"],"ch":OpenLayers.INCHES_PER_UNIT["IntnlChain"],"link":OpenLayers.INCHES_PER_UNIT["IntnlLink"],"us-in":OpenLayers.INCHES_PER_UNIT["inches"],"us-ft":OpenLayers.INCHES_PER_UNIT["Foot"],"us-yd":OpenLayers.INCHES_PER_UNIT["Yard"],"us-ch":OpenLayers.INCHES_PER_UNIT["GunterChain"],"us-mi":OpenLayers.INCHES_PER_UNIT["Mile"],"ind-yd":OpenLayers.INCHES_PER_UNIT["IndianYd37"],"ind-ft":OpenLayers.INCHES_PER_UNIT["IndianFt37"],"ind-ch":20.11669506/OpenLayers.METERS_PER_INCH});OpenLayers.DOTS_PER_INCH=72;OpenLayers.Util.normalizeScale=function(scale){var normScale=(scale>1.0)?(1.0/scale):scale;return normScale;};OpenLayers.Util.getResolutionFromScale=function(scale,units){if(units==null){units="degrees";}
var normScale=OpenLayers.Util.normalizeScale(scale);var resolution=1/(normScale*OpenLayers.INCHES_PER_UNIT[units]*OpenLayers.DOTS_PER_INCH);return resolution;};OpenLayers.Util.getScaleFromResolution=function(resolution,units){if(units==null){units="degrees";}
var scale=resolution*OpenLayers.INCHES_PER_UNIT[units]*OpenLayers.DOTS_PER_INCH;return scale;};OpenLayers.Util.safeStopPropagation=function(evt){OpenLayers.Event.stop(evt,true);};OpenLayers.Util.pagePosition=function(forElement){var valueT=0,valueL=0;var element=forElement;var child=forElement;while(element){if(element==document.body){if(OpenLayers.Element.getStyle(child,'position')=='absolute'){break;}}
valueT+=element.offsetTop||0;valueL+=element.offsetLeft||0;child=element;try{element=element.offsetParent;}catch(e){OpenLayers.Console.error(OpenLayers.i18n("pagePositionFailed",{'elemId':element.id}));break;}}
element=forElement;while(element){valueT-=element.scrollTop||0;valueL-=element.scrollLeft||0;element=element.parentNode;}
return[valueL,valueT];};OpenLayers.Util.isEquivalentUrl=function(url1,url2,options){options=options||{};OpenLayers.Util.applyDefaults(options,{ignoreCase:true,ignorePort80:true,ignoreHash:true});var urlObj1=OpenLayers.Util.createUrlObject(url1,options);var urlObj2=OpenLayers.Util.createUrlObject(url2,options);for(var key in urlObj1){if(key!=="args"){if(urlObj1[key]!=urlObj2[key]){return false;}}}
for(var key in urlObj1.args){if(urlObj1.args[key]!=urlObj2.args[key]){return false;}
delete urlObj2.args[key];}
for(var key in urlObj2.args){return false;}
return true;};OpenLayers.Util.createUrlObject=function(url,options){options=options||{};if(!(/^\w+:\/\//).test(url)){var loc=window.location;var port=loc.port?":"+loc.port:"";var fullUrl=loc.protocol+"//"+loc.host.split(":").shift()+port;if(url.indexOf("/")===0){url=fullUrl+url;}else{var parts=loc.pathname.split("/");parts.pop();url=fullUrl+parts.join("/")+"/"+url;}}
if(options.ignoreCase){url=url.toLowerCase();}
var a=document.createElement('a');a.href=url;var urlObject={};urlObject.host=a.host.split(":").shift();urlObject.protocol=a.protocol;if(options.ignorePort80){urlObject.port=(a.port=="80"||a.port=="0")?"":a.port;}else{urlObject.port=(a.port==""||a.port=="0")?"80":a.port;}
urlObject.hash=(options.ignoreHash||a.hash==="#")?"":a.hash;var queryString=a.search;if(!queryString){var qMark=url.indexOf("?");queryString=(qMark!=-1)?url.substr(qMark):"";}
urlObject.args=OpenLayers.Util.getParameters(queryString);urlObject.pathname=(a.pathname.charAt(0)=="/")?a.pathname:"/"+a.pathname;return urlObject;};OpenLayers.Util.removeTail=function(url){var head=null;var qMark=url.indexOf("?");var hashMark=url.indexOf("#");if(qMark==-1){head=(hashMark!=-1)?url.substr(0,hashMark):url;}else{head=(hashMark!=-1)?url.substr(0,Math.min(qMark,hashMark)):url.substr(0,qMark);}
return head;};OpenLayers.Util.getBrowserName=function(){var browserName="";var ua=navigator.userAgent.toLowerCase();if(ua.indexOf("opera")!=-1){browserName="opera";}else if(ua.indexOf("msie")!=-1){browserName="msie";}else if(ua.indexOf("safari")!=-1){browserName="safari";}else if(ua.indexOf("mozilla")!=-1){if(ua.indexOf("firefox")!=-1){browserName="firefox";}else{browserName="mozilla";}}
return browserName;};OpenLayers.Util.getRenderedDimensions=function(contentHTML,size,options){var w,h;var container=document.createElement("div");container.style.visibility="hidden";var containerElement=(options&&options.containerElement)?options.containerElement:document.body;if(size){if(size.w){w=size.w;container.style.width=w+"px";}else if(size.h){h=size.h;container.style.height=h+"px";}}
if(options&&options.displayClass){container.className=options.displayClass;}
var content=document.createElement("div");content.innerHTML=contentHTML;content.style.overflow="visible";if(content.childNodes){for(var i=0,l=content.childNodes.length;i<l;i++){if(!content.childNodes[i].style)continue;content.childNodes[i].style.overflow="visible";}}
container.appendChild(content);containerElement.appendChild(container);var parentHasPositionAbsolute=false;var parent=container.parentNode;while(parent&&parent.tagName.toLowerCase()!="body"){var parentPosition=OpenLayers.Element.getStyle(parent,"position");if(parentPosition=="absolute"){parentHasPositionAbsolute=true;break;}else if(parentPosition&&parentPosition!="static"){break;}
parent=parent.parentNode;}
if(!parentHasPositionAbsolute){container.style.position="absolute";}
if(!w){w=parseInt(content.scrollWidth);container.style.width=w+"px";}
if(!h){h=parseInt(content.scrollHeight);}
container.removeChild(content);containerElement.removeChild(container);return new OpenLayers.Size(w,h);};OpenLayers.Util.getScrollbarWidth=function(){var scrollbarWidth=OpenLayers.Util._scrollbarWidth;if(scrollbarWidth==null){var scr=null;var inn=null;var wNoScroll=0;var wScroll=0;scr=document.createElement('div');scr.style.position='absolute';scr.style.top='-1000px';scr.style.left='-1000px';scr.style.width='100px';scr.style.height='50px';scr.style.overflow='hidden';inn=document.createElement('div');inn.style.width='100%';inn.style.height='200px';scr.appendChild(inn);document.body.appendChild(scr);wNoScroll=inn.offsetWidth;scr.style.overflow='scroll';wScroll=inn.offsetWidth;document.body.removeChild(document.body.lastChild);OpenLayers.Util._scrollbarWidth=(wNoScroll-wScroll);scrollbarWidth=OpenLayers.Util._scrollbarWidth;}
return scrollbarWidth;};OpenLayers.Rico=new Object();OpenLayers.Rico.Corner={round:function(e,options){e=OpenLayers.Util.getElement(e);this._setOptions(options);var color=this.options.color;if(this.options.color=="fromElement"){color=this._background(e);}
var bgColor=this.options.bgColor;if(this.options.bgColor=="fromParent"){bgColor=this._background(e.offsetParent);}
this._roundCornersImpl(e,color,bgColor);},changeColor:function(theDiv,newColor){theDiv.style.backgroundColor=newColor;var spanElements=theDiv.parentNode.getElementsByTagName("span");for(var currIdx=0;currIdx<spanElements.length;currIdx++){spanElements[currIdx].style.backgroundColor=newColor;}},changeOpacity:function(theDiv,newOpacity){var mozillaOpacity=newOpacity;var ieOpacity='alpha(opacity='+newOpacity*100+')';theDiv.style.opacity=mozillaOpacity;theDiv.style.filter=ieOpacity;var spanElements=theDiv.parentNode.getElementsByTagName("span");for(var currIdx=0;currIdx<spanElements.length;currIdx++){spanElements[currIdx].style.opacity=mozillaOpacity;spanElements[currIdx].style.filter=ieOpacity;}},reRound:function(theDiv,options){var topRico=theDiv.parentNode.childNodes[0];var bottomRico=theDiv.parentNode.childNodes[2];theDiv.parentNode.removeChild(topRico);theDiv.parentNode.removeChild(bottomRico);this.round(theDiv.parentNode,options);},_roundCornersImpl:function(e,color,bgColor){if(this.options.border){this._renderBorder(e,bgColor);}
if(this._isTopRounded()){this._roundTopCorners(e,color,bgColor);}
if(this._isBottomRounded()){this._roundBottomCorners(e,color,bgColor);}},_renderBorder:function(el,bgColor){var borderValue="1px solid "+this._borderColor(bgColor);var borderL="border-left: "+borderValue;var borderR="border-right: "+borderValue;var style="style='"+borderL+";"+borderR+"'";el.innerHTML="<div "+style+">"+el.innerHTML+"</div>";},_roundTopCorners:function(el,color,bgColor){var corner=this._createCorner(bgColor);for(var i=0;i<this.options.numSlices;i++){corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));}
el.style.paddingTop=0;el.insertBefore(corner,el.firstChild);},_roundBottomCorners:function(el,color,bgColor){var corner=this._createCorner(bgColor);for(var i=(this.options.numSlices-1);i>=0;i--){corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));}
el.style.paddingBottom=0;el.appendChild(corner);},_createCorner:function(bgColor){var corner=document.createElement("div");corner.style.backgroundColor=(this._isTransparent()?"transparent":bgColor);return corner;},_createCornerSlice:function(color,bgColor,n,position){var slice=document.createElement("span");var inStyle=slice.style;inStyle.backgroundColor=color;inStyle.display="block";inStyle.height="1px";inStyle.overflow="hidden";inStyle.fontSize="1px";var borderColor=this._borderColor(color,bgColor);if(this.options.border&&n==0){inStyle.borderTopStyle="solid";inStyle.borderTopWidth="1px";inStyle.borderLeftWidth="0px";inStyle.borderRightWidth="0px";inStyle.borderBottomWidth="0px";inStyle.height="0px";inStyle.borderColor=borderColor;}
else if(borderColor){inStyle.borderColor=borderColor;inStyle.borderStyle="solid";inStyle.borderWidth="0px 1px";}
if(!this.options.compact&&(n==(this.options.numSlices-1))){inStyle.height="2px";}
this._setMargin(slice,n,position);this._setBorder(slice,n,position);return slice;},_setOptions:function(options){this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false};OpenLayers.Util.extend(this.options,options||{});this.options.numSlices=this.options.compact?2:4;if(this._isTransparent()){this.options.blend=false;}},_whichSideTop:function(){if(this._hasString(this.options.corners,"all","top")){return"";}
if(this.options.corners.indexOf("tl")>=0&&this.options.corners.indexOf("tr")>=0){return"";}
if(this.options.corners.indexOf("tl")>=0){return"left";}else if(this.options.corners.indexOf("tr")>=0){return"right";}
return"";},_whichSideBottom:function(){if(this._hasString(this.options.corners,"all","bottom")){return"";}
if(this.options.corners.indexOf("bl")>=0&&this.options.corners.indexOf("br")>=0){return"";}
if(this.options.corners.indexOf("bl")>=0){return"left";}else if(this.options.corners.indexOf("br")>=0){return"right";}
return"";},_borderColor:function(color,bgColor){if(color=="transparent"){return bgColor;}else if(this.options.border){return this.options.border;}else if(this.options.blend){return this._blend(bgColor,color);}else{return"";}},_setMargin:function(el,n,corners){var marginSize=this._marginSize(n);var whichSide=corners=="top"?this._whichSideTop():this._whichSideBottom();if(whichSide=="left"){el.style.marginLeft=marginSize+"px";el.style.marginRight="0px";}
else if(whichSide=="right"){el.style.marginRight=marginSize+"px";el.style.marginLeft="0px";}
else{el.style.marginLeft=marginSize+"px";el.style.marginRight=marginSize+"px";}},_setBorder:function(el,n,corners){var borderSize=this._borderSize(n);var whichSide=corners=="top"?this._whichSideTop():this._whichSideBottom();if(whichSide=="left"){el.style.borderLeftWidth=borderSize+"px";el.style.borderRightWidth="0px";}
else if(whichSide=="right"){el.style.borderRightWidth=borderSize+"px";el.style.borderLeftWidth="0px";}
else{el.style.borderLeftWidth=borderSize+"px";el.style.borderRightWidth=borderSize+"px";}
if(this.options.border!=false){el.style.borderLeftWidth=borderSize+"px";el.style.borderRightWidth=borderSize+"px";}},_marginSize:function(n){if(this._isTransparent()){return 0;}
var marginSizes=[5,3,2,1];var blendedMarginSizes=[3,2,1,0];var compactMarginSizes=[2,1];var smBlendedMarginSizes=[1,0];if(this.options.compact&&this.options.blend){return smBlendedMarginSizes[n];}else if(this.options.compact){return compactMarginSizes[n];}else if(this.options.blend){return blendedMarginSizes[n];}else{return marginSizes[n];}},_borderSize:function(n){var transparentBorderSizes=[5,3,2,1];var blendedBorderSizes=[2,1,1,1];var compactBorderSizes=[1,0];var actualBorderSizes=[0,2,0,0];if(this.options.compact&&(this.options.blend||this._isTransparent())){return 1;}else if(this.options.compact){return compactBorderSizes[n];}else if(this.options.blend){return blendedBorderSizes[n];}else if(this.options.border){return actualBorderSizes[n];}else if(this._isTransparent()){return transparentBorderSizes[n];}
return 0;},_hasString:function(str){for(var i=1;i<arguments.length;i++)if(str.indexOf(arguments[i])>=0){return true;}return false;},_blend:function(c1,c2){var cc1=OpenLayers.Rico.Color.createFromHex(c1);cc1.blend(OpenLayers.Rico.Color.createFromHex(c2));return cc1;},_background:function(el){try{return OpenLayers.Rico.Color.createColorFromBackground(el).asHex();}catch(err){return"#ffffff";}},_isTransparent:function(){return this.options.color=="transparent";},_isTopRounded:function(){return this._hasString(this.options.corners,"all","top","tl","tr");},_isBottomRounded:function(){return this._hasString(this.options.corners,"all","bottom","bl","br");},_hasSingleTextChild:function(el){return el.childNodes.length==1&&el.childNodes[0].nodeType==3;}};(function(){if(window.google&&google.gears){return;}
var factory=null;if(typeof GearsFactory!='undefined'){factory=new GearsFactory();}else{try{factory=new ActiveXObject('Gears.Factory');if(factory.getBuildInfo().indexOf('ie_mobile')!=-1){factory.privateSetGlobalObject(this);}}catch(e){if((typeof navigator.mimeTypes!='undefined')&&navigator.mimeTypes["application/x-googlegears"]){factory=document.createElement("object");factory.style.display="none";factory.width=0;factory.height=0;factory.type="application/x-googlegears";document.documentElement.appendChild(factory);}}}
if(!factory){return;}
if(!window.google){google={};}
if(!google.gears){google.gears={factory:factory};}})();OpenLayers.Element={visible:function(element){return OpenLayers.Util.getElement(element).style.display!='none';},toggle:function(){for(var i=0,len=arguments.length;i<len;i++){var element=OpenLayers.Util.getElement(arguments[i]);var display=OpenLayers.Element.visible(element)?'hide':'show';OpenLayers.Element[display](element);}},hide:function(){for(var i=0,len=arguments.length;i<len;i++){var element=OpenLayers.Util.getElement(arguments[i]);element.style.display='none';}},show:function(){for(var i=0,len=arguments.length;i<len;i++){var element=OpenLayers.Util.getElement(arguments[i]);element.style.display='';}},remove:function(element){element=OpenLayers.Util.getElement(element);element.parentNode.removeChild(element);},getHeight:function(element){element=OpenLayers.Util.getElement(element);return element.offsetHeight;},getDimensions:function(element){element=OpenLayers.Util.getElement(element);if(OpenLayers.Element.getStyle(element,'display')!='none'){return{width:element.offsetWidth,height:element.offsetHeight};}
var els=element.style;var originalVisibility=els.visibility;var originalPosition=els.position;els.visibility='hidden';els.position='absolute';els.display='';var originalWidth=element.clientWidth;var originalHeight=element.clientHeight;els.display='none';els.position=originalPosition;els.visibility=originalVisibility;return{width:originalWidth,height:originalHeight};},hasClass:function(element,name){var names=element.className;return(!!names&&new RegExp("(^|\\s)"+name+"(\\s|$)").test(names));},addClass:function(element,name){if(!OpenLayers.Element.hasClass(element,name)){element.className+=(element.className?" ":"")+name;}
return element;},removeClass:function(element,name){var names=element.className;if(names){element.className=OpenLayers.String.trim(names.replace(new RegExp("(^|\\s+)"+name+"(\\s+|$)")," "));}
return element;},toggleClass:function(element,name){if(OpenLayers.Element.hasClass(element,name)){OpenLayers.Element.removeClass(element,name);}else{OpenLayers.Element.addClass(element,name);}
return element;},getStyle:function(element,style){element=OpenLayers.Util.getElement(element);var value=null;if(element&&element.style){value=element.style[OpenLayers.String.camelize(style)];if(!value){if(document.defaultView&&document.defaultView.getComputedStyle){var css=document.defaultView.getComputedStyle(element,null);value=css?css.getPropertyValue(style):null;}else if(element.currentStyle){value=element.currentStyle[OpenLayers.String.camelize(style)];}}
var positions=['left','top','right','bottom'];if(window.opera&&(OpenLayers.Util.indexOf(positions,style)!=-1)&&(OpenLayers.Element.getStyle(element,'position')=='static')){value='auto';}}
return value=='auto'?null:value;}};OpenLayers.Size=OpenLayers.Class({w:0.0,h:0.0,initialize:function(w,h){this.w=parseFloat(w);this.h=parseFloat(h);},toString:function(){return("w="+this.w+",h="+this.h);},clone:function(){return new OpenLayers.Size(this.w,this.h);},equals:function(sz){var equals=false;if(sz!=null){equals=((this.w==sz.w&&this.h==sz.h)||(isNaN(this.w)&&isNaN(this.h)&&isNaN(sz.w)&&isNaN(sz.h)));}
return equals;},CLASS_NAME:"OpenLayers.Size"});OpenLayers.Console={log:function(){},debug:function(){},info:function(){},warn:function(){},error:function(){},userError:function(error){alert(error);},assert:function(){},dir:function(){},dirxml:function(){},trace:function(){},group:function(){},groupEnd:function(){},time:function(){},timeEnd:function(){},profile:function(){},profileEnd:function(){},count:function(){},CLASS_NAME:"OpenLayers.Console"};(function(){var scripts=document.getElementsByTagName("script");for(var i=0,len=scripts.length;i<len;++i){if(scripts[i].src.indexOf("firebug.js")!=-1){if(console){OpenLayers.Util.extend(OpenLayers.Console,console);break;}}}})();OpenLayers.Icon=OpenLayers.Class({url:null,size:null,offset:null,calculateOffset:null,imageDiv:null,px:null,initialize:function(url,size,offset,calculateOffset){this.url=url;this.size=(size)?size:new OpenLayers.Size(20,20);this.offset=offset?offset:new OpenLayers.Pixel(-(this.size.w/2),-(this.size.h/2));this.calculateOffset=calculateOffset;var id=OpenLayers.Util.createUniqueID("OL_Icon_");this.imageDiv=OpenLayers.Util.createAlphaImageDiv(id);},destroy:function(){this.erase();OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);this.imageDiv.innerHTML="";this.imageDiv=null;},clone:function(){return new OpenLayers.Icon(this.url,this.size,this.offset,this.calculateOffset);},setSize:function(size){if(size!=null){this.size=size;}
this.draw();},setUrl:function(url){if(url!=null){this.url=url;}
this.draw();},draw:function(px){OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,null,this.size,this.url,"absolute");this.moveTo(px);return this.imageDiv;},erase:function(){if(this.imageDiv!=null&&this.imageDiv.parentNode!=null){OpenLayers.Element.remove(this.imageDiv);}},setOpacity:function(opacity){OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,null,null,null,null,null,null,opacity);},moveTo:function(px){if(px!=null){this.px=px;}
if(this.imageDiv!=null){if(this.px==null){this.display(false);}else{if(this.calculateOffset){this.offset=this.calculateOffset(this.size);}
var offsetPx=this.px.offset(this.offset);OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,offsetPx);}}},display:function(display){this.imageDiv.style.display=(display)?"":"none";},isDrawn:function(){var isDrawn=(this.imageDiv&&this.imageDiv.parentNode&&(this.imageDiv.parentNode.nodeType!=11));return isDrawn;},CLASS_NAME:"OpenLayers.Icon"});OpenLayers.Popup=OpenLayers.Class({events:null,id:"",lonlat:null,div:null,contentSize:null,size:null,contentHTML:null,backgroundColor:"",opacity:"",border:"",contentDiv:null,groupDiv:null,closeDiv:null,autoSize:false,minSize:null,maxSize:null,displayClass:"olPopup",contentDisplayClass:"olPopupContent",padding:0,disableFirefoxOverflowHack:false,fixPadding:function(){if(typeof this.padding=="number"){this.padding=new OpenLayers.Bounds(this.padding,this.padding,this.padding,this.padding);}},panMapIfOutOfView:false,keepInMap:false,closeOnMove:false,map:null,initialize:function(id,lonlat,contentSize,contentHTML,closeBox,closeBoxCallback){if(id==null){id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");}
this.id=id;this.lonlat=lonlat;this.contentSize=(contentSize!=null)?contentSize:new OpenLayers.Size(OpenLayers.Popup.WIDTH,OpenLayers.Popup.HEIGHT);if(contentHTML!=null){this.contentHTML=contentHTML;}
this.backgroundColor=OpenLayers.Popup.COLOR;this.opacity=OpenLayers.Popup.OPACITY;this.border=OpenLayers.Popup.BORDER;this.div=OpenLayers.Util.createDiv(this.id,null,null,null,null,null,"hidden");this.div.className=this.displayClass;var groupDivId=this.id+"_GroupDiv";this.groupDiv=OpenLayers.Util.createDiv(groupDivId,null,null,null,"relative",null,"hidden");var id=this.div.id+"_contentDiv";this.contentDiv=OpenLayers.Util.createDiv(id,null,this.contentSize.clone(),null,"relative");this.contentDiv.className=this.contentDisplayClass;this.groupDiv.appendChild(this.contentDiv);this.div.appendChild(this.groupDiv);if(closeBox){this.addCloseBox(closeBoxCallback);}
this.registerEvents();},destroy:function(){this.id=null;this.lonlat=null;this.size=null;this.contentHTML=null;this.backgroundColor=null;this.opacity=null;this.border=null;if(this.closeOnMove&&this.map){this.map.events.unregister("movestart",this,this.hide);}
this.events.destroy();this.events=null;if(this.closeDiv){OpenLayers.Event.stopObservingElement(this.closeDiv);this.groupDiv.removeChild(this.closeDiv);}
this.closeDiv=null;this.div.removeChild(this.groupDiv);this.groupDiv=null;if(this.map!=null){this.map.removePopup(this);}
this.map=null;this.div=null;this.autoSize=null;this.minSize=null;this.maxSize=null;this.padding=null;this.panMapIfOutOfView=null;},draw:function(px){if(px==null){if((this.lonlat!=null)&&(this.map!=null)){px=this.map.getLayerPxFromLonLat(this.lonlat);}}
if(this.closeOnMove){this.map.events.register("movestart",this,this.hide);}
if(!this.disableFirefoxOverflowHack&&OpenLayers.Util.getBrowserName()=='firefox'){this.map.events.register("movestart",this,function(){var style=document.defaultView.getComputedStyle(this.contentDiv,null);var currentOverflow=style.getPropertyValue("overflow");if(currentOverflow!="hidden"){this.contentDiv._oldOverflow=currentOverflow;this.contentDiv.style.overflow="hidden";}});this.map.events.register("moveend",this,function(){var oldOverflow=this.contentDiv._oldOverflow;if(oldOverflow){this.contentDiv.style.overflow=oldOverflow;this.contentDiv._oldOverflow=null;}});}
this.moveTo(px);if(!this.autoSize&&!this.size){this.setSize(this.contentSize);}
this.setBackgroundColor();this.setOpacity();this.setBorder();this.setContentHTML();if(this.panMapIfOutOfView){this.panIntoView();}
return this.div;},updatePosition:function(){if((this.lonlat)&&(this.map)){var px=this.map.getLayerPxFromLonLat(this.lonlat);if(px){this.moveTo(px);}}},moveTo:function(px){if((px!=null)&&(this.div!=null)){this.div.style.left=px.x+"px";this.div.style.top=px.y+"px";}},visible:function(){return OpenLayers.Element.visible(this.div);},toggle:function(){if(this.visible()){this.hide();}else{this.show();}},show:function(){OpenLayers.Element.show(this.div);if(this.panMapIfOutOfView){this.panIntoView();}},hide:function(){OpenLayers.Element.hide(this.div);},setSize:function(contentSize){this.size=contentSize.clone();var contentDivPadding=this.getContentDivPadding();var wPadding=contentDivPadding.left+contentDivPadding.right;var hPadding=contentDivPadding.top+contentDivPadding.bottom;this.fixPadding();wPadding+=this.padding.left+this.padding.right;hPadding+=this.padding.top+this.padding.bottom;if(this.closeDiv){var closeDivWidth=parseInt(this.closeDiv.style.width);wPadding+=closeDivWidth+contentDivPadding.right;}
this.size.w+=wPadding;this.size.h+=hPadding;if(OpenLayers.Util.getBrowserName()=="msie"){this.contentSize.w+=contentDivPadding.left+contentDivPadding.right;this.contentSize.h+=contentDivPadding.bottom+contentDivPadding.top;}
if(this.div!=null){this.div.style.width=this.size.w+"px";this.div.style.height=this.size.h+"px";}
if(this.contentDiv!=null){this.contentDiv.style.width=contentSize.w+"px";this.contentDiv.style.height=contentSize.h+"px";}},updateSize:function(){var preparedHTML="<div class='"+this.contentDisplayClass+"'>"+
this.contentDiv.innerHTML+"</div>";var containerElement=(this.map)?this.map.layerContainerDiv:document.body;var realSize=OpenLayers.Util.getRenderedDimensions(preparedHTML,null,{displayClass:this.displayClass,containerElement:containerElement});var safeSize=this.getSafeContentSize(realSize);var newSize=null;if(safeSize.equals(realSize)){newSize=realSize;}else{var fixedSize=new OpenLayers.Size();fixedSize.w=(safeSize.w<realSize.w)?safeSize.w:null;fixedSize.h=(safeSize.h<realSize.h)?safeSize.h:null;if(fixedSize.w&&fixedSize.h){newSize=safeSize;}else{var clippedSize=OpenLayers.Util.getRenderedDimensions(preparedHTML,fixedSize,{displayClass:this.contentDisplayClass,containerElement:containerElement});var currentOverflow=OpenLayers.Element.getStyle(this.contentDiv,"overflow");if((currentOverflow!="hidden")&&(clippedSize.equals(safeSize))){var scrollBar=OpenLayers.Util.getScrollbarWidth();if(fixedSize.w){clippedSize.h+=scrollBar;}else{clippedSize.w+=scrollBar;}}
newSize=this.getSafeContentSize(clippedSize);}}
this.setSize(newSize);},setBackgroundColor:function(color){if(color!=undefined){this.backgroundColor=color;}
if(this.div!=null){this.div.style.backgroundColor=this.backgroundColor;}},setOpacity:function(opacity){if(opacity!=undefined){this.opacity=opacity;}
if(this.div!=null){this.div.style.opacity=this.opacity;this.div.style.filter='alpha(opacity='+this.opacity*100+')';}},setBorder:function(border){if(border!=undefined){this.border=border;}
if(this.div!=null){this.div.style.border=this.border;}},setContentHTML:function(contentHTML){if(contentHTML!=null){this.contentHTML=contentHTML;}
if((this.contentDiv!=null)&&(this.contentHTML!=null)&&(this.contentHTML!=this.contentDiv.innerHTML)){this.contentDiv.innerHTML=this.contentHTML;if(this.autoSize){this.registerImageListeners();this.updateSize();}}},registerImageListeners:function(){var onImgLoad=function(){this.popup.updateSize();if(this.popup.visible()&&this.popup.panMapIfOutOfView){this.popup.panIntoView();}
OpenLayers.Event.stopObserving(this.img,"load",this.img._onImageLoad);};var images=this.contentDiv.getElementsByTagName("img");for(var i=0,len=images.length;i<len;i++){var img=images[i];if(img.width==0||img.height==0){var context={'popup':this,'img':img};img._onImgLoad=OpenLayers.Function.bind(onImgLoad,context);OpenLayers.Event.observe(img,'load',img._onImgLoad);}}},getSafeContentSize:function(size){var safeContentSize=size.clone();var contentDivPadding=this.getContentDivPadding();var wPadding=contentDivPadding.left+contentDivPadding.right;var hPadding=contentDivPadding.top+contentDivPadding.bottom;this.fixPadding();wPadding+=this.padding.left+this.padding.right;hPadding+=this.padding.top+this.padding.bottom;if(this.closeDiv){var closeDivWidth=parseInt(this.closeDiv.style.width);wPadding+=closeDivWidth+contentDivPadding.right;}
if(this.minSize){safeContentSize.w=Math.max(safeContentSize.w,(this.minSize.w-wPadding));safeContentSize.h=Math.max(safeContentSize.h,(this.minSize.h-hPadding));}
if(this.maxSize){safeContentSize.w=Math.min(safeContentSize.w,(this.maxSize.w-wPadding));safeContentSize.h=Math.min(safeContentSize.h,(this.maxSize.h-hPadding));}
if(this.map&&this.map.size){var extraX=0,extraY=0;if(this.keepInMap&&!this.panMapIfOutOfView){var px=this.map.getPixelFromLonLat(this.lonlat);switch(this.relativePosition){case"tr":extraX=px.x;extraY=this.map.size.h-px.y;break;case"tl":extraX=this.map.size.w-px.x;extraY=this.map.size.h-px.y;break;case"bl":extraX=this.map.size.w-px.x;extraY=px.y;break;case"br":extraX=px.x;extraY=px.y;break;default:extraX=px.x;extraY=this.map.size.h-px.y;break;}}
var maxY=this.map.size.h-
this.map.paddingForPopups.top-
this.map.paddingForPopups.bottom-
hPadding-extraY;var maxX=this.map.size.w-
this.map.paddingForPopups.left-
this.map.paddingForPopups.right-
wPadding-extraX;safeContentSize.w=Math.min(safeContentSize.w,maxX);safeContentSize.h=Math.min(safeContentSize.h,maxY);}
return safeContentSize;},getContentDivPadding:function(){var contentDivPadding=this._contentDivPadding;if(!contentDivPadding){if(this.div.parentNode==null){this.div.style.display="none";document.body.appendChild(this.div);}
contentDivPadding=new OpenLayers.Bounds(OpenLayers.Element.getStyle(this.contentDiv,"padding-left"),OpenLayers.Element.getStyle(this.contentDiv,"padding-bottom"),OpenLayers.Element.getStyle(this.contentDiv,"padding-right"),OpenLayers.Element.getStyle(this.contentDiv,"padding-top"));this._contentDivPadding=contentDivPadding;if(this.div.parentNode==document.body){document.body.removeChild(this.div);this.div.style.display="";}}
return contentDivPadding;},addCloseBox:function(callback){this.closeDiv=OpenLayers.Util.createDiv(this.id+"_close",null,new OpenLayers.Size(17,17));this.closeDiv.className="olPopupCloseBox";var contentDivPadding=this.getContentDivPadding();this.closeDiv.style.right=contentDivPadding.right+"px";this.closeDiv.style.top=contentDivPadding.top+"px";this.groupDiv.appendChild(this.closeDiv);var closePopup=callback||function(e){this.hide();OpenLayers.Event.stop(e);};OpenLayers.Event.observe(this.closeDiv,"click",OpenLayers.Function.bindAsEventListener(closePopup,this));},panIntoView:function(){var mapSize=this.map.getSize();var origTL=this.map.getViewPortPxFromLayerPx(new OpenLayers.Pixel(parseInt(this.div.style.left),parseInt(this.div.style.top)));var newTL=origTL.clone();if(origTL.x<this.map.paddingForPopups.left){newTL.x=this.map.paddingForPopups.left;}else
if((origTL.x+this.size.w)>(mapSize.w-this.map.paddingForPopups.right)){newTL.x=mapSize.w-this.map.paddingForPopups.right-this.size.w;}
if(origTL.y<this.map.paddingForPopups.top){newTL.y=this.map.paddingForPopups.top;}else
if((origTL.y+this.size.h)>(mapSize.h-this.map.paddingForPopups.bottom)){newTL.y=mapSize.h-this.map.paddingForPopups.bottom-this.size.h;}
var dx=origTL.x-newTL.x;var dy=origTL.y-newTL.y;this.map.pan(dx,dy);},registerEvents:function(){this.events=new OpenLayers.Events(this,this.div,null,true);this.events.on({"mousedown":this.onmousedown,"mousemove":this.onmousemove,"mouseup":this.onmouseup,"click":this.onclick,"mouseout":this.onmouseout,"dblclick":this.ondblclick,scope:this});},onmousedown:function(evt){this.mousedown=true;OpenLayers.Event.stop(evt,true);},onmousemove:function(evt){if(this.mousedown){OpenLayers.Event.stop(evt,true);}},onmouseup:function(evt){if(this.mousedown){this.mousedown=false;OpenLayers.Event.stop(evt,true);}},onclick:function(evt){OpenLayers.Event.stop(evt,true);},onmouseout:function(evt){this.mousedown=false;},ondblclick:function(evt){OpenLayers.Event.stop(evt,true);},CLASS_NAME:"OpenLayers.Popup"});OpenLayers.Popup.WIDTH=200;OpenLayers.Popup.HEIGHT=200;OpenLayers.Popup.COLOR="white";OpenLayers.Popup.OPACITY=1;OpenLayers.Popup.BORDER="0px";OpenLayers.Protocol=OpenLayers.Class({format:null,options:null,autoDestroy:true,initialize:function(options){options=options||{};OpenLayers.Util.extend(this,options);this.options=options;},destroy:function(){this.options=null;this.format=null;},read:function(){},create:function(){},update:function(){},"delete":function(){},commit:function(){},abort:function(response){},CLASS_NAME:"OpenLayers.Protocol"});OpenLayers.Protocol.Response=OpenLayers.Class({code:null,requestType:null,last:true,features:null,reqFeatures:null,priv:null,initialize:function(options){OpenLayers.Util.extend(this,options);},success:function(){return this.code>0;},CLASS_NAME:"OpenLayers.Protocol.Response"});OpenLayers.Protocol.Response.SUCCESS=1;OpenLayers.Protocol.Response.FAILURE=0;OpenLayers.Renderer=OpenLayers.Class({container:null,root:null,extent:null,locked:false,size:null,resolution:null,map:null,initialize:function(containerID,options){this.container=OpenLayers.Util.getElement(containerID);},destroy:function(){this.container=null;this.extent=null;this.size=null;this.resolution=null;this.map=null;},supported:function(){return false;},setExtent:function(extent,resolutionChanged){this.extent=extent.clone();if(resolutionChanged){this.resolution=null;}},setSize:function(size){this.size=size.clone();this.resolution=null;},getResolution:function(){this.resolution=this.resolution||this.map.getResolution();return this.resolution;},drawFeature:function(feature,style){if(style==null){style=feature.style;}
if(feature.geometry){var bounds=feature.geometry.getBounds();if(bounds){if(!bounds.intersectsBounds(this.extent)){style={display:"none"};}
var rendered=this.drawGeometry(feature.geometry,style,feature.id);if(style.display!="none"&&style.label&&rendered!==false){this.drawText(feature.id,style,feature.geometry.getCentroid());}else{this.removeText(feature.id);}
return rendered;}}},drawGeometry:function(geometry,style,featureId){},drawText:function(featureId,style,location){},removeText:function(featureId){},clear:function(){},getFeatureIdFromEvent:function(evt){},eraseFeatures:function(features){if(!(features instanceof Array)){features=[features];}
for(var i=0,len=features.length;i<len;++i){this.eraseGeometry(features[i].geometry);this.removeText(features[i].id);}},eraseGeometry:function(geometry){},moveRoot:function(renderer){},getRenderLayerId:function(){return this.container.id;},CLASS_NAME:"OpenLayers.Renderer"});OpenLayers.Strategy=OpenLayers.Class({layer:null,options:null,active:null,autoActivate:true,autoDestroy:true,initialize:function(options){OpenLayers.Util.extend(this,options);this.options=options;this.active=false;},destroy:function(){this.deactivate();this.layer=null;this.options=null;},setLayer:function(layer){this.layer=layer;},activate:function(){if(!this.active){this.active=true;return true;}
return false;},deactivate:function(){if(this.active){this.active=false;return true;}
return false;},CLASS_NAME:"OpenLayers.Strategy"});OpenLayers.Rico.Color=OpenLayers.Class({initialize:function(red,green,blue){this.rgb={r:red,g:green,b:blue};},setRed:function(r){this.rgb.r=r;},setGreen:function(g){this.rgb.g=g;},setBlue:function(b){this.rgb.b=b;},setHue:function(h){var hsb=this.asHSB();hsb.h=h;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,hsb.b);},setSaturation:function(s){var hsb=this.asHSB();hsb.s=s;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,hsb.b);},setBrightness:function(b){var hsb=this.asHSB();hsb.b=b;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,hsb.b);},darken:function(percent){var hsb=this.asHSB();this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,Math.max(hsb.b-percent,0));},brighten:function(percent){var hsb=this.asHSB();this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,Math.min(hsb.b+percent,1));},blend:function(other){this.rgb.r=Math.floor((this.rgb.r+other.rgb.r)/2);this.rgb.g=Math.floor((this.rgb.g+other.rgb.g)/2);this.rgb.b=Math.floor((this.rgb.b+other.rgb.b)/2);},isBright:function(){var hsb=this.asHSB();return this.asHSB().b>0.5;},isDark:function(){return!this.isBright();},asRGB:function(){return"rgb("+this.rgb.r+","+this.rgb.g+","+this.rgb.b+")";},asHex:function(){return"#"+this.rgb.r.toColorPart()+this.rgb.g.toColorPart()+this.rgb.b.toColorPart();},asHSB:function(){return OpenLayers.Rico.Color.RGBtoHSB(this.rgb.r,this.rgb.g,this.rgb.b);},toString:function(){return this.asHex();}});OpenLayers.Rico.Color.createFromHex=function(hexCode){if(hexCode.length==4){var shortHexCode=hexCode;var hexCode='#';for(var i=1;i<4;i++){hexCode+=(shortHexCode.charAt(i)+
shortHexCode.charAt(i));}}
if(hexCode.indexOf('#')==0){hexCode=hexCode.substring(1);}
var red=hexCode.substring(0,2);var green=hexCode.substring(2,4);var blue=hexCode.substring(4,6);return new OpenLayers.Rico.Color(parseInt(red,16),parseInt(green,16),parseInt(blue,16));};OpenLayers.Rico.Color.createColorFromBackground=function(elem){var actualColor=RicoUtil.getElementsComputedStyle(OpenLayers.Util.getElement(elem),"backgroundColor","background-color");if(actualColor=="transparent"&&elem.parentNode){return OpenLayers.Rico.Color.createColorFromBackground(elem.parentNode);}
if(actualColor==null){return new OpenLayers.Rico.Color(255,255,255);}
if(actualColor.indexOf("rgb(")==0){var colors=actualColor.substring(4,actualColor.length-1);var colorArray=colors.split(",");return new OpenLayers.Rico.Color(parseInt(colorArray[0]),parseInt(colorArray[1]),parseInt(colorArray[2]));}
else if(actualColor.indexOf("#")==0){return OpenLayers.Rico.Color.createFromHex(actualColor);}
else{return new OpenLayers.Rico.Color(255,255,255);}};OpenLayers.Rico.Color.HSBtoRGB=function(hue,saturation,brightness){var red=0;var green=0;var blue=0;if(saturation==0){red=parseInt(brightness*255.0+0.5);green=red;blue=red;}
else{var h=(hue-Math.floor(hue))*6.0;var f=h-Math.floor(h);var p=brightness*(1.0-saturation);var q=brightness*(1.0-saturation*f);var t=brightness*(1.0-(saturation*(1.0-f)));switch(parseInt(h)){case 0:red=(brightness*255.0+0.5);green=(t*255.0+0.5);blue=(p*255.0+0.5);break;case 1:red=(q*255.0+0.5);green=(brightness*255.0+0.5);blue=(p*255.0+0.5);break;case 2:red=(p*255.0+0.5);green=(brightness*255.0+0.5);blue=(t*255.0+0.5);break;case 3:red=(p*255.0+0.5);green=(q*255.0+0.5);blue=(brightness*255.0+0.5);break;case 4:red=(t*255.0+0.5);green=(p*255.0+0.5);blue=(brightness*255.0+0.5);break;case 5:red=(brightness*255.0+0.5);green=(p*255.0+0.5);blue=(q*255.0+0.5);break;}}
return{r:parseInt(red),g:parseInt(green),b:parseInt(blue)};};OpenLayers.Rico.Color.RGBtoHSB=function(r,g,b){var hue;var saturation;var brightness;var cmax=(r>g)?r:g;if(b>cmax){cmax=b;}
var cmin=(r<g)?r:g;if(b<cmin){cmin=b;}
brightness=cmax/255.0;if(cmax!=0){saturation=(cmax-cmin)/cmax;}else{saturation=0;}
if(saturation==0){hue=0;}else{var redc=(cmax-r)/(cmax-cmin);var greenc=(cmax-g)/(cmax-cmin);var bluec=(cmax-b)/(cmax-cmin);if(r==cmax){hue=bluec-greenc;}else if(g==cmax){hue=2.0+redc-bluec;}else{hue=4.0+greenc-redc;}
hue=hue/6.0;if(hue<0){hue=hue+1.0;}}
return{h:hue,s:saturation,b:brightness};};OpenLayers.Bounds=OpenLayers.Class({left:null,bottom:null,right:null,top:null,centerLonLat:null,initialize:function(left,bottom,right,top){if(left!=null){this.left=OpenLayers.Util.toFloat(left);}
if(bottom!=null){this.bottom=OpenLayers.Util.toFloat(bottom);}
if(right!=null){this.right=OpenLayers.Util.toFloat(right);}
if(top!=null){this.top=OpenLayers.Util.toFloat(top);}},clone:function(){return new OpenLayers.Bounds(this.left,this.bottom,this.right,this.top);},equals:function(bounds){var equals=false;if(bounds!=null){equals=((this.left==bounds.left)&&(this.right==bounds.right)&&(this.top==bounds.top)&&(this.bottom==bounds.bottom));}
return equals;},toString:function(){return("left-bottom=("+this.left+","+this.bottom+")"
+" right-top=("+this.right+","+this.top+")");},toArray:function(){return[this.left,this.bottom,this.right,this.top];},toBBOX:function(decimal){if(decimal==null){decimal=6;}
var mult=Math.pow(10,decimal);var bbox=Math.round(this.left*mult)/mult+","+
Math.round(this.bottom*mult)/mult+","+
Math.round(this.right*mult)/mult+","+
Math.round(this.top*mult)/mult;return bbox;},toGeometry:function(){return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(this.left,this.bottom),new OpenLayers.Geometry.Point(this.right,this.bottom),new OpenLayers.Geometry.Point(this.right,this.top),new OpenLayers.Geometry.Point(this.left,this.top)])]);},getWidth:function(){return(this.right-this.left);},getHeight:function(){return(this.top-this.bottom);},getSize:function(){return new OpenLayers.Size(this.getWidth(),this.getHeight());},getCenterPixel:function(){return new OpenLayers.Pixel((this.left+this.right)/2,(this.bottom+this.top)/2);},getCenterLonLat:function(){if(!this.centerLonLat){this.centerLonLat=new OpenLayers.LonLat((this.left+this.right)/2,(this.bottom+this.top)/2);}
return this.centerLonLat;},scale:function(ratio,origin){if(origin==null){origin=this.getCenterLonLat();}
var bounds=[];var origx,origy;if(origin.CLASS_NAME=="OpenLayers.LonLat"){origx=origin.lon;origy=origin.lat;}else{origx=origin.x;origy=origin.y;}
var left=(this.left-origx)*ratio+origx;var bottom=(this.bottom-origy)*ratio+origy;var right=(this.right-origx)*ratio+origx;var top=(this.top-origy)*ratio+origy;return new OpenLayers.Bounds(left,bottom,right,top);},add:function(x,y){if((x==null)||(y==null)){var msg=OpenLayers.i18n("boundsAddError");OpenLayers.Console.error(msg);return null;}
return new OpenLayers.Bounds(this.left+x,this.bottom+y,this.right+x,this.top+y);},extend:function(object){var bounds=null;if(object){switch(object.CLASS_NAME){case"OpenLayers.LonLat":bounds=new OpenLayers.Bounds(object.lon,object.lat,object.lon,object.lat);break;case"OpenLayers.Geometry.Point":bounds=new OpenLayers.Bounds(object.x,object.y,object.x,object.y);break;case"OpenLayers.Bounds":bounds=object;break;}
if(bounds){this.centerLonLat=null;if((this.left==null)||(bounds.left<this.left)){this.left=bounds.left;}
if((this.bottom==null)||(bounds.bottom<this.bottom)){this.bottom=bounds.bottom;}
if((this.right==null)||(bounds.right>this.right)){this.right=bounds.right;}
if((this.top==null)||(bounds.top>this.top)){this.top=bounds.top;}}}},containsLonLat:function(ll,inclusive){return this.contains(ll.lon,ll.lat,inclusive);},containsPixel:function(px,inclusive){return this.contains(px.x,px.y,inclusive);},contains:function(x,y,inclusive){if(inclusive==null){inclusive=true;}
if(x==null||y==null){return false;}
x=OpenLayers.Util.toFloat(x);y=OpenLayers.Util.toFloat(y);var contains=false;if(inclusive){contains=((x>=this.left)&&(x<=this.right)&&(y>=this.bottom)&&(y<=this.top));}else{contains=((x>this.left)&&(x<this.right)&&(y>this.bottom)&&(y<this.top));}
return contains;},intersectsBounds:function(bounds,inclusive){if(inclusive==null){inclusive=true;}
var intersects=false;var mightTouch=(this.left==bounds.right||this.right==bounds.left||this.top==bounds.bottom||this.bottom==bounds.top);if(inclusive||!mightTouch){var inBottom=(((bounds.bottom>=this.bottom)&&(bounds.bottom<=this.top))||((this.bottom>=bounds.bottom)&&(this.bottom<=bounds.top)));var inTop=(((bounds.top>=this.bottom)&&(bounds.top<=this.top))||((this.top>bounds.bottom)&&(this.top<bounds.top)));var inLeft=(((bounds.left>=this.left)&&(bounds.left<=this.right))||((this.left>=bounds.left)&&(this.left<=bounds.right)));var inRight=(((bounds.right>=this.left)&&(bounds.right<=this.right))||((this.right>=bounds.left)&&(this.right<=bounds.right)));intersects=((inBottom||inTop)&&(inLeft||inRight));}
return intersects;},containsBounds:function(bounds,partial,inclusive){if(partial==null){partial=false;}
if(inclusive==null){inclusive=true;}
var bottomLeft=this.contains(bounds.left,bounds.bottom,inclusive);var bottomRight=this.contains(bounds.right,bounds.bottom,inclusive);var topLeft=this.contains(bounds.left,bounds.top,inclusive);var topRight=this.contains(bounds.right,bounds.top,inclusive);return(partial)?(bottomLeft||bottomRight||topLeft||topRight):(bottomLeft&&bottomRight&&topLeft&&topRight);},determineQuadrant:function(lonlat){var quadrant="";var center=this.getCenterLonLat();quadrant+=(lonlat.lat<center.lat)?"b":"t";quadrant+=(lonlat.lon<center.lon)?"l":"r";return quadrant;},transform:function(source,dest){this.centerLonLat=null;var ll=OpenLayers.Projection.transform({'x':this.left,'y':this.bottom},source,dest);var lr=OpenLayers.Projection.transform({'x':this.right,'y':this.bottom},source,dest);var ul=OpenLayers.Projection.transform({'x':this.left,'y':this.top},source,dest);var ur=OpenLayers.Projection.transform({'x':this.right,'y':this.top},source,dest);this.left=Math.min(ll.x,ul.x);this.bottom=Math.min(ll.y,lr.y);this.right=Math.max(lr.x,ur.x);this.top=Math.max(ul.y,ur.y);return this;},wrapDateLine:function(maxExtent,options){options=options||{};var leftTolerance=options.leftTolerance||0;var rightTolerance=options.rightTolerance||0;var newBounds=this.clone();if(maxExtent){while(newBounds.left<maxExtent.left&&(newBounds.right-rightTolerance)<=maxExtent.left){newBounds=newBounds.add(maxExtent.getWidth(),0);}
while((newBounds.left+leftTolerance)>=maxExtent.right&&newBounds.right>maxExtent.right){newBounds=newBounds.add(-maxExtent.getWidth(),0);}}
return newBounds;},CLASS_NAME:"OpenLayers.Bounds"});OpenLayers.Bounds.fromString=function(str){var bounds=str.split(",");return OpenLayers.Bounds.fromArray(bounds);};OpenLayers.Bounds.fromArray=function(bbox){return new OpenLayers.Bounds(parseFloat(bbox[0]),parseFloat(bbox[1]),parseFloat(bbox[2]),parseFloat(bbox[3]));};OpenLayers.Bounds.fromSize=function(size){return new OpenLayers.Bounds(0,size.h,size.w,0);};OpenLayers.Bounds.oppositeQuadrant=function(quadrant){var opp="";opp+=(quadrant.charAt(0)=='t')?'b':'t';opp+=(quadrant.charAt(1)=='l')?'r':'l';return opp;};OpenLayers.LonLat=OpenLayers.Class({lon:0.0,lat:0.0,initialize:function(lon,lat){this.lon=OpenLayers.Util.toFloat(lon);this.lat=OpenLayers.Util.toFloat(lat);},toString:function(){return("lon="+this.lon+",lat="+this.lat);},toShortString:function(){return(this.lon+", "+this.lat);},clone:function(){return new OpenLayers.LonLat(this.lon,this.lat);},add:function(lon,lat){if((lon==null)||(lat==null)){var msg=OpenLayers.i18n("lonlatAddError");OpenLayers.Console.error(msg);return null;}
return new OpenLayers.LonLat(this.lon+lon,this.lat+lat);},equals:function(ll){var equals=false;if(ll!=null){equals=((this.lon==ll.lon&&this.lat==ll.lat)||(isNaN(this.lon)&&isNaN(this.lat)&&isNaN(ll.lon)&&isNaN(ll.lat)));}
return equals;},transform:function(source,dest){var point=OpenLayers.Projection.transform({'x':this.lon,'y':this.lat},source,dest);this.lon=point.x;this.lat=point.y;return this;},wrapDateLine:function(maxExtent){var newLonLat=this.clone();if(maxExtent){while(newLonLat.lon<maxExtent.left){newLonLat.lon+=maxExtent.getWidth();}
while(newLonLat.lon>maxExtent.right){newLonLat.lon-=maxExtent.getWidth();}}
return newLonLat;},CLASS_NAME:"OpenLayers.LonLat"});OpenLayers.LonLat.fromString=function(str){var pair=str.split(",");return new OpenLayers.LonLat(parseFloat(pair[0]),parseFloat(pair[1]));};OpenLayers.Pixel=OpenLayers.Class({x:0.0,y:0.0,initialize:function(x,y){this.x=parseFloat(x);this.y=parseFloat(y);},toString:function(){return("x="+this.x+",y="+this.y);},clone:function(){return new OpenLayers.Pixel(this.x,this.y);},equals:function(px){var equals=false;if(px!=null){equals=((this.x==px.x&&this.y==px.y)||(isNaN(this.x)&&isNaN(this.y)&&isNaN(px.x)&&isNaN(px.y)));}
return equals;},add:function(x,y){if((x==null)||(y==null)){var msg=OpenLayers.i18n("pixelAddError");OpenLayers.Console.error(msg);return null;}
return new OpenLayers.Pixel(this.x+x,this.y+y);},offset:function(px){var newPx=this.clone();if(px){newPx=this.add(px.x,px.y);}
return newPx;},CLASS_NAME:"OpenLayers.Pixel"});OpenLayers.Control=OpenLayers.Class({id:null,map:null,div:null,type:null,allowSelection:false,displayClass:"",title:"",active:null,handler:null,eventListeners:null,events:null,EVENT_TYPES:["activate","deactivate"],initialize:function(options){this.displayClass=this.CLASS_NAME.replace("OpenLayers.","ol").replace(/\./g,"");OpenLayers.Util.extend(this,options);this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}
if(this.id==null){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");}},destroy:function(){if(this.events){if(this.eventListeners){this.events.un(this.eventListeners);}
this.events.destroy();this.events=null;}
this.eventListeners=null;if(this.handler){this.handler.destroy();this.handler=null;}
if(this.handlers){for(var key in this.handlers){if(this.handlers.hasOwnProperty(key)&&typeof this.handlers[key].destroy=="function"){this.handlers[key].destroy();}}
this.handlers=null;}
if(this.map){this.map.removeControl(this);this.map=null;}},setMap:function(map){this.map=map;if(this.handler){this.handler.setMap(map);}},draw:function(px){if(this.div==null){this.div=OpenLayers.Util.createDiv(this.id);this.div.className=this.displayClass;if(!this.allowSelection){this.div.className+=" olControlNoSelect";this.div.setAttribute("unselectable","on",0);this.div.onselectstart=function(){return(false);};}
if(this.title!=""){this.div.title=this.title;}}
if(px!=null){this.position=px.clone();}
this.moveTo(this.position);return this.div;},moveTo:function(px){if((px!=null)&&(this.div!=null)){this.div.style.left=px.x+"px";this.div.style.top=px.y+"px";}},activate:function(){if(this.active){return false;}
if(this.handler){this.handler.activate();}
this.active=true;if(this.map){OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");}
this.events.triggerEvent("activate");return true;},deactivate:function(){if(this.active){if(this.handler){this.handler.deactivate();}
this.active=false;if(this.map){OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");}
this.events.triggerEvent("deactivate");return true;}
return false;},CLASS_NAME:"OpenLayers.Control"});OpenLayers.Control.TYPE_BUTTON=1;OpenLayers.Control.TYPE_TOGGLE=2;OpenLayers.Control.TYPE_TOOL=3;OpenLayers.Lang={code:null,defaultCode:"en",getCode:function(){if(!OpenLayers.Lang.code){OpenLayers.Lang.setCode();}
return OpenLayers.Lang.code;},setCode:function(code){var lang;if(!code){code=(OpenLayers.Util.getBrowserName()=="msie")?navigator.userLanguage:navigator.language;}
var parts=code.split('-');parts[0]=parts[0].toLowerCase();if(typeof OpenLayers.Lang[parts[0]]=="object"){lang=parts[0];}
if(parts[1]){var testLang=parts[0]+'-'+parts[1].toUpperCase();if(typeof OpenLayers.Lang[testLang]=="object"){lang=testLang;}}
if(!lang){OpenLayers.Console.warn('Failed to find OpenLayers.Lang.'+parts.join("-")+' dictionary, falling back to default language');lang=OpenLayers.Lang.defaultCode;}
OpenLayers.Lang.code=lang;},translate:function(key,context){var dictionary=OpenLayers.Lang[OpenLayers.Lang.getCode()];var message=dictionary[key];if(!message){message=key;}
if(context){message=OpenLayers.String.format(message,context);}
return message;}};OpenLayers.i18n=OpenLayers.Lang.translate;OpenLayers.Popup.Anchored=OpenLayers.Class(OpenLayers.Popup,{relativePosition:null,keepInMap:true,anchor:null,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){var newArguments=[id,lonlat,contentSize,contentHTML,closeBox,closeBoxCallback];OpenLayers.Popup.prototype.initialize.apply(this,newArguments);this.anchor=(anchor!=null)?anchor:{size:new OpenLayers.Size(0,0),offset:new OpenLayers.Pixel(0,0)};},destroy:function(){this.anchor=null;this.relativePosition=null;OpenLayers.Popup.prototype.destroy.apply(this,arguments);},show:function(){this.updatePosition();OpenLayers.Popup.prototype.show.apply(this,arguments);},moveTo:function(px){var oldRelativePosition=this.relativePosition;this.relativePosition=this.calculateRelativePosition(px);var newPx=this.calculateNewPx(px);var newArguments=new Array(newPx);OpenLayers.Popup.prototype.moveTo.apply(this,newArguments);if(this.relativePosition!=oldRelativePosition){this.updateRelativePosition();}},setSize:function(contentSize){OpenLayers.Popup.prototype.setSize.apply(this,arguments);if((this.lonlat)&&(this.map)){var px=this.map.getLayerPxFromLonLat(this.lonlat);this.moveTo(px);}},calculateRelativePosition:function(px){var lonlat=this.map.getLonLatFromLayerPx(px);var extent=this.map.getExtent();var quadrant=extent.determineQuadrant(lonlat);return OpenLayers.Bounds.oppositeQuadrant(quadrant);},updateRelativePosition:function(){},calculateNewPx:function(px){var newPx=px.offset(this.anchor.offset);var size=this.size||this.contentSize;var top=(this.relativePosition.charAt(0)=='t');newPx.y+=(top)?-size.h:this.anchor.size.h;var left=(this.relativePosition.charAt(1)=='l');newPx.x+=(left)?-size.w:this.anchor.size.w;return newPx;},CLASS_NAME:"OpenLayers.Popup.Anchored"});OpenLayers.Protocol.SQL=OpenLayers.Class(OpenLayers.Protocol,{databaseName:'ol',tableName:"ol_vector_features",postReadFiltering:true,initialize:function(options){OpenLayers.Protocol.prototype.initialize.apply(this,[options]);},destroy:function(){OpenLayers.Protocol.prototype.destroy.apply(this);},supported:function(){return false;},evaluateFilter:function(feature,filter){return filter&&this.postReadFiltering?filter.evaluate(feature):true;},CLASS_NAME:"OpenLayers.Protocol.SQL"});OpenLayers.Protocol.WFS=function(options){options=OpenLayers.Util.applyDefaults(options,OpenLayers.Protocol.WFS.DEFAULTS);var cls=OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g,"_")];if(!cls){throw"Unsupported WFS version: "+options.version;}
return new cls(options);};OpenLayers.Protocol.WFS.fromWMSLayer=function(layer,options){var typeName,featurePrefix;var param=layer.params["LAYERS"];var parts=(param instanceof Array?param[0]:param).split(":");if(parts.length>1){featurePrefix=parts[0];}
typeName=parts.pop();var protocolOptions={url:layer.url,featureType:typeName,featurePrefix:featurePrefix,srsName:layer.projection&&layer.projection.getCode()||layer.map&&layer.map.getProjectionObject().getCode(),version:"1.1.0"};return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(options,protocolOptions));};OpenLayers.Protocol.WFS.DEFAULTS={"version":"1.0.0"};OpenLayers.Renderer.Canvas=OpenLayers.Class(OpenLayers.Renderer,{canvas:null,features:null,geometryMap:null,initialize:function(containerID){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.root=document.createElement("canvas");this.container.appendChild(this.root);this.canvas=this.root.getContext("2d");this.features={};this.geometryMap={};},eraseGeometry:function(geometry){this.eraseFeatures(this.features[this.geometryMap[geometry.id]][0]);},supported:function(){var canvas=document.createElement("canvas");return!!canvas.getContext;},setExtent:function(extent){this.extent=extent.clone();this.resolution=null;this.redraw();},setSize:function(size){this.size=size.clone();this.root.style.width=size.w+"px";this.root.style.height=size.h+"px";this.root.width=size.w;this.root.height=size.h;this.resolution=null;},drawFeature:function(feature,style){if(style==null){style=feature.style;}
style=OpenLayers.Util.extend({'fillColor':'#000000','strokeColor':'#000000','strokeWidth':2,'fillOpacity':1,'strokeOpacity':1},style);this.features[feature.id]=[feature,style];if(feature.geometry){this.geometryMap[feature.geometry.id]=feature.id;}
this.redraw();},drawGeometry:function(geometry,style){var className=geometry.CLASS_NAME;if((className=="OpenLayers.Geometry.Collection")||(className=="OpenLayers.Geometry.MultiPoint")||(className=="OpenLayers.Geometry.MultiLineString")||(className=="OpenLayers.Geometry.MultiPolygon")){for(var i=0;i<geometry.components.length;i++){this.drawGeometry(geometry.components[i],style);}
return;};switch(geometry.CLASS_NAME){case"OpenLayers.Geometry.Point":this.drawPoint(geometry,style);break;case"OpenLayers.Geometry.LineString":this.drawLineString(geometry,style);break;case"OpenLayers.Geometry.LinearRing":this.drawLinearRing(geometry,style);break;case"OpenLayers.Geometry.Polygon":this.drawPolygon(geometry,style);break;default:break;}},drawExternalGraphic:function(pt,style){var img=new Image();img.src=style.externalGraphic;if(style.graphicTitle){img.title=style.graphicTitle;}
var width=style.graphicWidth||style.graphicHeight;var height=style.graphicHeight||style.graphicWidth;width=width?width:style.pointRadius*2;height=height?height:style.pointRadius*2;var xOffset=(style.graphicXOffset!=undefined)?style.graphicXOffset:-(0.5*width);var yOffset=(style.graphicYOffset!=undefined)?style.graphicYOffset:-(0.5*height);var opacity=style.graphicOpacity||style.fillOpacity;var context={img:img,x:(pt[0]+xOffset),y:(pt[1]+yOffset),width:width,height:height,canvas:this.canvas};img.onload=OpenLayers.Function.bind(function(){this.canvas.drawImage(this.img,this.x,this.y,this.width,this.height);},context);},setCanvasStyle:function(type,style){if(type=="fill"){this.canvas.globalAlpha=style['fillOpacity'];this.canvas.fillStyle=style['fillColor'];}else if(type=="stroke"){this.canvas.globalAlpha=style['strokeOpacity'];this.canvas.strokeStyle=style['strokeColor'];this.canvas.lineWidth=style['strokeWidth'];}else{this.canvas.globalAlpha=0;this.canvas.lineWidth=1;}},drawPoint:function(geometry,style){if(style.graphic!==false){var pt=this.getLocalXY(geometry);if(style.externalGraphic){this.drawExternalGraphic(pt,style);}else{if(style.fill!==false){this.setCanvasStyle("fill",style);this.canvas.beginPath();this.canvas.arc(pt[0],pt[1],6,0,Math.PI*2,true);this.canvas.fill();}
if(style.stroke!==false){this.setCanvasStyle("stroke",style);this.canvas.beginPath();this.canvas.arc(pt[0],pt[1],6,0,Math.PI*2,true);this.canvas.stroke();this.setCanvasStyle("reset");}}}},drawLineString:function(geometry,style){if(style.stroke!==false){this.setCanvasStyle("stroke",style);this.canvas.beginPath();var start=this.getLocalXY(geometry.components[0]);this.canvas.moveTo(start[0],start[1]);for(var i=1;i<geometry.components.length;i++){var pt=this.getLocalXY(geometry.components[i]);this.canvas.lineTo(pt[0],pt[1]);}
this.canvas.stroke();}
this.setCanvasStyle("reset");},drawLinearRing:function(geometry,style){if(style.fill!==false){this.setCanvasStyle("fill",style);this.canvas.beginPath();var start=this.getLocalXY(geometry.components[0]);this.canvas.moveTo(start[0],start[1]);for(var i=1;i<geometry.components.length-1;i++){var pt=this.getLocalXY(geometry.components[i]);this.canvas.lineTo(pt[0],pt[1]);}
this.canvas.fill();}
if(style.stroke!==false){var oldWidth=this.canvas.lineWidth;this.setCanvasStyle("stroke",style);this.canvas.beginPath();var start=this.getLocalXY(geometry.components[0]);this.canvas.moveTo(start[0],start[1]);for(var i=1;i<geometry.components.length;i++){var pt=this.getLocalXY(geometry.components[i]);this.canvas.lineTo(pt[0],pt[1]);}
this.canvas.stroke();}
this.setCanvasStyle("reset");},drawPolygon:function(geometry,style){this.drawLinearRing(geometry.components[0],style);for(var i=1;i<geometry.components.length;i++){this.drawLinearRing(geometry.components[i],{fillOpacity:0,strokeWidth:0,strokeOpacity:0,strokeColor:'#000000',fillColor:'#000000'});}},drawText:function(location,style){style=OpenLayers.Util.extend({fontColor:"#000000",labelAlign:"cm"},style);var pt=this.getLocalXY(location);this.setCanvasStyle("reset");this.canvas.fillStyle=style.fontColor;this.canvas.globalAlpha=1;var fontStyle=style.fontWeight+" "+style.fontSize+" "+style.fontFamily;if(this.canvas.fillText){var labelAlign=OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]]||"middle";this.canvas.font=fontStyle;this.canvas.textAlign=labelAlign;this.canvas.fillText(style.label,pt[0],pt[1]);}else if(this.canvas.mozDrawText){this.canvas.mozTextStyle=fontStyle;var len=this.canvas.mozMeasureText(style.label);switch(style.labelAlign[0]){case"l":break;case"r":pt[0]-=len;break;case"c":default:pt[0]-=len/2;}
this.canvas.translate(pt[0],pt[1]);this.canvas.mozDrawText(style.label);this.canvas.translate(-1*pt[0],-1*pt[1]);}
this.setCanvasStyle("reset");},getLocalXY:function(point){var resolution=this.getResolution();var extent=this.extent;var x=(point.x/resolution+(-extent.left/resolution));var y=((extent.top/resolution)-point.y/resolution);return[x,y];},clear:function(){this.canvas.clearRect(0,0,this.root.width,this.root.height);},getFeatureIdFromEvent:function(evt){var loc=this.map.getLonLatFromPixel(evt.xy);var resolution=this.getResolution();var bounds=new OpenLayers.Bounds(loc.lon-resolution*5,loc.lat-resolution*5,loc.lon+resolution*5,loc.lat+resolution*5);var geom=bounds.toGeometry();for(var feat in this.features){if(!this.features.hasOwnProperty(feat)){continue;}
if(this.features[feat][0].geometry.intersects(geom)){return feat;}}
return null;},eraseFeatures:function(features){if(!(features instanceof Array)){features=[features];}
for(var i=0;i<features.length;++i){delete this.features[features[i].id];}
this.redraw();},redraw:function(){if(!this.locked){this.clear();var labelMap=[];var feature,style;for(var id in this.features){if(!this.features.hasOwnProperty(id)){continue;}
feature=this.features[id][0];style=this.features[id][1];if(!feature.geometry){continue;}
this.drawGeometry(feature.geometry,style);if(style.label){labelMap.push([feature,style]);}}
var item;for(var i=0;len=labelMap.length,i<len;++i){item=labelMap[i];this.drawText(item[0].geometry.getCentroid(),item[1]);}}},CLASS_NAME:"OpenLayers.Renderer.Canvas"});OpenLayers.Renderer.Canvas.LABEL_ALIGN={"l":"left","r":"right"};OpenLayers.ElementsIndexer=OpenLayers.Class({maxZIndex:null,order:null,indices:null,compare:null,initialize:function(yOrdering){this.compare=yOrdering?OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER:OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;this.order=[];this.indices={};this.maxZIndex=0;},insert:function(newNode){if(this.exists(newNode)){this.remove(newNode);}
var nodeId=newNode.id;this.determineZIndex(newNode);var leftIndex=-1;var rightIndex=this.order.length;var middle;while(rightIndex-leftIndex>1){middle=parseInt((leftIndex+rightIndex)/2);var placement=this.compare(this,newNode,OpenLayers.Util.getElement(this.order[middle]));if(placement>0){leftIndex=middle;}else{rightIndex=middle;}}
this.order.splice(rightIndex,0,nodeId);this.indices[nodeId]=this.getZIndex(newNode);return this.getNextElement(rightIndex);},remove:function(node){var nodeId=node.id;var arrayIndex=OpenLayers.Util.indexOf(this.order,nodeId);if(arrayIndex>=0){this.order.splice(arrayIndex,1);delete this.indices[nodeId];if(this.order.length>0){var lastId=this.order[this.order.length-1];this.maxZIndex=this.indices[lastId];}else{this.maxZIndex=0;}}},clear:function(){this.order=[];this.indices={};this.maxZIndex=0;},exists:function(node){return(this.indices[node.id]!=null);},getZIndex:function(node){return node._style.graphicZIndex;},determineZIndex:function(node){var zIndex=node._style.graphicZIndex;if(zIndex==null){zIndex=this.maxZIndex;node._style.graphicZIndex=zIndex;}else if(zIndex>this.maxZIndex){this.maxZIndex=zIndex;}},getNextElement:function(index){var nextIndex=index+1;if(nextIndex<this.order.length){var nextElement=OpenLayers.Util.getElement(this.order[nextIndex]);if(nextElement==undefined){nextElement=this.getNextElement(nextIndex);}
return nextElement;}else{return null;}},CLASS_NAME:"OpenLayers.ElementsIndexer"});OpenLayers.ElementsIndexer.IndexingMethods={Z_ORDER:function(indexer,newNode,nextNode){var newZIndex=indexer.getZIndex(newNode);var returnVal=0;if(nextNode){var nextZIndex=indexer.getZIndex(nextNode);returnVal=newZIndex-nextZIndex;}
return returnVal;},Z_ORDER_DRAWING_ORDER:function(indexer,newNode,nextNode){var returnVal=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(indexer,newNode,nextNode);if(nextNode&&returnVal==0){returnVal=1;}
return returnVal;},Z_ORDER_Y_ORDER:function(indexer,newNode,nextNode){var returnVal=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(indexer,newNode,nextNode);if(nextNode&&returnVal==0){var newLat=newNode._geometry.getBounds().bottom;var nextLat=nextNode._geometry.getBounds().bottom;var result=nextLat-newLat;returnVal=(result==0)?1:result;}
return returnVal;}};OpenLayers.Renderer.Elements=OpenLayers.Class(OpenLayers.Renderer,{rendererRoot:null,root:null,vectorRoot:null,textRoot:null,xmlns:null,indexer:null,BACKGROUND_ID_SUFFIX:"_background",LABEL_ID_SUFFIX:"_label",minimumSymbolizer:{strokeLinecap:"round",strokeOpacity:1,strokeDashstyle:"solid",fillOpacity:1,pointRadius:0},initialize:function(containerID,options){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.rendererRoot=this.createRenderRoot();this.root=this.createRoot("_root");this.vectorRoot=this.createRoot("_vroot");this.textRoot=this.createRoot("_troot");this.root.appendChild(this.vectorRoot);this.root.appendChild(this.textRoot);this.rendererRoot.appendChild(this.root);this.container.appendChild(this.rendererRoot);if(options&&(options.zIndexing||options.yOrdering)){this.indexer=new OpenLayers.ElementsIndexer(options.yOrdering);}},destroy:function(){this.clear();this.rendererRoot=null;this.root=null;this.xmlns=null;OpenLayers.Renderer.prototype.destroy.apply(this,arguments);},clear:function(){if(this.vectorRoot){while(this.vectorRoot.childNodes.length>0){this.vectorRoot.removeChild(this.vectorRoot.firstChild);}}
if(this.textRoot){while(this.textRoot.childNodes.length>0){this.textRoot.removeChild(this.textRoot.firstChild);}}
if(this.indexer){this.indexer.clear();}},getNodeType:function(geometry,style){},drawGeometry:function(geometry,style,featureId){var className=geometry.CLASS_NAME;var rendered=true;if((className=="OpenLayers.Geometry.Collection")||(className=="OpenLayers.Geometry.MultiPoint")||(className=="OpenLayers.Geometry.MultiLineString")||(className=="OpenLayers.Geometry.MultiPolygon")){for(var i=0,len=geometry.components.length;i<len;i++){rendered=this.drawGeometry(geometry.components[i],style,featureId)&&rendered;}
return rendered;};rendered=false;if(style.display!="none"){if(style.backgroundGraphic){this.redrawBackgroundNode(geometry.id,geometry,style,featureId);}
rendered=this.redrawNode(geometry.id,geometry,style,featureId);}
if(rendered==false){var node=document.getElementById(geometry.id);if(node){if(node._style.backgroundGraphic){node.parentNode.removeChild(document.getElementById(geometry.id+this.BACKGROUND_ID_SUFFIX));}
node.parentNode.removeChild(node);}}
return rendered;},redrawNode:function(id,geometry,style,featureId){var node=this.nodeFactory(id,this.getNodeType(geometry,style));node._featureId=featureId;node._geometry=geometry;node._geometryClass=geometry.CLASS_NAME;node._style=style;var drawResult=this.drawGeometryNode(node,geometry,style);if(drawResult===false){return false;}
node=drawResult.node;if(this.indexer){var insert=this.indexer.insert(node);if(insert){this.vectorRoot.insertBefore(node,insert);}else{this.vectorRoot.appendChild(node);}}else{if(node.parentNode!==this.vectorRoot){this.vectorRoot.appendChild(node);}}
this.postDraw(node);return drawResult.complete;},redrawBackgroundNode:function(id,geometry,style,featureId){var backgroundStyle=OpenLayers.Util.extend({},style);backgroundStyle.externalGraphic=backgroundStyle.backgroundGraphic;backgroundStyle.graphicXOffset=backgroundStyle.backgroundXOffset;backgroundStyle.graphicYOffset=backgroundStyle.backgroundYOffset;backgroundStyle.graphicZIndex=backgroundStyle.backgroundGraphicZIndex;backgroundStyle.graphicWidth=backgroundStyle.backgroundWidth||backgroundStyle.graphicWidth;backgroundStyle.graphicHeight=backgroundStyle.backgroundHeight||backgroundStyle.graphicHeight;backgroundStyle.backgroundGraphic=null;backgroundStyle.backgroundXOffset=null;backgroundStyle.backgroundYOffset=null;backgroundStyle.backgroundGraphicZIndex=null;return this.redrawNode(id+this.BACKGROUND_ID_SUFFIX,geometry,backgroundStyle,null);},drawGeometryNode:function(node,geometry,style){style=style||node._style;OpenLayers.Util.applyDefaults(style,this.minimumSymbolizer);var options={'isFilled':style.fill===undefined?true:style.fill,'isStroked':style.stroke===undefined?!!style.strokeWidth:style.stroke};var drawn;switch(geometry.CLASS_NAME){case"OpenLayers.Geometry.Point":if(style.graphic===false){options.isFilled=false;options.isStroked=false;}
drawn=this.drawPoint(node,geometry);break;case"OpenLayers.Geometry.LineString":options.isFilled=false;drawn=this.drawLineString(node,geometry);break;case"OpenLayers.Geometry.LinearRing":drawn=this.drawLinearRing(node,geometry);break;case"OpenLayers.Geometry.Polygon":drawn=this.drawPolygon(node,geometry);break;case"OpenLayers.Geometry.Surface":drawn=this.drawSurface(node,geometry);break;case"OpenLayers.Geometry.Rectangle":drawn=this.drawRectangle(node,geometry);break;default:break;}
node._style=style;node._options=options;if(drawn!=false){return{node:this.setStyle(node,style,options,geometry),complete:drawn};}else{return false;}},postDraw:function(node){},drawPoint:function(node,geometry){},drawLineString:function(node,geometry){},drawLinearRing:function(node,geometry){},drawPolygon:function(node,geometry){},drawRectangle:function(node,geometry){},drawCircle:function(node,geometry){},drawSurface:function(node,geometry){},removeText:function(featureId){var label=document.getElementById(featureId+this.LABEL_ID_SUFFIX);if(label){this.textRoot.removeChild(label);}},getFeatureIdFromEvent:function(evt){var target=evt.target;var useElement=target&&target.correspondingUseElement;var node=useElement?useElement:(target||evt.srcElement);var featureId=node._featureId;return featureId;},eraseGeometry:function(geometry){if((geometry.CLASS_NAME=="OpenLayers.Geometry.MultiPoint")||(geometry.CLASS_NAME=="OpenLayers.Geometry.MultiLineString")||(geometry.CLASS_NAME=="OpenLayers.Geometry.MultiPolygon")||(geometry.CLASS_NAME=="OpenLayers.Geometry.Collection")){for(var i=0,len=geometry.components.length;i<len;i++){this.eraseGeometry(geometry.components[i]);}}else{var element=OpenLayers.Util.getElement(geometry.id);if(element&&element.parentNode){if(element.geometry){element.geometry.destroy();element.geometry=null;}
element.parentNode.removeChild(element);if(this.indexer){this.indexer.remove(element);}
if(element._style.backgroundGraphic){var backgroundId=geometry.id+this.BACKGROUND_ID_SUFFIX;var bElem=OpenLayers.Util.getElement(backgroundId);if(bElem&&bElem.parentNode){bElem.parentNode.removeChild(bElem);}}}}},nodeFactory:function(id,type){var node=OpenLayers.Util.getElement(id);if(node){if(!this.nodeTypeCompare(node,type)){node.parentNode.removeChild(node);node=this.nodeFactory(id,type);}}else{node=this.createNode(type,id);}
return node;},nodeTypeCompare:function(node,type){},createNode:function(type,id){},moveRoot:function(renderer){var root=this.root;if(renderer.root.parentNode==this.rendererRoot){root=renderer.root;}
root.parentNode.removeChild(root);renderer.rendererRoot.appendChild(root);},getRenderLayerId:function(){return this.root.parentNode.parentNode.id;},isComplexSymbol:function(graphicName){return(graphicName!="circle")&&!!graphicName;},CLASS_NAME:"OpenLayers.Renderer.Elements"});OpenLayers.Renderer.symbol={"star":[350,75,379,161,469,161,397,215,423,301,350,250,277,301,303,215,231,161,321,161,350,75],"cross":[4,0,6,0,6,4,10,4,10,6,6,6,6,10,4,10,4,6,0,6,0,4,4,4,4,0],"x":[0,0,25,0,50,35,75,0,100,0,65,50,100,100,75,100,50,65,25,100,0,100,35,50,0,0],"square":[0,0,0,1,1,1,1,0,0,0],"triangle":[0,10,10,10,5,0,0,10]};OpenLayers.Strategy.Cluster=OpenLayers.Class(OpenLayers.Strategy,{distance:20,threshold:null,features:null,clusters:null,clustering:false,resolution:null,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.call(this);if(activated){this.layer.events.on({"beforefeaturesadded":this.cacheFeatures,"moveend":this.cluster,scope:this});}
return activated;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){this.clearCache();this.layer.events.un({"beforefeaturesadded":this.cacheFeatures,"moveend":this.cluster,scope:this});}
return deactivated;},cacheFeatures:function(event){var propagate=true;if(!this.clustering){this.clearCache();this.features=event.features;this.cluster();propagate=false;}
return propagate;},clearCache:function(){this.features=null;},cluster:function(event){if((!event||event.zoomChanged)&&this.features){var resolution=this.layer.map.getResolution();if(resolution!=this.resolution||!this.clustersExist()){this.resolution=resolution;var clusters=[];var feature,clustered,cluster;for(var i=0;i<this.features.length;++i){feature=this.features[i];if(feature.geometry){clustered=false;for(var j=0;j<clusters.length;++j){cluster=clusters[j];if(this.shouldCluster(cluster,feature)){this.addToCluster(cluster,feature);clustered=true;break;}}
if(!clustered){clusters.push(this.createCluster(this.features[i]));}}}
this.layer.destroyFeatures();if(clusters.length>0){if(this.threshold>1){var clone=clusters.slice();clusters=[];var candidate;for(var i=0,len=clone.length;i<len;++i){candidate=clone[i];if(candidate.attributes.count<this.threshold){Array.prototype.push.apply(clusters,candidate.cluster);}else{clusters.push(candidate);}}}
this.clustering=true;this.layer.addFeatures(clusters);this.clustering=false;}
this.clusters=clusters;}}},clustersExist:function(){var exist=false;if(this.clusters&&this.clusters.length>0&&this.clusters.length==this.layer.features.length){exist=true;for(var i=0;i<this.clusters.length;++i){if(this.clusters[i]!=this.layer.features[i]){exist=false;break;}}}
return exist;},shouldCluster:function(cluster,feature){var cc=cluster.geometry.getBounds().getCenterLonLat();var fc=feature.geometry.getBounds().getCenterLonLat();var distance=(Math.sqrt(Math.pow((cc.lon-fc.lon),2)+Math.pow((cc.lat-fc.lat),2))/this.resolution);return(distance<=this.distance);},addToCluster:function(cluster,feature){cluster.cluster.push(feature);cluster.attributes.count+=1;},createCluster:function(feature){var center=feature.geometry.getBounds().getCenterLonLat();var cluster=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(center.lon,center.lat),{count:1});cluster.cluster=[feature];return cluster;},CLASS_NAME:"OpenLayers.Strategy.Cluster"});OpenLayers.Strategy.Fixed=OpenLayers.Class(OpenLayers.Strategy,{preload:false,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},destroy:function(){OpenLayers.Strategy.prototype.destroy.apply(this,arguments);},activate:function(){if(OpenLayers.Strategy.prototype.activate.apply(this,arguments)){this.layer.events.on({"refresh":this.load,scope:this});if(this.layer.visibility==true||this.preload){this.load();}else{this.layer.events.on({"visibilitychanged":this.load,scope:this});}
return true;}
return false;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){this.layer.events.un({"refresh":this.load,"visibilitychanged":this.load,scope:this});}
return deactivated;},load:function(options){this.layer.events.triggerEvent("loadstart");this.layer.protocol.read(OpenLayers.Util.applyDefaults({callback:this.merge,scope:this},options));this.layer.events.un({"visibilitychanged":this.load,scope:this});},merge:function(resp){this.layer.destroyFeatures();var features=resp.features;if(features&&features.length>0){var remote=this.layer.projection;var local=this.layer.map.getProjectionObject();if(!local.equals(remote)){var geom;for(var i=0,len=features.length;i<len;++i){geom=features[i].geometry;if(geom){geom.transform(remote,local);}}}
this.layer.addFeatures(features);}
this.layer.events.triggerEvent("loadend");},CLASS_NAME:"OpenLayers.Strategy.Fixed"});OpenLayers.Strategy.Paging=OpenLayers.Class(OpenLayers.Strategy,{features:null,length:10,num:null,paging:false,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.call(this);if(activated){this.layer.events.on({"beforefeaturesadded":this.cacheFeatures,scope:this});}
return activated;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){this.clearCache();this.layer.events.un({"beforefeaturesadded":this.cacheFeatures,scope:this});}
return deactivated;},cacheFeatures:function(event){if(!this.paging){this.clearCache();this.features=event.features;this.pageNext(event);}},clearCache:function(){if(this.features){for(var i=0;i<this.features.length;++i){this.features[i].destroy();}}
this.features=null;this.num=null;},pageCount:function(){var numFeatures=this.features?this.features.length:0;return Math.ceil(numFeatures/this.length);},pageNum:function(){return this.num;},pageLength:function(newLength){if(newLength&&newLength>0){this.length=newLength;}
return this.length;},pageNext:function(event){var changed=false;if(this.features){if(this.num===null){this.num=-1;}
var start=(this.num+1)*this.length;changed=this.page(start,event);}
return changed;},pagePrevious:function(){var changed=false;if(this.features){if(this.num===null){this.num=this.pageCount();}
var start=(this.num-1)*this.length;changed=this.page(start);}
return changed;},page:function(start,event){var changed=false;if(this.features){if(start>=0&&start<this.features.length){var num=Math.floor(start/this.length);if(num!=this.num){this.paging=true;var features=this.features.slice(start,start+this.length);this.layer.removeFeatures(this.layer.features);this.num=num;if(event&&event.features){event.features=features;}else{this.layer.addFeatures(features);}
this.paging=false;changed=true;}}}
return changed;},CLASS_NAME:"OpenLayers.Strategy.Paging"});OpenLayers.Strategy.Save=OpenLayers.Class(OpenLayers.Strategy,{auto:false,timer:null,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.call(this);if(activated){if(this.auto){if(typeof this.auto==="number"){this.timer=window.setInterval(OpenLayers.Function.bind(this.save,this),this.auto*1000)}else{this.layer.events.on({"featureadded":this.triggerSave,"afterfeaturemodified":this.triggerSave,scope:this});}}}
return activated;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){if(this.auto){if(typeof this.auto==="number"){window.clearInterval(this.timer);}else{this.layer.events.un({"featureadded":this.triggerSave,"afterfeaturemodified":this.triggerSave,scope:this})}}}
return deactivated;},triggerSave:function(event){var feature=event.feature;if(feature.state===OpenLayers.State.INSERT||feature.state===OpenLayers.State.UPDATE||feature.state===OpenLayers.State.DELETE){this.save([event.feature]);}},save:function(features){if(!features){features=this.layer.features;}
var remote=this.layer.projection;var local=this.layer.map.getProjectionObject();if(!local.equals(remote)){var len=features.length;var clones=new Array(len);var orig,clone;for(var i=0;i<len;++i){orig=features[i];clone=orig.clone();clone.fid=orig.fid;clone.state=orig.state;clone._original=orig;clone.geometry.transform(local,remote);clones[i]=clone;}
features=clones;}
this.layer.protocol.commit(features,{callback:this.onCommit,scope:this});},onCommit:function(response){if(response.success()){var features=response.reqFeatures;var state,feature;var destroys=[];var insertIds=response.insertIds||[];var j=0;for(var i=0,len=features.length;i<len;++i){feature=features[i];feature=feature._original||feature;state=feature.state;if(state){if(state==OpenLayers.State.DELETE){destroys.push(feature);}else if(state==OpenLayers.State.INSERT){feature.fid=insertIds[j];++j;}
feature.state=null;}}
if(destroys.length>0){this.layer.destroyFeatures(destroys);}}},CLASS_NAME:"OpenLayers.Strategy.Save"});OpenLayers.Tween=OpenLayers.Class({INTERVAL:10,easing:null,begin:null,finish:null,duration:null,callbacks:null,time:null,interval:null,playing:false,initialize:function(easing){this.easing=(easing)?easing:OpenLayers.Easing.Expo.easeOut;},start:function(begin,finish,duration,options){this.playing=true;this.begin=begin;this.finish=finish;this.duration=duration;this.callbacks=options.callbacks;this.time=0;if(this.interval){window.clearInterval(this.interval);this.interval=null;}
if(this.callbacks&&this.callbacks.start){this.callbacks.start.call(this,this.begin);}
this.interval=window.setInterval(OpenLayers.Function.bind(this.play,this),this.INTERVAL);},stop:function(){if(!this.playing){return;}
if(this.callbacks&&this.callbacks.done){this.callbacks.done.call(this,this.finish);}
window.clearInterval(this.interval);this.interval=null;this.playing=false;},play:function(){var value={};for(var i in this.begin){var b=this.begin[i];var f=this.finish[i];if(b==null||f==null||isNaN(b)||isNaN(f)){OpenLayers.Console.error('invalid value for Tween');}
var c=f-b;value[i]=this.easing.apply(this,[this.time,b,c,this.duration]);}
this.time++;if(this.callbacks&&this.callbacks.eachStep){this.callbacks.eachStep.call(this,value);}
if(this.time>this.duration){if(this.callbacks&&this.callbacks.done){this.callbacks.done.call(this,this.finish);this.playing=false;}
window.clearInterval(this.interval);this.interval=null;}},CLASS_NAME:"OpenLayers.Tween"});OpenLayers.Easing={CLASS_NAME:"OpenLayers.Easing"};OpenLayers.Easing.Linear={easeIn:function(t,b,c,d){return c*t/d+b;},easeOut:function(t,b,c,d){return c*t/d+b;},easeInOut:function(t,b,c,d){return c*t/d+b;},CLASS_NAME:"OpenLayers.Easing.Linear"};OpenLayers.Easing.Expo={easeIn:function(t,b,c,d){return(t==0)?b:c*Math.pow(2,10*(t/d-1))+b;},easeOut:function(t,b,c,d){return(t==d)?b+c:c*(-Math.pow(2,-10*t/d)+1)+b;},easeInOut:function(t,b,c,d){if(t==0)return b;if(t==d)return b+c;if((t/=d/2)<1)return c/2*Math.pow(2,10*(t-1))+b;return c/2*(-Math.pow(2,-10*--t)+2)+b;},CLASS_NAME:"OpenLayers.Easing.Expo"};OpenLayers.Easing.Quad={easeIn:function(t,b,c,d){return c*(t/=d)*t+b;},easeOut:function(t,b,c,d){return-c*(t/=d)*(t-2)+b;},easeInOut:function(t,b,c,d){if((t/=d/2)<1)return c/2*t*t+b;return-c/2*((--t)*(t-2)-1)+b;},CLASS_NAME:"OpenLayers.Easing.Quad"};OpenLayers.Control.ArgParser=OpenLayers.Class(OpenLayers.Control,{center:null,zoom:null,layers:null,displayProjection:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},setMap:function(map){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var i=0,len=this.map.controls.length;i<len;i++){var control=this.map.controls[i];if((control!=this)&&(control.CLASS_NAME=="OpenLayers.Control.ArgParser")){if(control.displayProjection!=this.displayProjection){this.displayProjection=control.displayProjection;}
break;}}
if(i==this.map.controls.length){var args=OpenLayers.Util.getParameters();if(args.layers){this.layers=args.layers;this.map.events.register('addlayer',this,this.configureLayers);this.configureLayers();}
if(args.lat&&args.lon){this.center=new OpenLayers.LonLat(parseFloat(args.lon),parseFloat(args.lat));if(args.zoom){this.zoom=parseInt(args.zoom);}
this.map.events.register('changebaselayer',this,this.setCenter);this.setCenter();}}},setCenter:function(){if(this.map.baseLayer){this.map.events.unregister('changebaselayer',this,this.setCenter);if(this.displayProjection){this.center.transform(this.displayProjection,this.map.getProjectionObject());}
this.map.setCenter(this.center,this.zoom);}},configureLayers:function(){if(this.layers.length==this.map.layers.length){this.map.events.unregister('addlayer',this,this.configureLayers);for(var i=0,len=this.layers.length;i<len;i++){var layer=this.map.layers[i];var c=this.layers.charAt(i);if(c=="B"){this.map.setBaseLayer(layer);}else if((c=="T")||(c=="F")){layer.setVisibility(c=="T");}}}},CLASS_NAME:"OpenLayers.Control.ArgParser"});OpenLayers.Control.Attribution=OpenLayers.Class(OpenLayers.Control,{separator:", ",initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){this.map.events.un({"removelayer":this.updateAttribution,"addlayer":this.updateAttribution,"changelayer":this.updateAttribution,"changebaselayer":this.updateAttribution,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.map.events.on({'changebaselayer':this.updateAttribution,'changelayer':this.updateAttribution,'addlayer':this.updateAttribution,'removelayer':this.updateAttribution,scope:this});this.updateAttribution();return this.div;},updateAttribution:function(){var attributions=[];if(this.map&&this.map.layers){for(var i=0,len=this.map.layers.length;i<len;i++){var layer=this.map.layers[i];if(layer.attribution&&layer.getVisibility()){attributions.push(layer.attribution);}}
this.div.innerHTML=attributions.join(this.separator);}},CLASS_NAME:"OpenLayers.Control.Attribution"});OpenLayers.Control.Button=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){},CLASS_NAME:"OpenLayers.Control.Button"});OpenLayers.Control.LayerSwitcher=OpenLayers.Class(OpenLayers.Control,{activeColor:"darkblue",layerStates:null,layersDiv:null,baseLayersDiv:null,baseLayers:null,dataLbl:null,dataLayersDiv:null,dataLayers:null,minimizeDiv:null,maximizeDiv:null,ascending:true,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.layerStates=[];},destroy:function(){OpenLayers.Event.stopObservingElement(this.div);OpenLayers.Event.stopObservingElement(this.minimizeDiv);OpenLayers.Event.stopObservingElement(this.maximizeDiv);this.clearLayersArray("base");this.clearLayersArray("data");this.map.events.un({"addlayer":this.redraw,"changelayer":this.redraw,"removelayer":this.redraw,"changebaselayer":this.redraw,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments);},setMap:function(map){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.on({"addlayer":this.redraw,"changelayer":this.redraw,"removelayer":this.redraw,"changebaselayer":this.redraw,scope:this});},draw:function(){OpenLayers.Control.prototype.draw.apply(this);this.loadContents();if(!this.outsideViewport){this.minimizeControl();}
this.redraw();return this.div;},clearLayersArray:function(layersType){var layers=this[layersType+"Layers"];if(layers){for(var i=0,len=layers.length;i<len;i++){var layer=layers[i];OpenLayers.Event.stopObservingElement(layer.inputElem);OpenLayers.Event.stopObservingElement(layer.labelSpan);}}
this[layersType+"LayersDiv"].innerHTML="";this[layersType+"Layers"]=[];},checkRedraw:function(){var redraw=false;if(!this.layerStates.length||(this.map.layers.length!=this.layerStates.length)){redraw=true;}else{for(var i=0,len=this.layerStates.length;i<len;i++){var layerState=this.layerStates[i];var layer=this.map.layers[i];if((layerState.name!=layer.name)||(layerState.inRange!=layer.inRange)||(layerState.id!=layer.id)||(layerState.visibility!=layer.visibility)){redraw=true;break;}}}
return redraw;},redraw:function(){if(!this.checkRedraw()){return this.div;}
this.clearLayersArray("base");this.clearLayersArray("data");var containsOverlays=false;var containsBaseLayers=false;var len=this.map.layers.length;this.layerStates=new Array(len);for(var i=0;i<len;i++){var layer=this.map.layers[i];this.layerStates[i]={'name':layer.name,'visibility':layer.visibility,'inRange':layer.inRange,'id':layer.id};}
var layers=this.map.layers.slice();if(!this.ascending){layers.reverse();}
for(var i=0,len=layers.length;i<len;i++){var layer=layers[i];var baseLayer=layer.isBaseLayer;if(layer.displayInLayerSwitcher){if(baseLayer){containsBaseLayers=true;}else{containsOverlays=true;}
var checked=(baseLayer)?(layer==this.map.baseLayer):layer.getVisibility();var inputElem=document.createElement("input");inputElem.id=this.id+"_input_"+layer.name;inputElem.name=(baseLayer)?"baseLayers":layer.name;inputElem.type=(baseLayer)?"radio":"checkbox";inputElem.value=layer.name;inputElem.checked=checked;inputElem.defaultChecked=checked;if(!baseLayer&&!layer.inRange){inputElem.disabled=true;}
var context={'inputElem':inputElem,'layer':layer,'layerSwitcher':this};OpenLayers.Event.observe(inputElem,"mouseup",OpenLayers.Function.bindAsEventListener(this.onInputClick,context));var labelSpan=document.createElement("span");if(!baseLayer&&!layer.inRange){labelSpan.style.color="gray";}
labelSpan.innerHTML=layer.name;labelSpan.style.verticalAlign=(baseLayer)?"bottom":"baseline";OpenLayers.Event.observe(labelSpan,"click",OpenLayers.Function.bindAsEventListener(this.onInputClick,context));var br=document.createElement("br");var groupArray=(baseLayer)?this.baseLayers:this.dataLayers;groupArray.push({'layer':layer,'inputElem':inputElem,'labelSpan':labelSpan});var groupDiv=(baseLayer)?this.baseLayersDiv:this.dataLayersDiv;groupDiv.appendChild(inputElem);groupDiv.appendChild(labelSpan);groupDiv.appendChild(br);}}
this.dataLbl.style.display=(containsOverlays)?"":"none";this.baseLbl.style.display=(containsBaseLayers)?"":"none";return this.div;},onInputClick:function(e){if(!this.inputElem.disabled){if(this.inputElem.type=="radio"){this.inputElem.checked=true;this.layer.map.setBaseLayer(this.layer);}else{this.inputElem.checked=!this.inputElem.checked;this.layerSwitcher.updateMap();}}
OpenLayers.Event.stop(e);},onLayerClick:function(e){this.updateMap();},updateMap:function(){for(var i=0,len=this.baseLayers.length;i<len;i++){var layerEntry=this.baseLayers[i];if(layerEntry.inputElem.checked){this.map.setBaseLayer(layerEntry.layer,false);}}
for(var i=0,len=this.dataLayers.length;i<len;i++){var layerEntry=this.dataLayers[i];layerEntry.layer.setVisibility(layerEntry.inputElem.checked);}},maximizeControl:function(e){this.div.style.width="20em";this.div.style.height="";this.showControls(false);if(e!=null){OpenLayers.Event.stop(e);}},minimizeControl:function(e){this.div.style.width="0px";this.div.style.height="0px";this.showControls(true);if(e!=null){OpenLayers.Event.stop(e);}},showControls:function(minimize){this.maximizeDiv.style.display=minimize?"":"none";this.minimizeDiv.style.display=minimize?"none":"";this.layersDiv.style.display=minimize?"none":"";},loadContents:function(){this.div.style.position="absolute";this.div.style.top="25px";this.div.style.right="0px";this.div.style.left="";this.div.style.fontFamily="sans-serif";this.div.style.fontWeight="bold";this.div.style.marginTop="3px";this.div.style.marginLeft="3px";this.div.style.marginBottom="3px";this.div.style.fontSize="smaller";this.div.style.color="white";this.div.style.backgroundColor="transparent";OpenLayers.Event.observe(this.div,"mouseup",OpenLayers.Function.bindAsEventListener(this.mouseUp,this));OpenLayers.Event.observe(this.div,"click",this.ignoreEvent);OpenLayers.Event.observe(this.div,"mousedown",OpenLayers.Function.bindAsEventListener(this.mouseDown,this));OpenLayers.Event.observe(this.div,"dblclick",this.ignoreEvent);this.layersDiv=document.createElement("div");this.layersDiv.id=this.id+"_layersDiv";this.layersDiv.style.paddingTop="5px";this.layersDiv.style.paddingLeft="10px";this.layersDiv.style.paddingBottom="5px";this.layersDiv.style.paddingRight="75px";this.layersDiv.style.backgroundColor=this.activeColor;this.layersDiv.style.width="100%";this.layersDiv.style.height="100%";this.baseLbl=document.createElement("div");this.baseLbl.innerHTML=OpenLayers.i18n("baseLayer");this.baseLbl.style.marginTop="3px";this.baseLbl.style.marginLeft="3px";this.baseLbl.style.marginBottom="3px";this.baseLayersDiv=document.createElement("div");this.baseLayersDiv.style.paddingLeft="10px";this.dataLbl=document.createElement("div");this.dataLbl.innerHTML=OpenLayers.i18n("overlays");this.dataLbl.style.marginTop="3px";this.dataLbl.style.marginLeft="3px";this.dataLbl.style.marginBottom="3px";this.dataLayersDiv=document.createElement("div");this.dataLayersDiv.style.paddingLeft="10px";if(this.ascending){this.layersDiv.appendChild(this.baseLbl);this.layersDiv.appendChild(this.baseLayersDiv);this.layersDiv.appendChild(this.dataLbl);this.layersDiv.appendChild(this.dataLayersDiv);}else{this.layersDiv.appendChild(this.dataLbl);this.layersDiv.appendChild(this.dataLayersDiv);this.layersDiv.appendChild(this.baseLbl);this.layersDiv.appendChild(this.baseLayersDiv);}
this.div.appendChild(this.layersDiv);OpenLayers.Rico.Corner.round(this.div,{corners:"tl bl",bgColor:"transparent",color:this.activeColor,blend:false});OpenLayers.Rico.Corner.changeOpacity(this.layersDiv,0.75);var imgLocation=OpenLayers.Util.getImagesLocation();var sz=new OpenLayers.Size(18,18);var img=imgLocation+'layer-switcher-maximize.png';this.maximizeDiv=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MaximizeDiv",null,sz,img,"absolute");this.maximizeDiv.style.top="5px";this.maximizeDiv.style.right="0px";this.maximizeDiv.style.left="";this.maximizeDiv.style.display="none";OpenLayers.Event.observe(this.maximizeDiv,"click",OpenLayers.Function.bindAsEventListener(this.maximizeControl,this));this.div.appendChild(this.maximizeDiv);var img=imgLocation+'layer-switcher-minimize.png';var sz=new OpenLayers.Size(18,18);this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MinimizeDiv",null,sz,img,"absolute");this.minimizeDiv.style.top="5px";this.minimizeDiv.style.right="0px";this.minimizeDiv.style.left="";this.minimizeDiv.style.display="none";OpenLayers.Event.observe(this.minimizeDiv,"click",OpenLayers.Function.bindAsEventListener(this.minimizeControl,this));this.div.appendChild(this.minimizeDiv);},ignoreEvent:function(evt){OpenLayers.Event.stop(evt);},mouseDown:function(evt){this.isMouseDown=true;this.ignoreEvent(evt);},mouseUp:function(evt){if(this.isMouseDown){this.isMouseDown=false;this.ignoreEvent(evt);}},CLASS_NAME:"OpenLayers.Control.LayerSwitcher"});OpenLayers.Control.MouseDefaults=OpenLayers.Class(OpenLayers.Control,{performedDrag:false,wheelObserver:null,initialize:function(){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){if(this.handler){this.handler.destroy();}
this.handler=null;this.map.events.un({"click":this.defaultClick,"dblclick":this.defaultDblClick,"mousedown":this.defaultMouseDown,"mouseup":this.defaultMouseUp,"mousemove":this.defaultMouseMove,"mouseout":this.defaultMouseOut,scope:this});OpenLayers.Event.stopObserving(window,"DOMMouseScroll",this.wheelObserver);OpenLayers.Event.stopObserving(window,"mousewheel",this.wheelObserver);OpenLayers.Event.stopObserving(document,"mousewheel",this.wheelObserver);this.wheelObserver=null;OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){this.map.events.on({"click":this.defaultClick,"dblclick":this.defaultDblClick,"mousedown":this.defaultMouseDown,"mouseup":this.defaultMouseUp,"mousemove":this.defaultMouseMove,"mouseout":this.defaultMouseOut,scope:this});this.registerWheelEvents();},registerWheelEvents:function(){this.wheelObserver=OpenLayers.Function.bindAsEventListener(this.onWheelEvent,this);OpenLayers.Event.observe(window,"DOMMouseScroll",this.wheelObserver);OpenLayers.Event.observe(window,"mousewheel",this.wheelObserver);OpenLayers.Event.observe(document,"mousewheel",this.wheelObserver);},defaultClick:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
var notAfterDrag=!this.performedDrag;this.performedDrag=false;return notAfterDrag;},defaultDblClick:function(evt){var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom+1);OpenLayers.Event.stop(evt);return false;},defaultMouseDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
this.mouseDragStart=evt.xy.clone();this.performedDrag=false;if(evt.shiftKey){this.map.div.style.cursor="crosshair";this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.mouseDragStart,null,null,"absolute","2px solid red");this.zoomBox.style.backgroundColor="white";this.zoomBox.style.filter="alpha(opacity=50)";this.zoomBox.style.opacity="0.50";this.zoomBox.style.fontSize="1px";this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);}
document.onselectstart=function(){return false;};OpenLayers.Event.stop(evt);},defaultMouseMove:function(evt){this.mousePosition=evt.xy.clone();if(this.mouseDragStart!=null){if(this.zoomBox){var deltaX=Math.abs(this.mouseDragStart.x-evt.xy.x);var deltaY=Math.abs(this.mouseDragStart.y-evt.xy.y);this.zoomBox.style.width=Math.max(1,deltaX)+"px";this.zoomBox.style.height=Math.max(1,deltaY)+"px";if(evt.xy.x<this.mouseDragStart.x){this.zoomBox.style.left=evt.xy.x+"px";}
if(evt.xy.y<this.mouseDragStart.y){this.zoomBox.style.top=evt.xy.y+"px";}}else{var deltaX=this.mouseDragStart.x-evt.xy.x;var deltaY=this.mouseDragStart.y-evt.xy.y;var size=this.map.getSize();var newXY=new OpenLayers.Pixel(size.w/2+deltaX,size.h/2+deltaY);var newCenter=this.map.getLonLatFromViewPortPx(newXY);this.map.setCenter(newCenter,null,true);this.mouseDragStart=evt.xy.clone();this.map.div.style.cursor="move";}
this.performedDrag=true;}},defaultMouseUp:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
if(this.zoomBox){this.zoomBoxEnd(evt);}else{if(this.performedDrag){this.map.setCenter(this.map.center);}}
document.onselectstart=null;this.mouseDragStart=null;this.map.div.style.cursor="";},defaultMouseOut:function(evt){if(this.mouseDragStart!=null&&OpenLayers.Util.mouseLeft(evt,this.map.div)){if(this.zoomBox){this.removeZoomBox();}
this.mouseDragStart=null;}},defaultWheelUp:function(evt){if(this.map.getZoom()<=this.map.getNumZoomLevels()){this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),this.map.getZoom()+1);}},defaultWheelDown:function(evt){if(this.map.getZoom()>0){this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),this.map.getZoom()-1);}},zoomBoxEnd:function(evt){if(this.mouseDragStart!=null){if(Math.abs(this.mouseDragStart.x-evt.xy.x)>5||Math.abs(this.mouseDragStart.y-evt.xy.y)>5){var start=this.map.getLonLatFromViewPortPx(this.mouseDragStart);var end=this.map.getLonLatFromViewPortPx(evt.xy);var top=Math.max(start.lat,end.lat);var bottom=Math.min(start.lat,end.lat);var left=Math.min(start.lon,end.lon);var right=Math.max(start.lon,end.lon);var bounds=new OpenLayers.Bounds(left,bottom,right,top);this.map.zoomToExtent(bounds);}else{var end=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(new OpenLayers.LonLat((end.lon),(end.lat)),this.map.getZoom()+1);}
this.removeZoomBox();}},removeZoomBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.zoomBox=null;},onWheelEvent:function(e){var inMap=false;var elem=OpenLayers.Event.element(e);while(elem!=null){if(this.map&&elem==this.map.div){inMap=true;break;}
elem=elem.parentNode;}
if(inMap){var delta=0;if(!e){e=window.event;}
if(e.wheelDelta){delta=e.wheelDelta/120;if(window.opera&&window.opera.version()<9.2){delta=-delta;}}else if(e.detail){delta=-e.detail/3;}
if(delta){e.xy=this.mousePosition;if(delta<0){this.defaultWheelDown(e);}else{this.defaultWheelUp(e);}}
OpenLayers.Event.stop(e);}},CLASS_NAME:"OpenLayers.Control.MouseDefaults"});OpenLayers.Control.MousePosition=OpenLayers.Class(OpenLayers.Control,{element:null,prefix:'',separator:', ',suffix:'',numDigits:5,granularity:10,lastXy:null,displayProjection:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){if(this.map){this.map.events.unregister('mousemove',this,this.redraw);}
OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.element){this.div.left="";this.div.top="";this.element=this.div;}
this.redraw();return this.div;},redraw:function(evt){var lonLat;if(evt==null){lonLat=new OpenLayers.LonLat(0,0);}else{if(this.lastXy==null||Math.abs(evt.xy.x-this.lastXy.x)>this.granularity||Math.abs(evt.xy.y-this.lastXy.y)>this.granularity)
{this.lastXy=evt.xy;return;}
lonLat=this.map.getLonLatFromPixel(evt.xy);if(!lonLat){return;}
if(this.displayProjection){lonLat.transform(this.map.getProjectionObject(),this.displayProjection);}
this.lastXy=evt.xy;}
var newHtml=this.formatOutput(lonLat);if(newHtml!=this.element.innerHTML){this.element.innerHTML=newHtml;}},formatOutput:function(lonLat){var digits=parseInt(this.numDigits);var newHtml=this.prefix+
lonLat.lon.toFixed(digits)+
this.separator+
lonLat.lat.toFixed(digits)+
this.suffix;return newHtml;},setMap:function(){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.register('mousemove',this,this.redraw);},CLASS_NAME:"OpenLayers.Control.MousePosition"});OpenLayers.Control.Pan=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,direction:null,type:OpenLayers.Control.TYPE_BUTTON,initialize:function(direction,options){this.direction=direction;this.CLASS_NAME+=this.direction;OpenLayers.Control.prototype.initialize.apply(this,[options]);},trigger:function(){switch(this.direction){case OpenLayers.Control.Pan.NORTH:this.map.pan(0,-this.slideFactor);break;case OpenLayers.Control.Pan.SOUTH:this.map.pan(0,this.slideFactor);break;case OpenLayers.Control.Pan.WEST:this.map.pan(-this.slideFactor,0);break;case OpenLayers.Control.Pan.EAST:this.map.pan(this.slideFactor,0);break;}},CLASS_NAME:"OpenLayers.Control.Pan"});OpenLayers.Control.Pan.NORTH="North";OpenLayers.Control.Pan.SOUTH="South";OpenLayers.Control.Pan.EAST="East";OpenLayers.Control.Pan.WEST="West";OpenLayers.Control.PanZoom=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,slideRatio:null,buttons:null,position:null,initialize:function(options){this.position=new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,OpenLayers.Control.PanZoom.Y);OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this,arguments);this.removeButtons();this.buttons=null;this.position=null;},draw:function(px){OpenLayers.Control.prototype.draw.apply(this,arguments);px=this.position;this.buttons=[];var sz=new OpenLayers.Size(18,18);var centered=new OpenLayers.Pixel(px.x+sz.w/2,px.y);this._addButton("panup","north-mini.png",centered,sz);px.y=centered.y+sz.h;this._addButton("panleft","west-mini.png",px,sz);this._addButton("panright","east-mini.png",px.add(sz.w,0),sz);this._addButton("pandown","south-mini.png",centered.add(0,sz.h*2),sz);this._addButton("zoomin","zoom-plus-mini.png",centered.add(0,sz.h*3+5),sz);this._addButton("zoomworld","zoom-world-mini.png",centered.add(0,sz.h*4+5),sz);this._addButton("zoomout","zoom-minus-mini.png",centered.add(0,sz.h*5+5),sz);return this.div;},_addButton:function(id,img,xy,sz){var imgLocation=OpenLayers.Util.getImagesLocation()+img;var btn=OpenLayers.Util.createAlphaImageDiv(this.id+"_"+id,xy,sz,imgLocation,"absolute");this.div.appendChild(btn);OpenLayers.Event.observe(btn,"mousedown",OpenLayers.Function.bindAsEventListener(this.buttonDown,btn));OpenLayers.Event.observe(btn,"dblclick",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));OpenLayers.Event.observe(btn,"click",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));btn.action=id;btn.map=this.map;if(!this.slideRatio){var slideFactorPixels=this.slideFactor;var getSlideFactor=function(){return slideFactorPixels;};}else{var slideRatio=this.slideRatio;var getSlideFactor=function(dim){return this.map.getSize()[dim]*slideRatio;};}
btn.getSlideFactor=getSlideFactor;this.buttons.push(btn);return btn;},_removeButton:function(btn){OpenLayers.Event.stopObservingElement(btn);btn.map=null;this.div.removeChild(btn);OpenLayers.Util.removeItem(this.buttons,btn);},removeButtons:function(){for(var i=this.buttons.length-1;i>=0;--i){this._removeButton(this.buttons[i]);}},doubleClick:function(evt){OpenLayers.Event.stop(evt);return false;},buttonDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
switch(this.action){case"panup":this.map.pan(0,-this.getSlideFactor("h"));break;case"pandown":this.map.pan(0,this.getSlideFactor("h"));break;case"panleft":this.map.pan(-this.getSlideFactor("w"),0);break;case"panright":this.map.pan(this.getSlideFactor("w"),0);break;case"zoomin":this.map.zoomIn();break;case"zoomout":this.map.zoomOut();break;case"zoomworld":this.map.zoomToMaxExtent();break;}
OpenLayers.Event.stop(evt);},CLASS_NAME:"OpenLayers.Control.PanZoom"});OpenLayers.Control.PanZoom.X=4;OpenLayers.Control.PanZoom.Y=4;OpenLayers.Control.Panel=OpenLayers.Class(OpenLayers.Control,{controls:null,defaultControl:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.controls=[];},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this,arguments);for(var i=this.controls.length-1;i>=0;i--){if(this.controls[i].events){this.controls[i].events.un({"activate":this.redraw,"deactivate":this.redraw,scope:this});}
OpenLayers.Event.stopObservingElement(this.controls[i].panel_div);this.controls[i].panel_div=null;}},activate:function(){if(OpenLayers.Control.prototype.activate.apply(this,arguments)){for(var i=0,len=this.controls.length;i<len;i++){if(this.controls[i]==this.defaultControl){this.controls[i].activate();}}
this.redraw();return true;}else{return false;}},deactivate:function(){if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){for(var i=0,len=this.controls.length;i<len;i++){this.controls[i].deactivate();}
return true;}else{return false;}},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);for(var i=0,len=this.controls.length;i<len;i++){this.map.addControl(this.controls[i]);this.controls[i].deactivate();this.controls[i].events.on({"activate":this.redraw,"deactivate":this.redraw,scope:this});}
this.activate();return this.div;},redraw:function(){this.div.innerHTML="";if(this.active){for(var i=0,len=this.controls.length;i<len;i++){var element=this.controls[i].panel_div;if(this.controls[i].active){element.className=this.controls[i].displayClass+"ItemActive";}else{element.className=this.controls[i].displayClass+"ItemInactive";}
this.div.appendChild(element);}}},activateControl:function(control){if(!this.active){return false;}
if(control.type==OpenLayers.Control.TYPE_BUTTON){control.trigger();this.redraw();return;}
if(control.type==OpenLayers.Control.TYPE_TOGGLE){if(control.active){control.deactivate();}else{control.activate();}
this.redraw();return;}
for(var i=0,len=this.controls.length;i<len;i++){if(this.controls[i]!=control){if(this.controls[i].type!=OpenLayers.Control.TYPE_TOGGLE){this.controls[i].deactivate();}}}
control.activate();},addControls:function(controls){if(!(controls instanceof Array)){controls=[controls];}
this.controls=this.controls.concat(controls);for(var i=0,len=controls.length;i<len;i++){var element=document.createElement("div");var textNode=document.createTextNode(" ");controls[i].panel_div=element;if(controls[i].title!=""){controls[i].panel_div.title=controls[i].title;}
OpenLayers.Event.observe(controls[i].panel_div,"click",OpenLayers.Function.bind(this.onClick,this,controls[i]));OpenLayers.Event.observe(controls[i].panel_div,"mousedown",OpenLayers.Function.bindAsEventListener(OpenLayers.Event.stop));}
if(this.map){for(var i=0,len=controls.length;i<len;i++){this.map.addControl(controls[i]);controls[i].deactivate();controls[i].events.on({"activate":this.redraw,"deactivate":this.redraw,scope:this});}
this.redraw();}},onClick:function(ctrl,evt){OpenLayers.Event.stop(evt?evt:window.event);this.activateControl(ctrl);},getControlsBy:function(property,match){var test=(typeof match.test=="function");var found=OpenLayers.Array.filter(this.controls,function(item){return item[property]==match||(test&&match.test(item[property]));});return found;},getControlsByName:function(match){return this.getControlsBy("name",match);},getControlsByClass:function(match){return this.getControlsBy("CLASS_NAME",match);},CLASS_NAME:"OpenLayers.Control.Panel"});OpenLayers.Control.Scale=OpenLayers.Class(OpenLayers.Control,{element:null,initialize:function(element,options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.element=OpenLayers.Util.getElement(element);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.element){this.element=document.createElement("div");this.div.appendChild(this.element);}
this.map.events.register('moveend',this,this.updateScale);this.updateScale();return this.div;},updateScale:function(){var scale=this.map.getScale();if(!scale){return;}
if(scale>=9500&&scale<=950000){scale=Math.round(scale/1000)+"K";}else if(scale>=950000){scale=Math.round(scale/1000000)+"M";}else{scale=Math.round(scale);}
this.element.innerHTML=OpenLayers.i18n("scale",{'scaleDenom':scale});},CLASS_NAME:"OpenLayers.Control.Scale"});OpenLayers.Control.ScaleLine=OpenLayers.Class(OpenLayers.Control,{maxWidth:100,topOutUnits:"km",topInUnits:"m",bottomOutUnits:"mi",bottomInUnits:"ft",eTop:null,eBottom:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,[options]);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.eTop){this.div.style.display="block";this.div.style.position="absolute";this.eTop=document.createElement("div");this.eTop.className=this.displayClass+"Top";var theLen=this.topInUnits.length;this.div.appendChild(this.eTop);if((this.topOutUnits=="")||(this.topInUnits=="")){this.eTop.style.visibility="hidden";}else{this.eTop.style.visibility="visible";}
this.eBottom=document.createElement("div");this.eBottom.className=this.displayClass+"Bottom";this.div.appendChild(this.eBottom);if((this.bottomOutUnits=="")||(this.bottomInUnits=="")){this.eBottom.style.visibility="hidden";}else{this.eBottom.style.visibility="visible";}}
this.map.events.register('moveend',this,this.update);this.update();return this.div;},getBarLen:function(maxLen){var digits=parseInt(Math.log(maxLen)/Math.log(10));var pow10=Math.pow(10,digits);var firstChar=parseInt(maxLen/pow10);var barLen;if(firstChar>5){barLen=5;}else if(firstChar>2){barLen=2;}else{barLen=1;}
return barLen*pow10;},update:function(){var res=this.map.getResolution();if(!res){return;}
var curMapUnits=this.map.getUnits();var inches=OpenLayers.INCHES_PER_UNIT;var maxSizeData=this.maxWidth*res*inches[curMapUnits];var topUnits;var bottomUnits;if(maxSizeData>100000){topUnits=this.topOutUnits;bottomUnits=this.bottomOutUnits;}else{topUnits=this.topInUnits;bottomUnits=this.bottomInUnits;}
var topMax=maxSizeData/inches[topUnits];var bottomMax=maxSizeData/inches[bottomUnits];var topRounded=this.getBarLen(topMax);var bottomRounded=this.getBarLen(bottomMax);topMax=topRounded/inches[curMapUnits]*inches[topUnits];bottomMax=bottomRounded/inches[curMapUnits]*inches[bottomUnits];var topPx=topMax/res;var bottomPx=bottomMax/res;if(this.eBottom.style.visibility=="visible"){this.eBottom.style.width=Math.round(bottomPx)+"px";this.eBottom.innerHTML=bottomRounded+" "+bottomUnits;}
if(this.eTop.style.visibility=="visible"){this.eTop.style.width=Math.round(topPx)+"px";this.eTop.innerHTML=topRounded+" "+topUnits;}},CLASS_NAME:"OpenLayers.Control.ScaleLine"});OpenLayers.Control.ZoomIn=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){this.map.zoomIn();},CLASS_NAME:"OpenLayers.Control.ZoomIn"});OpenLayers.Control.ZoomOut=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){this.map.zoomOut();},CLASS_NAME:"OpenLayers.Control.ZoomOut"});OpenLayers.Control.ZoomToMaxExtent=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){if(this.map){this.map.zoomToMaxExtent();}},CLASS_NAME:"OpenLayers.Control.ZoomToMaxExtent"});OpenLayers.Event={observers:false,KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,element:function(event){return event.target||event.srcElement;},isLeftClick:function(event){return(((event.which)&&(event.which==1))||((event.button)&&(event.button==1)));},isRightClick:function(event){return(((event.which)&&(event.which==3))||((event.button)&&(event.button==2)));},stop:function(event,allowDefault){if(!allowDefault){if(event.preventDefault){event.preventDefault();}else{event.returnValue=false;}}
if(event.stopPropagation){event.stopPropagation();}else{event.cancelBubble=true;}},findElement:function(event,tagName){var element=OpenLayers.Event.element(event);while(element.parentNode&&(!element.tagName||(element.tagName.toUpperCase()!=tagName.toUpperCase()))){element=element.parentNode;}
return element;},observe:function(elementParam,name,observer,useCapture){var element=OpenLayers.Util.getElement(elementParam);useCapture=useCapture||false;if(name=='keypress'&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||element.attachEvent)){name='keydown';}
if(!this.observers){this.observers={};}
if(!element._eventCacheID){var idPrefix="eventCacheID_";if(element.id){idPrefix=element.id+"_"+idPrefix;}
element._eventCacheID=OpenLayers.Util.createUniqueID(idPrefix);}
var cacheID=element._eventCacheID;if(!this.observers[cacheID]){this.observers[cacheID]=[];}
this.observers[cacheID].push({'element':element,'name':name,'observer':observer,'useCapture':useCapture});if(element.addEventListener){element.addEventListener(name,observer,useCapture);}else if(element.attachEvent){element.attachEvent('on'+name,observer);}},stopObservingElement:function(elementParam){var element=OpenLayers.Util.getElement(elementParam);var cacheID=element._eventCacheID;this._removeElementObservers(OpenLayers.Event.observers[cacheID]);},_removeElementObservers:function(elementObservers){if(elementObservers){for(var i=elementObservers.length-1;i>=0;i--){var entry=elementObservers[i];var args=new Array(entry.element,entry.name,entry.observer,entry.useCapture);var removed=OpenLayers.Event.stopObserving.apply(this,args);}}},stopObserving:function(elementParam,name,observer,useCapture){useCapture=useCapture||false;var element=OpenLayers.Util.getElement(elementParam);var cacheID=element._eventCacheID;if(name=='keypress'){if(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||element.detachEvent){name='keydown';}}
var foundEntry=false;var elementObservers=OpenLayers.Event.observers[cacheID];if(elementObservers){var i=0;while(!foundEntry&&i<elementObservers.length){var cacheEntry=elementObservers[i];if((cacheEntry.name==name)&&(cacheEntry.observer==observer)&&(cacheEntry.useCapture==useCapture)){elementObservers.splice(i,1);if(elementObservers.length==0){delete OpenLayers.Event.observers[cacheID];}
foundEntry=true;break;}
i++;}}
if(foundEntry){if(element.removeEventListener){element.removeEventListener(name,observer,useCapture);}else if(element&&element.detachEvent){element.detachEvent('on'+name,observer);}}
return foundEntry;},unloadCache:function(){if(OpenLayers.Event&&OpenLayers.Event.observers){for(var cacheID in OpenLayers.Event.observers){var elementObservers=OpenLayers.Event.observers[cacheID];OpenLayers.Event._removeElementObservers.apply(this,[elementObservers]);}
OpenLayers.Event.observers=false;}},CLASS_NAME:"OpenLayers.Event"};OpenLayers.Event.observe(window,'unload',OpenLayers.Event.unloadCache,false);if(window.Event){OpenLayers.Util.applyDefaults(window.Event,OpenLayers.Event);}else{var Event=OpenLayers.Event;}
OpenLayers.Events=OpenLayers.Class({BROWSER_EVENTS:["mouseover","mouseout","mousedown","mouseup","mousemove","click","dblclick","rightclick","dblrightclick","resize","focus","blur"],listeners:null,object:null,element:null,eventTypes:null,eventHandler:null,fallThrough:null,includeXY:false,clearMouseListener:null,initialize:function(object,element,eventTypes,fallThrough,options){OpenLayers.Util.extend(this,options);this.object=object;this.fallThrough=fallThrough;this.listeners={};this.eventHandler=OpenLayers.Function.bindAsEventListener(this.handleBrowserEvent,this);this.clearMouseListener=OpenLayers.Function.bind(this.clearMouseCache,this);this.eventTypes=[];if(eventTypes!=null){for(var i=0,len=eventTypes.length;i<len;i++){this.addEventType(eventTypes[i]);}}
if(element!=null){this.attachToElement(element);}},destroy:function(){if(this.element){OpenLayers.Event.stopObservingElement(this.element);if(this.element.hasScrollEvent){OpenLayers.Event.stopObserving(window,"scroll",this.clearMouseListener);}}
this.element=null;this.listeners=null;this.object=null;this.eventTypes=null;this.fallThrough=null;this.eventHandler=null;},addEventType:function(eventName){if(!this.listeners[eventName]){this.eventTypes.push(eventName);this.listeners[eventName]=[];}},attachToElement:function(element){if(this.element){OpenLayers.Event.stopObservingElement(this.element);}
this.element=element;for(var i=0,len=this.BROWSER_EVENTS.length;i<len;i++){var eventType=this.BROWSER_EVENTS[i];this.addEventType(eventType);OpenLayers.Event.observe(element,eventType,this.eventHandler);}
OpenLayers.Event.observe(element,"dragstart",OpenLayers.Event.stop);},on:function(object){for(var type in object){if(type!="scope"){this.register(type,object.scope,object[type]);}}},register:function(type,obj,func){if((func!=null)&&(OpenLayers.Util.indexOf(this.eventTypes,type)!=-1)){if(obj==null){obj=this.object;}
var listeners=this.listeners[type];listeners.push({obj:obj,func:func});}},registerPriority:function(type,obj,func){if(func!=null){if(obj==null){obj=this.object;}
var listeners=this.listeners[type];if(listeners!=null){listeners.unshift({obj:obj,func:func});}}},un:function(object){for(var type in object){if(type!="scope"){this.unregister(type,object.scope,object[type]);}}},unregister:function(type,obj,func){if(obj==null){obj=this.object;}
var listeners=this.listeners[type];if(listeners!=null){for(var i=0,len=listeners.length;i<len;i++){if(listeners[i].obj==obj&&listeners[i].func==func){listeners.splice(i,1);break;}}}},remove:function(type){if(this.listeners[type]!=null){this.listeners[type]=[];}},triggerEvent:function(type,evt){var listeners=this.listeners[type];if(!listeners||listeners.length==0){return;}
if(evt==null){evt={};}
evt.object=this.object;evt.element=this.element;if(!evt.type){evt.type=type;}
var listeners=listeners.slice(),continueChain;for(var i=0,len=listeners.length;i<len;i++){var callback=listeners[i];continueChain=callback.func.apply(callback.obj,[evt]);if((continueChain!=undefined)&&(continueChain==false)){break;}}
if(!this.fallThrough){OpenLayers.Event.stop(evt,true);}
return continueChain;},handleBrowserEvent:function(evt){if(this.includeXY){evt.xy=this.getMousePosition(evt);}
this.triggerEvent(evt.type,evt);},clearMouseCache:function(){this.element.scrolls=null;this.element.lefttop=null;this.element.offsets=null;},getMousePosition:function(evt){if(!this.includeXY){this.clearMouseCache();}else if(!this.element.hasScrollEvent){OpenLayers.Event.observe(window,"scroll",this.clearMouseListener);this.element.hasScrollEvent=true;}
if(!this.element.scrolls){this.element.scrolls=[(document.documentElement.scrollLeft||document.body.scrollLeft),(document.documentElement.scrollTop||document.body.scrollTop)];}
if(!this.element.lefttop){this.element.lefttop=[(document.documentElement.clientLeft||0),(document.documentElement.clientTop||0)];}
if(!this.element.offsets){this.element.offsets=OpenLayers.Util.pagePosition(this.element);this.element.offsets[0]+=this.element.scrolls[0];this.element.offsets[1]+=this.element.scrolls[1];}
return new OpenLayers.Pixel((evt.clientX+this.element.scrolls[0])-this.element.offsets[0]
-this.element.lefttop[0],(evt.clientY+this.element.scrolls[1])-this.element.offsets[1]
-this.element.lefttop[1]);},CLASS_NAME:"OpenLayers.Events"});OpenLayers.Format=OpenLayers.Class({options:null,externalProjection:null,internalProjection:null,data:null,keepData:false,initialize:function(options){OpenLayers.Util.extend(this,options);this.options=options;},destroy:function(){},read:function(data){OpenLayers.Console.userError(OpenLayers.i18n("readNotImplemented"));},write:function(object){OpenLayers.Console.userError(OpenLayers.i18n("writeNotImplemented"));},CLASS_NAME:"OpenLayers.Format"});OpenLayers.Lang.en={'unhandledRequest':"Unhandled request return ${statusText}",'permalink':"Permalink",'overlays':"Overlays",'baseLayer':"Base Layer",'sameProjection':"The overview map only works when it is in the same projection as the main map",'readNotImplemented':"Read not implemented.",'writeNotImplemented':"Write not implemented.",'noFID':"Can't update a feature for which there is no FID.",'errorLoadingGML':"Error in loading GML file ${url}",'browserNotSupported':"Your browser does not support vector rendering. Currently supported renderers are:\n${renderers}",'componentShouldBe':"addFeatures : component should be an ${geomType}",'getFeatureError':"getFeatureFromEvent called on layer with no renderer. This usually means you "+"destroyed a layer, but not some handler which is associated with it.",'minZoomLevelError':"The minZoomLevel property is only intended for use "+"with the FixedZoomLevels-descendent layers. That this "+"wfs layer checks for minZoomLevel is a relic of the"+"past. We cannot, however, remove it without possibly "+"breaking OL based applications that may depend on it."+" Therefore we are deprecating it -- the minZoomLevel "+"check below will be removed at 3.0. Please instead "+"use min/max resolution setting as described here: "+"http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"WFS Transaction: SUCCESS ${response}",'commitFailed':"WFS Transaction: FAILED ${response}",'googleWarning':"The Google Layer was unable to load correctly.<br><br>"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.<br><br>"+"Most likely, this is because the Google Maps library "+"script was either not included, or does not contain the "+"correct API key for your site.<br><br>"+"Developers: For help getting this working correctly, "+"<a href='http://trac.openlayers.org/wiki/Google' "+"target='_blank'>click here</a>",'getLayerWarning':"The ${layerType} Layer was unable to load correctly.<br><br>"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.<br><br>"+"Most likely, this is because the ${layerLib} library "+"script was not correctly included.<br><br>"+"Developers: For help getting this working correctly, "+"<a href='http://trac.openlayers.org/wiki/${layerLib}' "+"target='_blank'>click here</a>",'scale':"Scale = 1 : ${scaleDenom}",'layerAlreadyAdded':"You tried to add the layer: ${layerName} to the map, but it has already been added",'reprojectDeprecated':"You are using the 'reproject' option "+"on the ${layerName} layer. This option is deprecated: "+"its use was designed to support displaying data over commercial "+"basemaps, but that functionality should now be achieved by using "+"Spherical Mercator support. More information is available from "+"http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"This method has been deprecated and will be removed in 3.0. "+"Please use ${newMethod} instead.",'boundsAddError':"You must pass both x and y values to the add function.",'lonlatAddError':"You must pass both lon and lat values to the add function.",'pixelAddError':"You must pass both x and y values to the add function.",'unsupportedGeometryType':"Unsupported geometry type: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition failed: element with id ${elemId} may be misplaced.",'end':'','filterEvaluateNotImplemented':"evaluate is not implemented for this filter type."};OpenLayers.Popup.AnchoredBubble=OpenLayers.Class(OpenLayers.Popup.Anchored,{rounded:false,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){this.padding=new OpenLayers.Bounds(0,OpenLayers.Popup.AnchoredBubble.CORNER_SIZE,0,OpenLayers.Popup.AnchoredBubble.CORNER_SIZE);OpenLayers.Popup.Anchored.prototype.initialize.apply(this,arguments);},draw:function(px){OpenLayers.Popup.Anchored.prototype.draw.apply(this,arguments);this.setContentHTML();this.setBackgroundColor();this.setOpacity();return this.div;},updateRelativePosition:function(){this.setRicoCorners();},setSize:function(contentSize){OpenLayers.Popup.Anchored.prototype.setSize.apply(this,arguments);this.setRicoCorners();},setBackgroundColor:function(color){if(color!=undefined){this.backgroundColor=color;}
if(this.div!=null){if(this.contentDiv!=null){this.div.style.background="transparent";OpenLayers.Rico.Corner.changeColor(this.groupDiv,this.backgroundColor);}}},setOpacity:function(opacity){OpenLayers.Popup.Anchored.prototype.setOpacity.call(this,opacity);if(this.div!=null){if(this.groupDiv!=null){OpenLayers.Rico.Corner.changeOpacity(this.groupDiv,this.opacity);}}},setBorder:function(border){this.border=0;},setRicoCorners:function(){var corners=this.getCornersToRound(this.relativePosition);var options={corners:corners,color:this.backgroundColor,bgColor:"transparent",blend:false};if(!this.rounded){OpenLayers.Rico.Corner.round(this.div,options);this.rounded=true;}else{OpenLayers.Rico.Corner.reRound(this.groupDiv,options);this.setBackgroundColor();this.setOpacity();}},getCornersToRound:function(){var corners=['tl','tr','bl','br'];var corner=OpenLayers.Bounds.oppositeQuadrant(this.relativePosition);OpenLayers.Util.removeItem(corners,corner);return corners.join(" ");},CLASS_NAME:"OpenLayers.Popup.AnchoredBubble"});OpenLayers.Popup.AnchoredBubble.CORNER_SIZE=5;OpenLayers.Popup.Framed=OpenLayers.Class(OpenLayers.Popup.Anchored,{imageSrc:null,imageSize:null,isAlphaImage:false,positionBlocks:null,blocks:null,fixedRelativePosition:false,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){OpenLayers.Popup.Anchored.prototype.initialize.apply(this,arguments);if(this.fixedRelativePosition){this.updateRelativePosition();this.calculateRelativePosition=function(px){return this.relativePosition;};}
this.contentDiv.style.position="absolute";this.contentDiv.style.zIndex=1;if(closeBox){this.closeDiv.style.zIndex=1;}
this.groupDiv.style.position="absolute";this.groupDiv.style.top="0px";this.groupDiv.style.left="0px";this.groupDiv.style.height="100%";this.groupDiv.style.width="100%";},destroy:function(){this.imageSrc=null;this.imageSize=null;this.isAlphaImage=null;this.fixedRelativePosition=false;this.positionBlocks=null;for(var i=0;i<this.blocks.length;i++){var block=this.blocks[i];if(block.image){block.div.removeChild(block.image);}
block.image=null;if(block.div){this.groupDiv.removeChild(block.div);}
block.div=null;}
this.blocks=null;OpenLayers.Popup.Anchored.prototype.destroy.apply(this,arguments);},setBackgroundColor:function(color){},setBorder:function(){},setOpacity:function(opacity){},setSize:function(contentSize){OpenLayers.Popup.Anchored.prototype.setSize.apply(this,arguments);this.updateBlocks();},updateRelativePosition:function(){this.padding=this.positionBlocks[this.relativePosition].padding;if(this.closeDiv){var contentDivPadding=this.getContentDivPadding();this.closeDiv.style.right=contentDivPadding.right+
this.padding.right+"px";this.closeDiv.style.top=contentDivPadding.top+
this.padding.top+"px";}
this.updateBlocks();},calculateNewPx:function(px){var newPx=OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(this,arguments);newPx=newPx.offset(this.positionBlocks[this.relativePosition].offset);return newPx;},createBlocks:function(){this.blocks=[];var firstPosition=null;for(var key in this.positionBlocks){firstPosition=key;break;}
var position=this.positionBlocks[firstPosition];for(var i=0;i<position.blocks.length;i++){var block={};this.blocks.push(block);var divId=this.id+'_FrameDecorationDiv_'+i;block.div=OpenLayers.Util.createDiv(divId,null,null,null,"absolute",null,"hidden",null);var imgId=this.id+'_FrameDecorationImg_'+i;var imageCreator=(this.isAlphaImage)?OpenLayers.Util.createAlphaImageDiv:OpenLayers.Util.createImage;block.image=imageCreator(imgId,null,this.imageSize,this.imageSrc,"absolute",null,null,null);block.div.appendChild(block.image);this.groupDiv.appendChild(block.div);}},updateBlocks:function(){if(!this.blocks){this.createBlocks();}
if(this.size&&this.relativePosition){var position=this.positionBlocks[this.relativePosition];for(var i=0;i<position.blocks.length;i++){var positionBlock=position.blocks[i];var block=this.blocks[i];var l=positionBlock.anchor.left;var b=positionBlock.anchor.bottom;var r=positionBlock.anchor.right;var t=positionBlock.anchor.top;var w=(isNaN(positionBlock.size.w))?this.size.w-(r+l):positionBlock.size.w;var h=(isNaN(positionBlock.size.h))?this.size.h-(b+t):positionBlock.size.h;block.div.style.width=(w<0?0:w)+'px';block.div.style.height=(h<0?0:h)+'px';block.div.style.left=(l!=null)?l+'px':'';block.div.style.bottom=(b!=null)?b+'px':'';block.div.style.right=(r!=null)?r+'px':'';block.div.style.top=(t!=null)?t+'px':'';block.image.style.left=positionBlock.position.x+'px';block.image.style.top=positionBlock.position.y+'px';}
this.contentDiv.style.left=this.padding.left+"px";this.contentDiv.style.top=this.padding.top+"px";}},CLASS_NAME:"OpenLayers.Popup.Framed"});OpenLayers.Projection=OpenLayers.Class({proj:null,projCode:null,initialize:function(projCode,options){OpenLayers.Util.extend(this,options);this.projCode=projCode;if(window.Proj4js){this.proj=new Proj4js.Proj(projCode);}},getCode:function(){return this.proj?this.proj.srsCode:this.projCode;},getUnits:function(){return this.proj?this.proj.units:null;},toString:function(){return this.getCode();},equals:function(projection){if(projection&&projection.getCode){return this.getCode()==projection.getCode();}else{return false;}},destroy:function(){delete this.proj;delete this.projCode;},CLASS_NAME:"OpenLayers.Projection"});OpenLayers.Projection.transforms={};OpenLayers.Projection.addTransform=function(from,to,method){if(!OpenLayers.Projection.transforms[from]){OpenLayers.Projection.transforms[from]={};}
OpenLayers.Projection.transforms[from][to]=method;};OpenLayers.Projection.transform=function(point,source,dest){if(source.proj&&dest.proj){point=Proj4js.transform(source.proj,dest.proj,point);}else if(source&&dest&&OpenLayers.Projection.transforms[source.getCode()]&&OpenLayers.Projection.transforms[source.getCode()][dest.getCode()]){OpenLayers.Projection.transforms[source.getCode()][dest.getCode()](point);}
return point;};OpenLayers.Protocol.WFS.v1=OpenLayers.Class(OpenLayers.Protocol,{version:null,srsName:"EPSG:4326",featureType:null,featureNS:null,geometryName:"the_geom",schema:null,featurePrefix:"feature",formatOptions:null,initialize:function(options){OpenLayers.Protocol.prototype.initialize.apply(this,[options]);if(!options.format){this.format=OpenLayers.Format.WFST(OpenLayers.Util.extend({version:this.version,featureType:this.featureType,featureNS:this.featureNS,featurePrefix:this.featurePrefix,geometryName:this.geometryName,srsName:this.srsName,schema:this.schema},this.formatOptions));}
if(!this.featureNS){var readNode=this.format.readNode;this.format.readNode=function(node,obj){if(!this.featureNS&&node.prefix==this.featurePrefix){this.featureNS=node.namespaceURI;this.setNamespace("feature",this.featureNS);}
return readNode.apply(this,arguments);}}},destroy:function(){if(this.options&&!this.options.format){this.format.destroy();}
this.format=null;OpenLayers.Protocol.prototype.destroy.apply(this);},createCallback:function(method,response,options){return OpenLayers.Function.bind(function(){method.apply(this,[response,options]);},this);},read:function(options){options=OpenLayers.Util.extend({},options);OpenLayers.Util.applyDefaults(options,this.options||{});var response=new OpenLayers.Protocol.Response({requestType:"read"});var data=OpenLayers.Format.XML.prototype.write.apply(this.format,[this.format.writeNode("wfs:GetFeature",options)]);response.priv=OpenLayers.Request.POST({url:options.url,callback:this.createCallback(this.handleRead,response,options),params:options.params,headers:options.headers,data:data});return response;},handleRead:function(response,options){if(options.callback){var request=response.priv;if(request.status>=200&&request.status<300){response.features=this.parseFeatures(request);response.code=OpenLayers.Protocol.Response.SUCCESS;}else{response.code=OpenLayers.Protocol.Response.FAILURE;}
options.callback.call(options.scope,response);};},parseFeatures:function(request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
if(!doc||doc.length<=0){return null;}
return this.format.read(doc);},commit:function(features,options){options=OpenLayers.Util.extend({},options);OpenLayers.Util.applyDefaults(options,this.options);var response=new OpenLayers.Protocol.Response({requestType:"commit",reqFeatures:features});response.priv=OpenLayers.Request.POST({url:options.url,data:this.format.write(features,options),callback:this.createCallback(this.handleCommit,response,options)});return response;},handleCommit:function(response,options){if(options.callback){var request=response.priv;var data=request.responseXML;if(!data||!data.documentElement){data=request.responseText;}
var obj=this.format.read(data)||{};response.insertIds=obj.insertIds||[];response.code=(obj.success)?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE;options.callback.call(options.scope,response);}},filterDelete:function(filter,options){options=OpenLayers.Util.extend({},options);OpenLayers.Util.applyDefaults(options,this.options);var response=new OpenLayers.Protocol.Response({requestType:"commit"});var root=this.format.createElementNSPlus("wfs:Transaction",{attributes:{service:"WFS",version:this.version}});var deleteNode=this.format.createElementNSPlus("wfs:Delete",{attributes:{typeName:(options.featureNS?this.featurePrefix+":":"")+
options.featureType}});if(options.featureNS){deleteNode.setAttribute("xmlns:"+this.featurePrefix,options.featureNS);}
var filterNode=this.format.writeNode("ogc:Filter",filter);deleteNode.appendChild(filterNode);root.appendChild(deleteNode);var data=OpenLayers.Format.XML.prototype.write.apply(this.format,[root]);return OpenLayers.Request.POST({url:this.url,callback:options.callback||function(){},data:data});},abort:function(response){if(response){response.priv.abort();}},CLASS_NAME:"OpenLayers.Protocol.WFS.v1"});OpenLayers.Renderer.SVG=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"http://www.w3.org/2000/svg",xlinkns:"http://www.w3.org/1999/xlink",MAX_PIXEL:15000,translationParameters:null,symbolSize:{},isGecko:null,initialize:function(containerID){if(!this.supported()){return;}
OpenLayers.Renderer.Elements.prototype.initialize.apply(this,arguments);this.translationParameters={x:0,y:0};this.isGecko=(navigator.userAgent.toLowerCase().indexOf("gecko/")!=-1);},destroy:function(){OpenLayers.Renderer.Elements.prototype.destroy.apply(this,arguments);},supported:function(){var svgFeature="http://www.w3.org/TR/SVG11/feature#";return(document.implementation&&(document.implementation.hasFeature("org.w3c.svg","1.0")||document.implementation.hasFeature(svgFeature+"SVG","1.1")||document.implementation.hasFeature(svgFeature+"BasicStructure","1.1")));},inValidRange:function(x,y,xyOnly){var left=x+(xyOnly?0:this.translationParameters.x);var top=y+(xyOnly?0:this.translationParameters.y);return(left>=-this.MAX_PIXEL&&left<=this.MAX_PIXEL&&top>=-this.MAX_PIXEL&&top<=this.MAX_PIXEL);},setExtent:function(extent,resolutionChanged){OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments);var resolution=this.getResolution();var left=-extent.left/resolution;var top=extent.top/resolution;if(resolutionChanged){this.left=left;this.top=top;var extentString="0 0 "+this.size.w+" "+this.size.h;this.rendererRoot.setAttributeNS(null,"viewBox",extentString);this.translate(0,0);return true;}else{var inRange=this.translate(left-this.left,top-this.top);if(!inRange){this.setExtent(extent,true);}
return inRange;}},translate:function(x,y){if(!this.inValidRange(x,y,true)){return false;}else{var transformString="";if(x||y){transformString="translate("+x+","+y+")";}
this.root.setAttributeNS(null,"transform",transformString);this.translationParameters={x:x,y:y};return true;}},setSize:function(size){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);this.rendererRoot.setAttributeNS(null,"width",this.size.w);this.rendererRoot.setAttributeNS(null,"height",this.size.h);},getNodeType:function(geometry,style){var nodeType=null;switch(geometry.CLASS_NAME){case"OpenLayers.Geometry.Point":if(style.externalGraphic){nodeType="image";}else if(this.isComplexSymbol(style.graphicName)){nodeType="use";}else{nodeType="circle";}
break;case"OpenLayers.Geometry.Rectangle":nodeType="rect";break;case"OpenLayers.Geometry.LineString":nodeType="polyline";break;case"OpenLayers.Geometry.LinearRing":nodeType="polygon";break;case"OpenLayers.Geometry.Polygon":case"OpenLayers.Geometry.Curve":case"OpenLayers.Geometry.Surface":nodeType="path";break;default:break;}
return nodeType;},setStyle:function(node,style,options){style=style||node._style;options=options||node._options;var r=parseFloat(node.getAttributeNS(null,"r"));var widthFactor=1;var pos;if(node._geometryClass=="OpenLayers.Geometry.Point"&&r){node.style.visibility="";if(style.graphic===false){node.style.visibility="hidden";}else if(style.externalGraphic){pos=this.getPosition(node);if(style.graphicTitle){node.setAttributeNS(null,"title",style.graphicTitle);}
if(style.graphicWidth&&style.graphicHeight){node.setAttributeNS(null,"preserveAspectRatio","none");}
var width=style.graphicWidth||style.graphicHeight;var height=style.graphicHeight||style.graphicWidth;width=width?width:style.pointRadius*2;height=height?height:style.pointRadius*2;var xOffset=(style.graphicXOffset!=undefined)?style.graphicXOffset:-(0.5*width);var yOffset=(style.graphicYOffset!=undefined)?style.graphicYOffset:-(0.5*height);var opacity=style.graphicOpacity||style.fillOpacity;node.setAttributeNS(null,"x",(pos.x+xOffset).toFixed());node.setAttributeNS(null,"y",(pos.y+yOffset).toFixed());node.setAttributeNS(null,"width",width);node.setAttributeNS(null,"height",height);node.setAttributeNS(this.xlinkns,"href",style.externalGraphic);node.setAttributeNS(null,"style","opacity: "+opacity);}else if(this.isComplexSymbol(style.graphicName)){var offset=style.pointRadius*3;var size=offset*2;var id=this.importSymbol(style.graphicName);var href="#"+id;pos=this.getPosition(node);widthFactor=this.symbolSize[id]/size;var parent=node.parentNode;var nextSibling=node.nextSibling;if(parent){parent.removeChild(node);}
node.setAttributeNS(this.xlinkns,"href",href);node.setAttributeNS(null,"width",size);node.setAttributeNS(null,"height",size);node.setAttributeNS(null,"x",pos.x-offset);node.setAttributeNS(null,"y",pos.y-offset);if(nextSibling){parent.insertBefore(node,nextSibling);}else if(parent){parent.appendChild(node);}}else{node.setAttributeNS(null,"r",style.pointRadius);}
if(typeof style.rotation!="undefined"&&pos){var rotation=OpenLayers.String.format("rotate(${0} ${1} ${2})",[style.rotation,pos.x,pos.y]);node.setAttributeNS(null,"transform",rotation);}}
if(options.isFilled){node.setAttributeNS(null,"fill",style.fillColor);node.setAttributeNS(null,"fill-opacity",style.fillOpacity);}else{node.setAttributeNS(null,"fill","none");}
if(options.isStroked){node.setAttributeNS(null,"stroke",style.strokeColor);node.setAttributeNS(null,"stroke-opacity",style.strokeOpacity);node.setAttributeNS(null,"stroke-width",style.strokeWidth*widthFactor);node.setAttributeNS(null,"stroke-linecap",style.strokeLinecap);node.setAttributeNS(null,"stroke-linejoin","round");node.setAttributeNS(null,"stroke-dasharray",this.dashStyle(style,widthFactor));}else{node.setAttributeNS(null,"stroke","none");}
if(style.pointerEvents){node.setAttributeNS(null,"pointer-events",style.pointerEvents);}
if(style.cursor!=null){node.setAttributeNS(null,"cursor",style.cursor);}
return node;},dashStyle:function(style,widthFactor){var w=style.strokeWidth*widthFactor;switch(style.strokeDashstyle){case'solid':return'none';case'dot':return[1,4*w].join();case'dash':return[4*w,4*w].join();case'dashdot':return[4*w,4*w,1,4*w].join();case'longdash':return[8*w,4*w].join();case'longdashdot':return[8*w,4*w,1,4*w].join();default:return style.strokeDashstyle.replace(/ /g,",");}},createNode:function(type,id){var node=document.createElementNS(this.xmlns,type);if(id){node.setAttributeNS(null,"id",id);}
return node;},nodeTypeCompare:function(node,type){return(type==node.nodeName);},createRenderRoot:function(){return this.nodeFactory(this.container.id+"_svgRoot","svg");},createRoot:function(suffix){return this.nodeFactory(this.container.id+suffix,"g");},createDefs:function(){var defs=this.nodeFactory(this.container.id+"_defs","defs");this.rendererRoot.appendChild(defs);return defs;},drawPoint:function(node,geometry){return this.drawCircle(node,geometry,1);},drawCircle:function(node,geometry,radius){var resolution=this.getResolution();var x=(geometry.x/resolution+this.left);var y=(this.top-geometry.y/resolution);if(this.inValidRange(x,y)){node.setAttributeNS(null,"cx",x);node.setAttributeNS(null,"cy",y);node.setAttributeNS(null,"r",radius);return node;}else{return false;}},drawLineString:function(node,geometry){var componentsResult=this.getComponentsString(geometry.components);if(componentsResult.path){node.setAttributeNS(null,"points",componentsResult.path);return(componentsResult.complete?node:null);}else{return false;}},drawLinearRing:function(node,geometry){var componentsResult=this.getComponentsString(geometry.components);if(componentsResult.path){node.setAttributeNS(null,"points",componentsResult.path);return(componentsResult.complete?node:null);}else{return false;}},drawPolygon:function(node,geometry){var d="";var draw=true;var complete=true;var linearRingResult,path;for(var j=0,len=geometry.components.length;j<len;j++){d+=" M";linearRingResult=this.getComponentsString(geometry.components[j].components," ");path=linearRingResult.path;if(path){d+=" "+path;complete=linearRingResult.complete&&complete;}else{draw=false;}}
d+=" z";if(draw){node.setAttributeNS(null,"d",d);node.setAttributeNS(null,"fill-rule","evenodd");return complete?node:null;}else{return false;}},drawRectangle:function(node,geometry){var resolution=this.getResolution();var x=(geometry.x/resolution+this.left);var y=(this.top-geometry.y/resolution);if(this.inValidRange(x,y)){node.setAttributeNS(null,"x",x);node.setAttributeNS(null,"y",y);node.setAttributeNS(null,"width",geometry.width/resolution);node.setAttributeNS(null,"height",geometry.height/resolution);return node;}else{return false;}},drawSurface:function(node,geometry){var d=null;var draw=true;for(var i=0,len=geometry.components.length;i<len;i++){if((i%3)==0&&(i/3)==0){var component=this.getShortString(geometry.components[i]);if(!component){draw=false;}
d="M "+component;}else if((i%3)==1){var component=this.getShortString(geometry.components[i]);if(!component){draw=false;}
d+=" C "+component;}else{var component=this.getShortString(geometry.components[i]);if(!component){draw=false;}
d+=" "+component;}}
d+=" Z";if(draw){node.setAttributeNS(null,"d",d);return node;}else{return false;}},drawText:function(featureId,style,location){var resolution=this.getResolution();var x=(location.x/resolution+this.left);var y=(location.y/resolution-this.top);var label=this.nodeFactory(featureId+this.LABEL_ID_SUFFIX,"text");var tspan=this.nodeFactory(featureId+this.LABEL_ID_SUFFIX+"_tspan","tspan");label.setAttributeNS(null,"x",x);label.setAttributeNS(null,"y",-y);label.setAttributeNS(null,"pointer-events","none");if(style.fontColor){label.setAttributeNS(null,"fill",style.fontColor);}
if(style.fontFamily){label.setAttributeNS(null,"font-family",style.fontFamily);}
if(style.fontSize){label.setAttributeNS(null,"font-size",style.fontSize);}
if(style.fontWeight){label.setAttributeNS(null,"font-weight",style.fontWeight);}
var align=style.labelAlign||"cm";label.setAttributeNS(null,"text-anchor",OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]]||"middle");if(this.isGecko){label.setAttributeNS(null,"dominant-baseline",OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]]||"central");}else{tspan.setAttributeNS(null,"baseline-shift",OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]]||"-35%");}
tspan.textContent=style.label;if(!label.parentNode){label.appendChild(tspan);this.textRoot.appendChild(label);}},getComponentsString:function(components,separator){var renderCmp=[];var complete=true;var len=components.length;var strings=[];var str,component,j;for(var i=0;i<len;i++){component=components[i];renderCmp.push(component);str=this.getShortString(component);if(str){strings.push(str);}else{if(i>0){if(this.getShortString(components[i-1])){strings.push(this.clipLine(components[i],components[i-1]));}}
if(i<len-1){if(this.getShortString(components[i+1])){strings.push(this.clipLine(components[i],components[i+1]));}}
complete=false;}}
return{path:strings.join(separator||","),complete:complete};},clipLine:function(badComponent,goodComponent){if(goodComponent.equals(badComponent)){return"";}
var resolution=this.getResolution();var maxX=this.MAX_PIXEL-this.translationParameters.x;var maxY=this.MAX_PIXEL-this.translationParameters.y;var x1=goodComponent.x/resolution+this.left;var y1=this.top-goodComponent.y/resolution;var x2=badComponent.x/resolution+this.left;var y2=this.top-badComponent.y/resolution;var k;if(x2<-maxX||x2>maxX){k=(y2-y1)/(x2-x1);x2=x2<0?-maxX:maxX;y2=y1+(x2-x1)*k;}
if(y2<-maxY||y2>maxY){k=(x2-x1)/(y2-y1);y2=y2<0?-maxY:maxY;x2=x1+(y2-y1)*k;}
return x2+","+y2;},getShortString:function(point){var resolution=this.getResolution();var x=(point.x/resolution+this.left);var y=(this.top-point.y/resolution);if(this.inValidRange(x,y)){return x+","+y;}else{return false;}},getPosition:function(node){return({x:parseFloat(node.getAttributeNS(null,"cx")),y:parseFloat(node.getAttributeNS(null,"cy"))});},importSymbol:function(graphicName){if(!this.defs){this.defs=this.createDefs();}
var id=this.container.id+"-"+graphicName;if(document.getElementById(id)!=null){return id;}
var symbol=OpenLayers.Renderer.symbol[graphicName];if(!symbol){throw new Error(graphicName+' is not a valid symbol name');return;}
var symbolNode=this.nodeFactory(id,"symbol");var node=this.nodeFactory(null,"polygon");symbolNode.appendChild(node);var symbolExtent=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,0,0);var points="";var x,y;for(var i=0;i<symbol.length;i=i+2){x=symbol[i];y=symbol[i+1];symbolExtent.left=Math.min(symbolExtent.left,x);symbolExtent.bottom=Math.min(symbolExtent.bottom,y);symbolExtent.right=Math.max(symbolExtent.right,x);symbolExtent.top=Math.max(symbolExtent.top,y);points+=" "+x+","+y;}
node.setAttributeNS(null,"points",points);var width=symbolExtent.getWidth();var height=symbolExtent.getHeight();var viewBox=[symbolExtent.left-width,symbolExtent.bottom-height,width*3,height*3];symbolNode.setAttributeNS(null,"viewBox",viewBox.join(" "));this.symbolSize[id]=Math.max(width,height)*3;this.defs.appendChild(symbolNode);return symbolNode.id;},CLASS_NAME:"OpenLayers.Renderer.SVG"});OpenLayers.Renderer.SVG.LABEL_ALIGN={"l":"start","r":"end","b":"bottom","t":"hanging"};OpenLayers.Renderer.SVG.LABEL_VSHIFT={"t":"-70%","b":"0"};OpenLayers.Renderer.VML=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"urn:schemas-microsoft-com:vml",symbolCache:{},offset:null,initialize:function(containerID){if(!this.supported()){return;}
if(!document.namespaces.olv){document.namespaces.add("olv",this.xmlns);var style=document.createStyleSheet();var shapes=['shape','rect','oval','fill','stroke','imagedata','group','textbox'];for(var i=0,len=shapes.length;i<len;i++){style.addRule('olv\\:'+shapes[i],"behavior: url(#default#VML); "+"position: absolute; display: inline-block;");}}
OpenLayers.Renderer.Elements.prototype.initialize.apply(this,arguments);this.offset={x:0,y:0};},destroy:function(){OpenLayers.Renderer.Elements.prototype.destroy.apply(this,arguments);},supported:function(){return!!(document.namespaces);},setExtent:function(extent,resolutionChanged){OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments);var resolution=this.getResolution();var left=extent.left/resolution;var top=extent.top/resolution-this.size.h;if(resolutionChanged){this.offset={x:left,y:top};left=0;top=0;}else{left=left-this.offset.x;top=top-this.offset.y;}
var org=left+" "+top;this.root.coordorigin=org;var roots=[this.root,this.vectorRoot,this.textRoot];var root;for(var i=0,len=roots.length;i<len;++i){root=roots[i];var size=this.size.w+" "+this.size.h;root.coordsize=size;}
this.root.style.flip="y";return true;},setSize:function(size){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);var roots=[this.rendererRoot,this.root,this.vectorRoot,this.textRoot];var w=this.size.w+"px";var h=this.size.h+"px";var root;for(var i=0,len=roots.length;i<len;++i){root=roots[i];root.style.width=w;root.style.height=h;}},getNodeType:function(geometry,style){var nodeType=null;switch(geometry.CLASS_NAME){case"OpenLayers.Geometry.Point":if(style.externalGraphic){nodeType="olv:rect";}else if(this.isComplexSymbol(style.graphicName)){nodeType="olv:shape";}else{nodeType="olv:oval";}
break;case"OpenLayers.Geometry.Rectangle":nodeType="olv:rect";break;case"OpenLayers.Geometry.LineString":case"OpenLayers.Geometry.LinearRing":case"OpenLayers.Geometry.Polygon":case"OpenLayers.Geometry.Curve":case"OpenLayers.Geometry.Surface":nodeType="olv:shape";break;default:break;}
return nodeType;},setStyle:function(node,style,options,geometry){style=style||node._style;options=options||node._options;var widthFactor=1;if(node._geometryClass=="OpenLayers.Geometry.Point"){if(style.externalGraphic){if(style.graphicTitle){node.title=style.graphicTitle;}
var width=style.graphicWidth||style.graphicHeight;var height=style.graphicHeight||style.graphicWidth;width=width?width:style.pointRadius*2;height=height?height:style.pointRadius*2;var resolution=this.getResolution();var xOffset=(style.graphicXOffset!=undefined)?style.graphicXOffset:-(0.5*width);var yOffset=(style.graphicYOffset!=undefined)?style.graphicYOffset:-(0.5*height);node.style.left=((geometry.x/resolution-this.offset.x)+xOffset).toFixed();node.style.top=((geometry.y/resolution-this.offset.y)-(yOffset+height)).toFixed();node.style.width=width+"px";node.style.height=height+"px";node.style.flip="y";style.fillColor="none";options.isStroked=false;}else if(this.isComplexSymbol(style.graphicName)){var cache=this.importSymbol(style.graphicName);node.path=cache.path;node.coordorigin=cache.left+","+cache.bottom;var size=cache.size;node.coordsize=size+","+size;this.drawCircle(node,geometry,style.pointRadius);node.style.flip="y";}else{this.drawCircle(node,geometry,style.pointRadius);}}
if(options.isFilled){node.fillcolor=style.fillColor;}else{node.filled="false";}
var fills=node.getElementsByTagName("fill");var fill=(fills.length==0)?null:fills[0];if(!options.isFilled){if(fill){node.removeChild(fill);}}else{if(!fill){fill=this.createNode('olv:fill',node.id+"_fill");}
fill.opacity=style.fillOpacity;if(node._geometryClass=="OpenLayers.Geometry.Point"&&style.externalGraphic){if(style.graphicOpacity){fill.opacity=style.graphicOpacity;}
fill.src=style.externalGraphic;fill.type="frame";if(!(style.graphicWidth&&style.graphicHeight)){fill.aspect="atmost";}}
if(fill.parentNode!=node){node.appendChild(fill);}}
if(typeof style.rotation!="undefined"){if(style.externalGraphic){this.graphicRotate(node,xOffset,yOffset);fill.opacity=0;}else{node.style.rotation=style.rotation;}}
if(options.isStroked){node.strokecolor=style.strokeColor;node.strokeweight=style.strokeWidth+"px";}else{node.stroked=false;}
var strokes=node.getElementsByTagName("stroke");var stroke=(strokes.length==0)?null:strokes[0];if(!options.isStroked){if(stroke){node.removeChild(stroke);}}else{if(!stroke){stroke=this.createNode('olv:stroke',node.id+"_stroke");node.appendChild(stroke);}
stroke.opacity=style.strokeOpacity;stroke.endcap=!style.strokeLinecap||style.strokeLinecap=='butt'?'flat':style.strokeLinecap;stroke.dashstyle=this.dashStyle(style);}
if(style.cursor!="inherit"&&style.cursor!=null){node.style.cursor=style.cursor;}
return node;},graphicRotate:function(node,xOffset,yOffset){var style=style||node._style;var options=node._options;var aspectRatio,size;if(!(style.graphicWidth&&style.graphicHeight)){var img=new Image();img.onreadystatechange=OpenLayers.Function.bind(function(){if(img.readyState=="complete"||img.readyState=="interactive"){aspectRatio=img.width/img.height;size=Math.max(style.pointRadius*2,style.graphicWidth||0,style.graphicHeight||0);xOffset=xOffset*aspectRatio;style.graphicWidth=size*aspectRatio;style.graphicHeight=size;this.graphicRotate(node,xOffset,yOffset);}},this);img.src=style.externalGraphic;return;}else{size=Math.max(style.graphicWidth,style.graphicHeight);aspectRatio=style.graphicWidth/style.graphicHeight;}
var width=Math.round(style.graphicWidth||size*aspectRatio);var height=Math.round(style.graphicHeight||size);node.style.width=width+"px";node.style.height=height+"px";var image=document.getElementById(node.id+"_image");if(!image){image=this.createNode("olv:imagedata",node.id+"_image");node.appendChild(image);}
image.style.width=width+"px";image.style.height=height+"px";image.src=style.externalGraphic;image.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader("+"src='', sizingMethod='scale')";var rotation=style.rotation*Math.PI/180;var sintheta=Math.sin(rotation);var costheta=Math.cos(rotation);var filter="progid:DXImageTransform.Microsoft.Matrix(M11="+costheta+",M12="+(-sintheta)+",M21="+sintheta+",M22="+costheta+",SizingMethod='auto expand')\n";var opacity=style.graphicOpacity||style.fillOpacity;if(opacity&&opacity!=1){filter+="progid:DXImageTransform.Microsoft.BasicImage(opacity="+
opacity+")\n";}
node.style.filter=filter;var centerPoint=new OpenLayers.Geometry.Point(-xOffset,-yOffset);var imgBox=new OpenLayers.Bounds(0,0,width,height).toGeometry();imgBox.rotate(style.rotation,centerPoint);var imgBounds=imgBox.getBounds();node.style.left=Math.round(parseInt(node.style.left)+imgBounds.left)+"px";node.style.top=Math.round(parseInt(node.style.top)-imgBounds.bottom)+"px";},postDraw:function(node){var fillColor=node._style.fillColor;var strokeColor=node._style.strokeColor;if(fillColor=="none"&&node.fillcolor!=fillColor){node.fillcolor=fillColor;}
if(strokeColor=="none"&&node.strokecolor!=strokeColor){node.strokecolor=strokeColor;}},setNodeDimension:function(node,geometry){var bbox=geometry.getBounds();if(bbox){var resolution=this.getResolution();var scaledBox=new OpenLayers.Bounds((bbox.left/resolution-this.offset.x).toFixed(),(bbox.bottom/resolution-this.offset.y).toFixed(),(bbox.right/resolution-this.offset.x).toFixed(),(bbox.top/resolution-this.offset.y).toFixed());node.style.left=scaledBox.left+"px";node.style.top=scaledBox.top+"px";node.style.width=scaledBox.getWidth()+"px";node.style.height=scaledBox.getHeight()+"px";node.coordorigin=scaledBox.left+" "+scaledBox.top;node.coordsize=scaledBox.getWidth()+" "+scaledBox.getHeight();}},dashStyle:function(style){var dash=style.strokeDashstyle;switch(dash){case'solid':case'dot':case'dash':case'dashdot':case'longdash':case'longdashdot':return dash;default:var parts=dash.split(/[ ,]/);if(parts.length==2){if(1*parts[0]>=2*parts[1]){return"longdash";}
return(parts[0]==1||parts[1]==1)?"dot":"dash";}else if(parts.length==4){return(1*parts[0]>=2*parts[1])?"longdashdot":"dashdot";}
return"solid";}},createNode:function(type,id){var node=document.createElement(type);if(id){node.id=id;}
node.unselectable='on';node.onselectstart=function(){return(false);};return node;},nodeTypeCompare:function(node,type){var subType=type;var splitIndex=subType.indexOf(":");if(splitIndex!=-1){subType=subType.substr(splitIndex+1);}
var nodeName=node.nodeName;splitIndex=nodeName.indexOf(":");if(splitIndex!=-1){nodeName=nodeName.substr(splitIndex+1);}
return(subType==nodeName);},createRenderRoot:function(){return this.nodeFactory(this.container.id+"_vmlRoot","div");},createRoot:function(suffix){return this.nodeFactory(this.container.id+suffix,"olv:group");},drawPoint:function(node,geometry){return this.drawCircle(node,geometry,1);},drawCircle:function(node,geometry,radius){if(!isNaN(geometry.x)&&!isNaN(geometry.y)){var resolution=this.getResolution();node.style.left=((geometry.x/resolution-this.offset.x).toFixed()-radius)+"px";node.style.top=((geometry.y/resolution-this.offset.y).toFixed()-radius)+"px";var diameter=radius*2;node.style.width=diameter+"px";node.style.height=diameter+"px";return node;}
return false;},drawLineString:function(node,geometry){return this.drawLine(node,geometry,false);},drawLinearRing:function(node,geometry){return this.drawLine(node,geometry,true);},drawLine:function(node,geometry,closeLine){this.setNodeDimension(node,geometry);var resolution=this.getResolution();var numComponents=geometry.components.length;var parts=new Array(numComponents);var comp,x,y;for(var i=0;i<numComponents;i++){comp=geometry.components[i];x=(comp.x/resolution-this.offset.x);y=(comp.y/resolution-this.offset.y);parts[i]=" "+x.toFixed()+","+y.toFixed()+" l ";}
var end=(closeLine)?" x e":" e";node.path="m"+parts.join("")+end;return node;},drawPolygon:function(node,geometry){this.setNodeDimension(node,geometry);var resolution=this.getResolution();var path=[];var linearRing,i,j,len,ilen,comp,x,y;for(j=0,len=geometry.components.length;j<len;j++){linearRing=geometry.components[j];path.push("m");for(i=0,ilen=linearRing.components.length;i<ilen;i++){comp=linearRing.components[i];x=comp.x/resolution-this.offset.x;y=comp.y/resolution-this.offset.y;path.push(" "+x.toFixed()+","+y.toFixed());if(i==0){path.push(" l");}}
path.push(" x ");}
path.push("e");node.path=path.join("");return node;},drawRectangle:function(node,geometry){var resolution=this.getResolution();node.style.left=(geometry.x/resolution-this.offset.x)+"px";node.style.top=(geometry.y/resolution-this.offset.y)+"px";node.style.width=geometry.width/resolution+"px";node.style.height=geometry.height/resolution+"px";return node;},drawText:function(featureId,style,location){var label=this.nodeFactory(featureId+this.LABEL_ID_SUFFIX,"olv:rect");var textbox=this.nodeFactory(featureId+this.LABEL_ID_SUFFIX+"_textbox","olv:textbox");var resolution=this.getResolution();label.style.left=(location.x/resolution-this.offset.x).toFixed()+"px";label.style.top=(location.y/resolution-this.offset.y).toFixed()+"px";label.style.flip="y";textbox.innerText=style.label;if(style.fillColor){textbox.style.color=style.fontColor;}
if(style.fontFamily){textbox.style.fontFamily=style.fontFamily;}
if(style.fontSize){textbox.style.fontSize=style.fontSize;}
if(style.fontWeight){textbox.style.fontWeight=style.fontWeight;}
textbox.style.whiteSpace="nowrap";textbox.inset="1px,0px,0px,0px";if(!label.parentNode){label.appendChild(textbox);this.textRoot.appendChild(label);}
var align=style.labelAlign||"cm";var xshift=textbox.clientWidth*(OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]);var yshift=textbox.clientHeight*(OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]);label.style.left=parseInt(label.style.left)-xshift-1+"px";label.style.top=parseInt(label.style.top)+yshift+"px";},drawSurface:function(node,geometry){this.setNodeDimension(node,geometry);var resolution=this.getResolution();var path=[];var comp,x,y;for(var i=0,len=geometry.components.length;i<len;i++){comp=geometry.components[i];x=comp.x/resolution-this.offset.x;y=comp.y/resolution-this.offset.y;if((i%3)==0&&(i/3)==0){path.push("m");}else if((i%3)==1){path.push(" c");}
path.push(" "+x+","+y);}
path.push(" x e");node.path=path.join("");return node;},moveRoot:function(renderer){var layer=this.map.getLayer(renderer.container.id);if(layer instanceof OpenLayers.Layer.Vector.RootContainer){layer=this.map.getLayer(this.container.id);}
layer&&layer.renderer.clear();OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this,arguments);layer&&layer.redraw();},importSymbol:function(graphicName){var id=this.container.id+"-"+graphicName;var cache=this.symbolCache[id];if(cache){return cache;}
var symbol=OpenLayers.Renderer.symbol[graphicName];if(!symbol){throw new Error(graphicName+' is not a valid symbol name');return;}
var symbolExtent=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,0,0);var pathitems=["m"];for(var i=0;i<symbol.length;i=i+2){x=symbol[i];y=symbol[i+1];symbolExtent.left=Math.min(symbolExtent.left,x);symbolExtent.bottom=Math.min(symbolExtent.bottom,y);symbolExtent.right=Math.max(symbolExtent.right,x);symbolExtent.top=Math.max(symbolExtent.top,y);pathitems.push(x);pathitems.push(y);if(i==0){pathitems.push("l");}}
pathitems.push("x e");var path=pathitems.join(" ");var diff=(symbolExtent.getWidth()-symbolExtent.getHeight())/2;if(diff>0){symbolExtent.bottom=symbolExtent.bottom-diff;symbolExtent.top=symbolExtent.top+diff;}else{symbolExtent.left=symbolExtent.left-diff;symbolExtent.right=symbolExtent.right+diff;}
cache={path:path,size:symbolExtent.getWidth(),left:symbolExtent.left,bottom:symbolExtent.bottom};this.symbolCache[id]=cache;return cache;},CLASS_NAME:"OpenLayers.Renderer.VML"});OpenLayers.Renderer.VML.LABEL_SHIFT={"l":0,"c":.5,"r":1,"t":0,"m":.5,"b":1};OpenLayers.Tile=OpenLayers.Class({EVENT_TYPES:["loadstart","loadend","reload","unload"],events:null,id:null,layer:null,url:null,bounds:null,size:null,position:null,isLoading:false,initialize:function(layer,position,bounds,url,size){this.layer=layer;this.position=position.clone();this.bounds=bounds.clone();this.url=url;this.size=size.clone();this.id=OpenLayers.Util.createUniqueID("Tile_");this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);},unload:function(){if(this.isLoading){this.isLoading=false;this.events.triggerEvent("unload");}},destroy:function(){this.layer=null;this.bounds=null;this.size=null;this.position=null;this.events.destroy();this.events=null;},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile(this.layer,this.position,this.bounds,this.url,this.size);}
OpenLayers.Util.applyDefaults(obj,this);return obj;},draw:function(){var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));this.shouldDraw=(withinMaxExtent||this.layer.displayOutsideMaxExtent);this.clear();return this.shouldDraw;},moveTo:function(bounds,position,redraw){if(redraw==null){redraw=true;}
this.bounds=bounds.clone();this.position=position.clone();if(redraw){this.draw();}},clear:function(){},getBoundsFromBaseLayer:function(position){var msg=OpenLayers.i18n('reprojectDeprecated',{'layerName':this.layer.name});OpenLayers.Console.warn(msg);var topLeft=this.layer.map.getLonLatFromLayerPx(position);var bottomRightPx=position.clone();bottomRightPx.x+=this.size.w;bottomRightPx.y+=this.size.h;var bottomRight=this.layer.map.getLonLatFromLayerPx(bottomRightPx);if(topLeft.lon>bottomRight.lon){if(topLeft.lon<0){topLeft.lon=-180-(topLeft.lon+180);}else{bottomRight.lon=180+bottomRight.lon+180;}}
var bounds=new OpenLayers.Bounds(topLeft.lon,bottomRight.lat,bottomRight.lon,topLeft.lat);return bounds;},showTile:function(){if(this.shouldDraw){this.show();}},show:function(){},hide:function(){},CLASS_NAME:"OpenLayers.Tile"});OpenLayers.Control.MouseToolbar=OpenLayers.Class(OpenLayers.Control.MouseDefaults,{mode:null,buttons:null,direction:"vertical",buttonClicked:null,initialize:function(position,direction){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.position=new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,OpenLayers.Control.MouseToolbar.Y);if(position){this.position=position;}
if(direction){this.direction=direction;}
this.measureDivs=[];},destroy:function(){for(var btnId in this.buttons){var btn=this.buttons[btnId];btn.map=null;btn.events.destroy();}
OpenLayers.Control.MouseDefaults.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);OpenLayers.Control.MouseDefaults.prototype.draw.apply(this,arguments);this.buttons={};var sz=new OpenLayers.Size(28,28);var centered=new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,0);this._addButton("zoombox","drag-rectangle-off.png","drag-rectangle-on.png",centered,sz,"Shift->Drag to zoom to area");centered=centered.add((this.direction=="vertical"?0:sz.w),(this.direction=="vertical"?sz.h:0));this._addButton("pan","panning-hand-off.png","panning-hand-on.png",centered,sz,"Drag the map to pan.");centered=centered.add((this.direction=="vertical"?0:sz.w),(this.direction=="vertical"?sz.h:0));this.switchModeTo("pan");return this.div;},_addButton:function(id,img,activeImg,xy,sz,title){var imgLocation=OpenLayers.Util.getImagesLocation()+img;var activeImgLocation=OpenLayers.Util.getImagesLocation()+activeImg;var btn=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MouseToolbar_"+id,xy,sz,imgLocation,"absolute");this.div.appendChild(btn);btn.imgLocation=imgLocation;btn.activeImgLocation=activeImgLocation;btn.events=new OpenLayers.Events(this,btn,null,true);btn.events.on({"mousedown":this.buttonDown,"mouseup":this.buttonUp,"dblclick":OpenLayers.Event.stop,scope:this});btn.action=id;btn.title=title;btn.alt=title;btn.map=this.map;this.buttons[id]=btn;return btn;},buttonDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
this.buttonClicked=evt.element.action;OpenLayers.Event.stop(evt);},buttonUp:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
if(this.buttonClicked!=null){if(this.buttonClicked==evt.element.action){this.switchModeTo(evt.element.action);}
OpenLayers.Event.stop(evt);this.buttonClicked=null;}},defaultDblClick:function(evt){this.switchModeTo("pan");this.performedDrag=false;var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom+1);OpenLayers.Event.stop(evt);return false;},defaultMouseDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
this.mouseDragStart=evt.xy.clone();this.performedDrag=false;this.startViaKeyboard=false;if(evt.shiftKey&&this.mode!="zoombox"){this.switchModeTo("zoombox");this.startViaKeyboard=true;}else if(evt.altKey&&this.mode!="measure"){this.switchModeTo("measure");}else if(!this.mode){this.switchModeTo("pan");}
switch(this.mode){case"zoombox":this.map.div.style.cursor="crosshair";this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.mouseDragStart,null,null,"absolute","2px solid red");this.zoomBox.style.backgroundColor="white";this.zoomBox.style.filter="alpha(opacity=50)";this.zoomBox.style.opacity="0.50";this.zoomBox.style.fontSize="1px";this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);this.performedDrag=true;break;case"measure":var distance="";if(this.measureStart){var measureEnd=this.map.getLonLatFromViewPortPx(this.mouseDragStart);distance=OpenLayers.Util.distVincenty(this.measureStart,measureEnd);distance=Math.round(distance*100)/100;distance=distance+"km";this.measureStartBox=this.measureBox;}
this.measureStart=this.map.getLonLatFromViewPortPx(this.mouseDragStart);;this.measureBox=OpenLayers.Util.createDiv(null,this.mouseDragStart.add(-2-parseInt(this.map.layerContainerDiv.style.left),-2-parseInt(this.map.layerContainerDiv.style.top)),null,null,"absolute");this.measureBox.style.width="4px";this.measureBox.style.height="4px";this.measureBox.style.fontSize="1px";this.measureBox.style.backgroundColor="red";this.measureBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.layerContainerDiv.appendChild(this.measureBox);if(distance){this.measureBoxDistance=OpenLayers.Util.createDiv(null,this.mouseDragStart.add(-2-parseInt(this.map.layerContainerDiv.style.left),2-parseInt(this.map.layerContainerDiv.style.top)),null,null,"absolute");this.measureBoxDistance.innerHTML=distance;this.measureBoxDistance.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.layerContainerDiv.appendChild(this.measureBoxDistance);this.measureDivs.push(this.measureBoxDistance);}
this.measureBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.layerContainerDiv.appendChild(this.measureBox);this.measureDivs.push(this.measureBox);break;default:this.map.div.style.cursor="move";break;}
document.onselectstart=function(){return false;};OpenLayers.Event.stop(evt);},switchModeTo:function(mode){if(mode!=this.mode){if(this.mode&&this.buttons[this.mode]){OpenLayers.Util.modifyAlphaImageDiv(this.buttons[this.mode],null,null,null,this.buttons[this.mode].imgLocation);}
if(this.mode=="measure"&&mode!="measure"){for(var i=0,len=this.measureDivs.length;i<len;i++){if(this.measureDivs[i]){this.map.layerContainerDiv.removeChild(this.measureDivs[i]);}}
this.measureDivs=[];this.measureStart=null;}
this.mode=mode;if(this.buttons[mode]){OpenLayers.Util.modifyAlphaImageDiv(this.buttons[mode],null,null,null,this.buttons[mode].activeImgLocation);}
switch(this.mode){case"zoombox":this.map.div.style.cursor="crosshair";break;default:this.map.div.style.cursor="";break;}}},leaveMode:function(){this.switchModeTo("pan");},defaultMouseMove:function(evt){if(this.mouseDragStart!=null){switch(this.mode){case"zoombox":var deltaX=Math.abs(this.mouseDragStart.x-evt.xy.x);var deltaY=Math.abs(this.mouseDragStart.y-evt.xy.y);this.zoomBox.style.width=Math.max(1,deltaX)+"px";this.zoomBox.style.height=Math.max(1,deltaY)+"px";if(evt.xy.x<this.mouseDragStart.x){this.zoomBox.style.left=evt.xy.x+"px";}
if(evt.xy.y<this.mouseDragStart.y){this.zoomBox.style.top=evt.xy.y+"px";}
break;default:var deltaX=this.mouseDragStart.x-evt.xy.x;var deltaY=this.mouseDragStart.y-evt.xy.y;var size=this.map.getSize();var newXY=new OpenLayers.Pixel(size.w/2+deltaX,size.h/2+deltaY);var newCenter=this.map.getLonLatFromViewPortPx(newXY);this.map.setCenter(newCenter,null,true);this.mouseDragStart=evt.xy.clone();}
this.performedDrag=true;}},defaultMouseUp:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
switch(this.mode){case"zoombox":this.zoomBoxEnd(evt);if(this.startViaKeyboard){this.leaveMode();}
break;case"pan":if(this.performedDrag){this.map.setCenter(this.map.center);}}
document.onselectstart=null;this.mouseDragStart=null;this.map.div.style.cursor="default";},defaultMouseOut:function(evt){if(this.mouseDragStart!=null&&OpenLayers.Util.mouseLeft(evt,this.map.div)){if(this.zoomBox){this.removeZoomBox();if(this.startViaKeyboard){this.leaveMode();}}
this.mouseDragStart=null;this.map.div.style.cursor="default";}},defaultClick:function(evt){if(this.performedDrag){this.performedDrag=false;return false;}},CLASS_NAME:"OpenLayers.Control.MouseToolbar"});OpenLayers.Control.MouseToolbar.X=6;OpenLayers.Control.MouseToolbar.Y=300;OpenLayers.Control.NavigationHistory=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOGGLE,previous:null,previousOptions:null,next:null,nextOptions:null,limit:50,activateOnDraw:true,clearOnDeactivate:false,registry:null,nextStack:null,previousStack:null,listeners:null,restoring:false,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.registry=OpenLayers.Util.extend({"moveend":function(){return{center:this.map.getCenter(),resolution:this.map.getResolution()};}},this.registry);this.clear();var previousOptions={trigger:OpenLayers.Function.bind(this.previousTrigger,this),displayClass:this.displayClass+" "+this.displayClass+"Previous"};OpenLayers.Util.extend(previousOptions,this.previousOptions);this.previous=new OpenLayers.Control.Button(previousOptions);var nextOptions={trigger:OpenLayers.Function.bind(this.nextTrigger,this),displayClass:this.displayClass+" "+this.displayClass+"Next"};OpenLayers.Util.extend(nextOptions,this.nextOptions);this.next=new OpenLayers.Control.Button(nextOptions);},onPreviousChange:function(state,length){if(state&&!this.previous.active){this.previous.activate();}else if(!state&&this.previous.active){this.previous.deactivate();}},onNextChange:function(state,length){if(state&&!this.next.active){this.next.activate();}else if(!state&&this.next.active){this.next.deactivate();}},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this);this.previous.destroy();this.next.destroy();this.deactivate();for(var prop in this){this[prop]=null;}},setMap:function(map){this.map=map;this.next.setMap(map);this.previous.setMap(map);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.next.draw();this.previous.draw();if(this.activateOnDraw){this.activate();}},previousTrigger:function(){var current=this.previousStack.shift();var state=this.previousStack.shift();if(state!=undefined){this.nextStack.unshift(current);this.previousStack.unshift(state);this.restoring=true;this.restore(state);this.restoring=false;this.onNextChange(this.nextStack[0],this.nextStack.length);this.onPreviousChange(this.previousStack[1],this.previousStack.length-1);}else{this.previousStack.unshift(current);}
return state;},nextTrigger:function(){var state=this.nextStack.shift();if(state!=undefined){this.previousStack.unshift(state);this.restoring=true;this.restore(state);this.restoring=false;this.onNextChange(this.nextStack[0],this.nextStack.length);this.onPreviousChange(this.previousStack[1],this.previousStack.length-1);}
return state;},clear:function(){this.previousStack=[];this.nextStack=[];},restore:function(state){var zoom=this.map.getZoomForResolution(state.resolution);this.map.setCenter(state.center,zoom);},setListeners:function(){this.listeners={};for(var type in this.registry){this.listeners[type]=OpenLayers.Function.bind(function(){if(!this.restoring){var state=this.registry[type].apply(this,arguments);this.previousStack.unshift(state);if(this.previousStack.length>1){this.onPreviousChange(this.previousStack[1],this.previousStack.length-1);}
if(this.previousStack.length>(this.limit+1)){this.previousStack.pop();}
if(this.nextStack.length>0){this.nextStack=[];this.onNextChange(null,0);}}
return true;},this);}},activate:function(){var activated=false;if(this.map){if(OpenLayers.Control.prototype.activate.apply(this)){if(this.listeners==null){this.setListeners();}
for(var type in this.listeners){this.map.events.register(type,this,this.listeners[type]);}
activated=true;if(this.previousStack.length==0){this.initStack();}}}
return activated;},initStack:function(){if(this.map.getCenter()){this.listeners.moveend();}},deactivate:function(){var deactivated=false;if(this.map){if(OpenLayers.Control.prototype.deactivate.apply(this)){for(var type in this.listeners){this.map.events.unregister(type,this,this.listeners[type]);}
if(this.clearOnDeactivate){this.clear();}
deactivated=true;}}
return deactivated;},CLASS_NAME:"OpenLayers.Control.NavigationHistory"});OpenLayers.Control.PanPanel=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(options){OpenLayers.Control.Panel.prototype.initialize.apply(this,[options]);this.addControls([new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST)]);},CLASS_NAME:"OpenLayers.Control.PanPanel"});OpenLayers.Control.PanZoomBar=OpenLayers.Class(OpenLayers.Control.PanZoom,{zoomStopWidth:18,zoomStopHeight:11,slider:null,sliderEvents:null,zoomBarDiv:null,divEvents:null,zoomWorldIcon:false,initialize:function(){OpenLayers.Control.PanZoom.prototype.initialize.apply(this,arguments);},destroy:function(){this._removeZoomBar();this.map.events.un({"changebaselayer":this.redraw,scope:this});OpenLayers.Control.PanZoom.prototype.destroy.apply(this,arguments);},setMap:function(map){OpenLayers.Control.PanZoom.prototype.setMap.apply(this,arguments);this.map.events.register("changebaselayer",this,this.redraw);},redraw:function(){if(this.div!=null){this.removeButtons();this._removeZoomBar();}
this.draw();},draw:function(px){OpenLayers.Control.prototype.draw.apply(this,arguments);px=this.position.clone();this.buttons=[];var sz=new OpenLayers.Size(18,18);var centered=new OpenLayers.Pixel(px.x+sz.w/2,px.y);var wposition=sz.w;if(this.zoomWorldIcon){centered=new OpenLayers.Pixel(px.x+sz.w,px.y);}
this._addButton("panup","north-mini.png",centered,sz);px.y=centered.y+sz.h;this._addButton("panleft","west-mini.png",px,sz);if(this.zoomWorldIcon){this._addButton("zoomworld","zoom-world-mini.png",px.add(sz.w,0),sz);wposition*=2;}
this._addButton("panright","east-mini.png",px.add(wposition,0),sz);this._addButton("pandown","south-mini.png",centered.add(0,sz.h*2),sz);this._addButton("zoomin","zoom-plus-mini.png",centered.add(0,sz.h*3+5),sz);centered=this._addZoomBar(centered.add(0,sz.h*4+5));this._addButton("zoomout","zoom-minus-mini.png",centered,sz);return this.div;},_addZoomBar:function(centered){var imgLocation=OpenLayers.Util.getImagesLocation();var id=this.id+"_"+this.map.id;var zoomsToEnd=this.map.getNumZoomLevels()-1-this.map.getZoom();var slider=OpenLayers.Util.createAlphaImageDiv(id,centered.add(-1,zoomsToEnd*this.zoomStopHeight),new OpenLayers.Size(20,9),imgLocation+"slider.png","absolute");this.slider=slider;this.sliderEvents=new OpenLayers.Events(this,slider,null,true,{includeXY:true});this.sliderEvents.on({"mousedown":this.zoomBarDown,"mousemove":this.zoomBarDrag,"mouseup":this.zoomBarUp,"dblclick":this.doubleClick,"click":this.doubleClick});var sz=new OpenLayers.Size();sz.h=this.zoomStopHeight*this.map.getNumZoomLevels();sz.w=this.zoomStopWidth;var div=null;if(OpenLayers.Util.alphaHack()){var id=this.id+"_"+this.map.id;div=OpenLayers.Util.createAlphaImageDiv(id,centered,new OpenLayers.Size(sz.w,this.zoomStopHeight),imgLocation+"zoombar.png","absolute",null,"crop");div.style.height=sz.h+"px";}else{div=OpenLayers.Util.createDiv('OpenLayers_Control_PanZoomBar_Zoombar'+this.map.id,centered,sz,imgLocation+"zoombar.png");}
this.zoombarDiv=div;this.divEvents=new OpenLayers.Events(this,div,null,true,{includeXY:true});this.divEvents.on({"mousedown":this.divClick,"mousemove":this.passEventToSlider,"dblclick":this.doubleClick,"click":this.doubleClick});this.div.appendChild(div);this.startTop=parseInt(div.style.top);this.div.appendChild(slider);this.map.events.register("zoomend",this,this.moveZoomBar);centered=centered.add(0,this.zoomStopHeight*this.map.getNumZoomLevels());return centered;},_removeZoomBar:function(){this.sliderEvents.un({"mousedown":this.zoomBarDown,"mousemove":this.zoomBarDrag,"mouseup":this.zoomBarUp,"dblclick":this.doubleClick,"click":this.doubleClick});this.sliderEvents.destroy();this.divEvents.un({"mousedown":this.divClick,"mousemove":this.passEventToSlider,"dblclick":this.doubleClick,"click":this.doubleClick});this.divEvents.destroy();this.div.removeChild(this.zoombarDiv);this.zoombarDiv=null;this.div.removeChild(this.slider);this.slider=null;this.map.events.unregister("zoomend",this,this.moveZoomBar);},passEventToSlider:function(evt){this.sliderEvents.handleBrowserEvent(evt);},divClick:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
var y=evt.xy.y;var top=OpenLayers.Util.pagePosition(evt.object)[1];var levels=(y-top)/this.zoomStopHeight;if(!this.map.fractionalZoom){levels=Math.floor(levels);}
var zoom=(this.map.getNumZoomLevels()-1)-levels;zoom=Math.min(Math.max(zoom,0),this.map.getNumZoomLevels()-1);this.map.zoomTo(zoom);OpenLayers.Event.stop(evt);},zoomBarDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
this.map.events.on({"mousemove":this.passEventToSlider,"mouseup":this.passEventToSlider,scope:this});this.mouseDragStart=evt.xy.clone();this.zoomStart=evt.xy.clone();this.div.style.cursor="move";this.zoombarDiv.offsets=null;OpenLayers.Event.stop(evt);},zoomBarDrag:function(evt){if(this.mouseDragStart!=null){var deltaY=this.mouseDragStart.y-evt.xy.y;var offsets=OpenLayers.Util.pagePosition(this.zoombarDiv);if((evt.clientY-offsets[1])>0&&(evt.clientY-offsets[1])<parseInt(this.zoombarDiv.style.height)-2){var newTop=parseInt(this.slider.style.top)-deltaY;this.slider.style.top=newTop+"px";this.mouseDragStart=evt.xy.clone();}
OpenLayers.Event.stop(evt);}},zoomBarUp:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
if(this.zoomStart){this.div.style.cursor="";this.map.events.un({"mouseup":this.passEventToSlider,"mousemove":this.passEventToSlider,scope:this});var deltaY=this.zoomStart.y-evt.xy.y;var zoomLevel=this.map.zoom;if(this.map.fractionalZoom){zoomLevel+=deltaY/this.zoomStopHeight;zoomLevel=Math.min(Math.max(zoomLevel,0),this.map.getNumZoomLevels()-1);}else{zoomLevel+=Math.round(deltaY/this.zoomStopHeight);}
this.map.zoomTo(zoomLevel);this.moveZoomBar();this.mouseDragStart=null;OpenLayers.Event.stop(evt);}},moveZoomBar:function(){var newTop=((this.map.getNumZoomLevels()-1)-this.map.getZoom())*this.zoomStopHeight+this.startTop+1;this.slider.style.top=newTop+"px";},CLASS_NAME:"OpenLayers.Control.PanZoomBar"});OpenLayers.Control.Permalink=OpenLayers.Class(OpenLayers.Control,{argParserClass:OpenLayers.Control.ArgParser,element:null,base:'',displayProjection:null,initialize:function(element,base,options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.element=OpenLayers.Util.getElement(element);this.base=base||document.location.href;},destroy:function(){if(this.element.parentNode==this.div){this.div.removeChild(this.element);}
this.element=null;this.map.events.unregister('moveend',this,this.updateLink);OpenLayers.Control.prototype.destroy.apply(this,arguments);},setMap:function(map){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var i=0,len=this.map.controls.length;i<len;i++){var control=this.map.controls[i];if(control.CLASS_NAME==this.argParserClass.CLASS_NAME){if(control.displayProjection!=this.displayProjection){this.displayProjection=control.displayProjection;}
break;}}
if(i==this.map.controls.length){this.map.addControl(new this.argParserClass({'displayProjection':this.displayProjection}));}},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.element){this.div.className=this.displayClass;this.element=document.createElement("a");this.element.innerHTML=OpenLayers.i18n("permalink");this.element.href="";this.div.appendChild(this.element);}
this.map.events.on({'moveend':this.updateLink,'changelayer':this.updateLink,'changebaselayer':this.updateLink,scope:this});this.updateLink();return this.div;},updateLink:function(){var href=this.base;if(href.indexOf('?')!=-1){href=href.substring(0,href.indexOf('?'));}
href+='?'+OpenLayers.Util.getParameterString(this.createParams());this.element.href=href;},createParams:function(center,zoom,layers){center=center||this.map.getCenter();var params=OpenLayers.Util.getParameters(this.base);if(center){params.zoom=zoom||this.map.getZoom();var lat=center.lat;var lon=center.lon;if(this.displayProjection){var mapPosition=OpenLayers.Projection.transform({x:lon,y:lat},this.map.getProjectionObject(),this.displayProjection);lon=mapPosition.x;lat=mapPosition.y;}
params.lat=Math.round(lat*100000)/100000;params.lon=Math.round(lon*100000)/100000;layers=layers||this.map.layers;params.layers='';for(var i=0,len=layers.length;i<len;i++){var layer=layers[i];if(layer.isBaseLayer){params.layers+=(layer==this.map.baseLayer)?"B":"0";}else{params.layers+=(layer.getVisibility())?"T":"F";}}}
return params;},CLASS_NAME:"OpenLayers.Control.Permalink"});OpenLayers.Control.ZoomPanel=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(options){OpenLayers.Control.Panel.prototype.initialize.apply(this,[options]);this.addControls([new OpenLayers.Control.ZoomIn(),new OpenLayers.Control.ZoomToMaxExtent(),new OpenLayers.Control.ZoomOut()]);},CLASS_NAME:"OpenLayers.Control.ZoomPanel"});OpenLayers.Format.JSON=OpenLayers.Class(OpenLayers.Format,{indent:"    ",space:" ",newline:"\n",level:0,pretty:false,initialize:function(options){OpenLayers.Format.prototype.initialize.apply(this,[options]);},read:function(json,filter){try{if(/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){var object=eval('('+json+')');if(typeof filter==='function'){function walk(k,v){if(v&&typeof v==='object'){for(var i in v){if(v.hasOwnProperty(i)){v[i]=walk(i,v[i]);}}}
return filter(k,v);}
object=walk('',object);}
if(this.keepData){this.data=object;}
return object;}}catch(e){}
return null;},write:function(value,pretty){this.pretty=!!pretty;var json=null;var type=typeof value;if(this.serialize[type]){try{json=this.serialize[type].apply(this,[value]);}catch(err){OpenLayers.Console.error("Trouble serializing: "+err);}}
return json;},writeIndent:function(){var pieces=[];if(this.pretty){for(var i=0;i<this.level;++i){pieces.push(this.indent);}}
return pieces.join('');},writeNewline:function(){return(this.pretty)?this.newline:'';},writeSpace:function(){return(this.pretty)?this.space:'';},serialize:{'object':function(object){if(object==null){return"null";}
if(object.constructor==Date){return this.serialize.date.apply(this,[object]);}
if(object.constructor==Array){return this.serialize.array.apply(this,[object]);}
var pieces=['{'];this.level+=1;var key,keyJSON,valueJSON;var addComma=false;for(key in object){if(object.hasOwnProperty(key)){keyJSON=OpenLayers.Format.JSON.prototype.write.apply(this,[key,this.pretty]);valueJSON=OpenLayers.Format.JSON.prototype.write.apply(this,[object[key],this.pretty]);if(keyJSON!=null&&valueJSON!=null){if(addComma){pieces.push(',');}
pieces.push(this.writeNewline(),this.writeIndent(),keyJSON,':',this.writeSpace(),valueJSON);addComma=true;}}}
this.level-=1;pieces.push(this.writeNewline(),this.writeIndent(),'}');return pieces.join('');},'array':function(array){var json;var pieces=['['];this.level+=1;for(var i=0,len=array.length;i<len;++i){json=OpenLayers.Format.JSON.prototype.write.apply(this,[array[i],this.pretty]);if(json!=null){if(i>0){pieces.push(',');}
pieces.push(this.writeNewline(),this.writeIndent(),json);}}
this.level-=1;pieces.push(this.writeNewline(),this.writeIndent(),']');return pieces.join('');},'string':function(string){var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};if(/["\\\x00-\x1f]/.test(string)){return'"'+string.replace(/([\x00-\x1f\\"])/g,function(a,b){var c=m[b];if(c){return c;}
c=b.charCodeAt();return'\\u00'+
Math.floor(c/16).toString(16)+
(c%16).toString(16);})+'"';}
return'"'+string+'"';},'number':function(number){return isFinite(number)?String(number):"null";},'boolean':function(bool){return String(bool);},'date':function(date){function format(number){return(number<10)?'0'+number:number;}
return'"'+date.getFullYear()+'-'+
format(date.getMonth()+1)+'-'+
format(date.getDate())+'T'+
format(date.getHours())+':'+
format(date.getMinutes())+':'+
format(date.getSeconds())+'"';}},CLASS_NAME:"OpenLayers.Format.JSON"});OpenLayers.Format.WFST=function(options){options=OpenLayers.Util.applyDefaults(options,OpenLayers.Format.WFST.DEFAULTS);var cls=OpenLayers.Format.WFST["v"+options.version.replace(/\./g,"_")];if(!cls){throw"Unsupported WFST version: "+options.version;}
return new cls(options);};OpenLayers.Format.WFST.DEFAULTS={"version":"1.0.0"};OpenLayers.Format.WMSCapabilities=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.1.1",version:null,parser:null,initialize:function(options){OpenLayers.Format.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var root=data.documentElement;var version=this.version||root.getAttribute("version")||this.defaultVersion;if(!this.parser||this.parser.version!==version){var constr=OpenLayers.Format.WMSCapabilities["v"+version.replace(/\./g,"_")];if(!constr){throw"Can't find a WMS capabilities parser for version "+version;}
var parser=new constr(this.options);}
var capabilities=parser.read(data);capabilities.version=version;return capabilities;},CLASS_NAME:"OpenLayers.Format.WMSCapabilities"});OpenLayers.Format.XML=OpenLayers.Class(OpenLayers.Format,{namespaces:null,namespaceAlias:null,defaultPrefix:null,readers:{},writers:{},xmldom:null,initialize:function(options){if(window.ActiveXObject){this.xmldom=new ActiveXObject("Microsoft.XMLDOM");}
OpenLayers.Format.prototype.initialize.apply(this,[options]);this.namespaces=OpenLayers.Util.extend({},this.namespaces);this.namespaceAlias={};for(var alias in this.namespaces){this.namespaceAlias[this.namespaces[alias]]=alias;}},destroy:function(){this.xmldom=null;OpenLayers.Format.prototype.destroy.apply(this,arguments);},setNamespace:function(alias,uri){this.namespaces[alias]=uri;this.namespaceAlias[uri]=alias;},read:function(text){var index=text.indexOf('<');if(index>0){text=text.substring(index);}
var node=OpenLayers.Util.Try(OpenLayers.Function.bind((function(){var xmldom;if(window.ActiveXObject&&!this.xmldom){xmldom=new ActiveXObject("Microsoft.XMLDOM");}else{xmldom=this.xmldom;}
xmldom.loadXML(text);return xmldom;}),this),function(){return new DOMParser().parseFromString(text,'text/xml');},function(){var req=new XMLHttpRequest();req.open("GET","data:"+"text/xml"+";charset=utf-8,"+encodeURIComponent(text),false);if(req.overrideMimeType){req.overrideMimeType("text/xml");}
req.send(null);return req.responseXML;});if(this.keepData){this.data=node;}
return node;},write:function(node){var data;if(this.xmldom){data=node.xml;}else{var serializer=new XMLSerializer();if(node.nodeType==1){var doc=document.implementation.createDocument("","",null);if(doc.importNode){node=doc.importNode(node,true);}
doc.appendChild(node);data=serializer.serializeToString(doc);}else{data=serializer.serializeToString(node);}}
return data;},createElementNS:function(uri,name){var element;if(this.xmldom){if(typeof uri=="string"){element=this.xmldom.createNode(1,name,uri);}else{element=this.xmldom.createNode(1,name,"");}}else{element=document.createElementNS(uri,name);}
return element;},createTextNode:function(text){var node;if(this.xmldom){node=this.xmldom.createTextNode(text);}else{node=document.createTextNode(text);}
return node;},getElementsByTagNameNS:function(node,uri,name){var elements=[];if(node.getElementsByTagNameNS){elements=node.getElementsByTagNameNS(uri,name);}else{var allNodes=node.getElementsByTagName("*");var potentialNode,fullName;for(var i=0,len=allNodes.length;i<len;++i){potentialNode=allNodes[i];fullName=(potentialNode.prefix)?(potentialNode.prefix+":"+name):name;if((name=="*")||(fullName==potentialNode.nodeName)){if((uri=="*")||(uri==potentialNode.namespaceURI)){elements.push(potentialNode);}}}}
return elements;},getAttributeNodeNS:function(node,uri,name){var attributeNode=null;if(node.getAttributeNodeNS){attributeNode=node.getAttributeNodeNS(uri,name);}else{var attributes=node.attributes;var potentialNode,fullName;for(var i=0,len=attributes.length;i<len;++i){potentialNode=attributes[i];if(potentialNode.namespaceURI==uri){fullName=(potentialNode.prefix)?(potentialNode.prefix+":"+name):name;if(fullName==potentialNode.nodeName){attributeNode=potentialNode;break;}}}}
return attributeNode;},getAttributeNS:function(node,uri,name){var attributeValue="";if(node.getAttributeNS){attributeValue=node.getAttributeNS(uri,name)||"";}else{var attributeNode=this.getAttributeNodeNS(node,uri,name);if(attributeNode){attributeValue=attributeNode.nodeValue;}}
return attributeValue;},getChildValue:function(node,def){var value=def||"";if(node){for(var child=node.firstChild;child;child=child.nextSibling){switch(child.nodeType){case 3:case 4:value+=child.nodeValue;}}}
return value;},concatChildValues:function(node,def){var value="";var child=node.firstChild;var childValue;while(child){childValue=child.nodeValue;if(childValue){value+=childValue;}
child=child.nextSibling;}
if(value==""&&def!=undefined){value=def;}
return value;},isSimpleContent:function(node){var simple=true;for(var child=node.firstChild;child;child=child.nextSibling){if(child.nodeType===1){simple=false;break;}}
return simple;},contentType:function(node){var simple=false,complex=false;var type=OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;for(var child=node.firstChild;child;child=child.nextSibling){switch(child.nodeType){case 1:complex=true;break;case 8:break;default:simple=true;}
if(complex&&simple){break;}}
if(complex&&simple){type=OpenLayers.Format.XML.CONTENT_TYPE.MIXED;}else if(complex){return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;}else if(simple){return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;}
return type;},hasAttributeNS:function(node,uri,name){var found=false;if(node.hasAttributeNS){found=node.hasAttributeNS(uri,name);}else{found=!!this.getAttributeNodeNS(node,uri,name);}
return found;},setAttributeNS:function(node,uri,name,value){if(node.setAttributeNS){node.setAttributeNS(uri,name,value);}else{if(this.xmldom){if(uri){var attribute=node.ownerDocument.createNode(2,name,uri);attribute.nodeValue=value;node.setAttributeNode(attribute);}else{node.setAttribute(name,value);}}else{throw"setAttributeNS not implemented";}}},createElementNSPlus:function(name,options){options=options||{};var uri=options.uri||this.namespaces[options.prefix];if(!uri){var loc=name.indexOf(":");uri=this.namespaces[name.substring(0,loc)];}
if(!uri){uri=this.namespaces[this.defaultPrefix];}
var node=this.createElementNS(uri,name);if(options.attributes){this.setAttributes(node,options.attributes);}
var value=options.value;if(value!=null){if(typeof value=="boolean"){value=String(value);}
node.appendChild(this.createTextNode(value));}
return node;},setAttributes:function(node,obj){var value,uri;for(var name in obj){if(obj[name]!=null&&obj[name].toString){value=obj[name].toString();uri=this.namespaces[name.substring(0,name.indexOf(":"))]||null;this.setAttributeNS(node,uri,name,value);}}},readNode:function(node,obj){if(!obj){obj={};}
var group=this.readers[this.namespaceAlias[node.namespaceURI]];if(group){var local=node.localName||node.nodeName.split(":").pop();var reader=group[local]||group["*"];if(reader){reader.apply(this,[node,obj]);}}
return obj;},readChildNodes:function(node,obj){if(!obj){obj={};}
var children=node.childNodes;var child;for(var i=0,len=children.length;i<len;++i){child=children[i];if(child.nodeType==1){this.readNode(child,obj);}}
return obj;},writeNode:function(name,obj,parent){var prefix,local;var split=name.indexOf(":");if(split>0){prefix=name.substring(0,split);local=name.substring(split+1);}else{if(parent){prefix=this.namespaceAlias[parent.namespaceURI];}else{prefix=this.defaultPrefix;}
local=name;}
var child=this.writers[prefix][local].apply(this,[obj]);if(parent){parent.appendChild(child);}
return child;},getChildEl:function(node,name,uri){return node&&this.getThisOrNextEl(node.firstChild,name,uri);},getNextEl:function(node,name,uri){return node&&this.getThisOrNextEl(node.nextSibling,name,uri);},getThisOrNextEl:function(node,name,uri){outer:for(var sibling=node;sibling;sibling=sibling.nextSibling){switch(sibling.nodeType){case 1:if((!name||name===(sibling.localName||sibling.nodeName.split(":").pop()))&&(!uri||uri===sibling.namespaceURI)){break outer;}
sibling=null;break outer;case 3:if(/^\s*$/.test(sibling.nodeValue)){break;}
case 4:case 6:case 12:case 10:case 11:sibling=null;break outer;}}
return sibling||null;},lookupNamespaceURI:function(node,prefix){var uri=null;if(node){if(node.lookupNamespaceURI){uri=node.lookupNamespaceURI(prefix);}else{outer:switch(node.nodeType){case 1:if(node.namespaceURI!==null&&node.prefix===prefix){uri=node.namespaceURI;break outer;}
var len=node.attributes.length;if(len){var attr;for(var i=0;i<len;++i){attr=node.attributes[i];if(attr.prefix==="xmlns"&&attr.name==="xmlns:"+prefix){uri=attr.value||null;break outer;}else if(attr.name==="xmlns"&&prefix===null){uri=attr.value||null;break outer;}}}
uri=this.lookupNamespaceURI(node.parentNode,prefix);break outer;case 2:uri=this.lookupNamespaceURI(node.ownerElement,prefix);break outer;case 9:uri=this.lookupNamespaceURI(node.documentElement,prefix);break outer;case 6:case 12:case 10:case 11:break outer;default:uri=this.lookupNamespaceURI(node.parentNode,prefix);break outer;}}}
return uri;},CLASS_NAME:"OpenLayers.Format.XML"});OpenLayers.Format.XML.CONTENT_TYPE={EMPTY:0,SIMPLE:1,COMPLEX:2,MIXED:3};OpenLayers.Format.XML.lookupNamespaceURI=OpenLayers.Function.bind(OpenLayers.Format.XML.prototype.lookupNamespaceURI,OpenLayers.Format.XML.prototype);OpenLayers.Handler=OpenLayers.Class({id:null,control:null,map:null,keyMask:null,active:false,evt:null,initialize:function(control,callbacks,options){OpenLayers.Util.extend(this,options);this.control=control;this.callbacks=callbacks;if(control.map){this.setMap(control.map);}
OpenLayers.Util.extend(this,options);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},setMap:function(map){this.map=map;},checkModifiers:function(evt){if(this.keyMask==null){return true;}
var keyModifiers=(evt.shiftKey?OpenLayers.Handler.MOD_SHIFT:0)|(evt.ctrlKey?OpenLayers.Handler.MOD_CTRL:0)|(evt.altKey?OpenLayers.Handler.MOD_ALT:0);return(keyModifiers==this.keyMask);},activate:function(){if(this.active){return false;}
var events=OpenLayers.Events.prototype.BROWSER_EVENTS;for(var i=0,len=events.length;i<len;i++){if(this[events[i]]){this.register(events[i],this[events[i]]);}}
this.active=true;return true;},deactivate:function(){if(!this.active){return false;}
var events=OpenLayers.Events.prototype.BROWSER_EVENTS;for(var i=0,len=events.length;i<len;i++){if(this[events[i]]){this.unregister(events[i],this[events[i]]);}}
this.active=false;return true;},callback:function(name,args){if(name&&this.callbacks[name]){this.callbacks[name].apply(this.control,args);}},register:function(name,method){this.map.events.registerPriority(name,this,method);this.map.events.registerPriority(name,this,this.setEvent);},unregister:function(name,method){this.map.events.unregister(name,this,method);this.map.events.unregister(name,this,this.setEvent);},setEvent:function(evt){this.evt=evt;return true;},destroy:function(){this.deactivate();this.control=this.map=null;},CLASS_NAME:"OpenLayers.Handler"});OpenLayers.Handler.MOD_NONE=0;OpenLayers.Handler.MOD_SHIFT=1;OpenLayers.Handler.MOD_CTRL=2;OpenLayers.Handler.MOD_ALT=4;OpenLayers.Map=OpenLayers.Class({Z_INDEX_BASE:{BaseLayer:100,Overlay:325,Feature:725,Popup:750,Control:1000},EVENT_TYPES:["preaddlayer","addlayer","removelayer","changelayer","movestart","move","moveend","zoomend","popupopen","popupclose","addmarker","removemarker","clearmarkers","mouseover","mouseout","mousemove","dragstart","drag","dragend","changebaselayer"],id:null,fractionalZoom:false,events:null,allOverlays:false,div:null,dragging:false,size:null,viewPortDiv:null,layerContainerOrigin:null,layerContainerDiv:null,layers:null,controls:null,popups:null,baseLayer:null,center:null,resolution:null,zoom:0,panRatio:1.5,viewRequestID:0,tileSize:null,projection:"EPSG:4326",units:'degrees',resolutions:null,maxResolution:1.40625,minResolution:null,maxScale:null,minScale:null,maxExtent:null,minExtent:null,restrictedExtent:null,numZoomLevels:16,theme:null,displayProjection:null,fallThrough:true,panTween:null,eventListeners:null,panMethod:OpenLayers.Easing.Expo.easeOut,panDuration:50,paddingForPopups:null,initialize:function(div,options){if(arguments.length===1&&typeof div==="object"){options=div;div=options&&options.div;}
this.tileSize=new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,OpenLayers.Map.TILE_HEIGHT);this.maxExtent=new OpenLayers.Bounds(-180,-90,180,90);this.paddingForPopups=new OpenLayers.Bounds(15,15,15,15);this.theme=OpenLayers._getScriptLocation()+'theme/default/style.css';OpenLayers.Util.extend(this,options);this.id=OpenLayers.Util.createUniqueID("OpenLayers.Map_");this.div=OpenLayers.Util.getElement(div);if(!this.div){this.div=document.createElement("div");this.div.style.height="1px";this.div.style.width="1px";}
OpenLayers.Element.addClass(this.div,'olMap');var id=this.div.id+"_OpenLayers_ViewPort";this.viewPortDiv=OpenLayers.Util.createDiv(id,null,null,null,"relative",null,"hidden");this.viewPortDiv.style.width="100%";this.viewPortDiv.style.height="100%";this.viewPortDiv.className="olMapViewport";this.div.appendChild(this.viewPortDiv);id=this.div.id+"_OpenLayers_Container";this.layerContainerDiv=OpenLayers.Util.createDiv(id);this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;this.viewPortDiv.appendChild(this.layerContainerDiv);this.events=new OpenLayers.Events(this,this.div,this.EVENT_TYPES,this.fallThrough,{includeXY:true});this.updateSize();if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}
this.events.register("movestart",this,this.updateSize);if(OpenLayers.String.contains(navigator.appName,"Microsoft")){this.events.register("resize",this,this.updateSize);}else{this.updateSizeDestroy=OpenLayers.Function.bind(this.updateSize,this);OpenLayers.Event.observe(window,'resize',this.updateSizeDestroy);}
if(this.theme){var addNode=true;var nodes=document.getElementsByTagName('link');for(var i=0,len=nodes.length;i<len;++i){if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,this.theme)){addNode=false;break;}}
if(addNode){var cssNode=document.createElement('link');cssNode.setAttribute('rel','stylesheet');cssNode.setAttribute('type','text/css');cssNode.setAttribute('href',this.theme);document.getElementsByTagName('head')[0].appendChild(cssNode);}}
this.layers=[];if(this.controls==null){if(OpenLayers.Control!=null){this.controls=[new OpenLayers.Control.Navigation(),new OpenLayers.Control.PanZoom(),new OpenLayers.Control.ArgParser(),new OpenLayers.Control.Attribution()];}else{this.controls=[];}}
for(var i=0,len=this.controls.length;i<len;i++){this.addControlToMap(this.controls[i]);}
this.popups=[];this.unloadDestroy=OpenLayers.Function.bind(this.destroy,this);OpenLayers.Event.observe(window,'unload',this.unloadDestroy);},render:function(div){this.div=OpenLayers.Util.getElement(div);OpenLayers.Element.addClass(this.div,'olMap');this.events.attachToElement(this.div);this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);this.div.appendChild(this.viewPortDiv);this.updateSize();},unloadDestroy:null,updateSizeDestroy:null,destroy:function(){if(!this.unloadDestroy){return false;}
OpenLayers.Event.stopObserving(window,'unload',this.unloadDestroy);this.unloadDestroy=null;if(this.updateSizeDestroy){OpenLayers.Event.stopObserving(window,'resize',this.updateSizeDestroy);}else{this.events.unregister("resize",this,this.updateSize);}
this.paddingForPopups=null;if(this.controls!=null){for(var i=this.controls.length-1;i>=0;--i){this.controls[i].destroy();}
this.controls=null;}
if(this.layers!=null){for(var i=this.layers.length-1;i>=0;--i){this.layers[i].destroy(false);}
this.layers=null;}
if(this.viewPortDiv){this.div.removeChild(this.viewPortDiv);}
this.viewPortDiv=null;if(this.eventListeners){this.events.un(this.eventListeners);this.eventListeners=null;}
this.events.destroy();this.events=null;},setOptions:function(options){OpenLayers.Util.extend(this,options);},getTileSize:function(){return this.tileSize;},getBy:function(array,property,match){var test=(typeof match.test=="function");var found=OpenLayers.Array.filter(this[array],function(item){return item[property]==match||(test&&match.test(item[property]));});return found;},getLayersBy:function(property,match){return this.getBy("layers",property,match);},getLayersByName:function(match){return this.getLayersBy("name",match);},getLayersByClass:function(match){return this.getLayersBy("CLASS_NAME",match);},getControlsBy:function(property,match){return this.getBy("controls",property,match);},getControlsByClass:function(match){return this.getControlsBy("CLASS_NAME",match);},getLayer:function(id){var foundLayer=null;for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];if(layer.id==id){foundLayer=layer;break;}}
return foundLayer;},setLayerZIndex:function(layer,zIdx){layer.setZIndex(this.Z_INDEX_BASE[layer.isBaseLayer?'BaseLayer':'Overlay']
+zIdx*5);},resetLayersZIndex:function(){for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];this.setLayerZIndex(layer,i);}},addLayer:function(layer){for(var i=0,len=this.layers.length;i<len;i++){if(this.layers[i]==layer){var msg=OpenLayers.i18n('layerAlreadyAdded',{'layerName':layer.name});OpenLayers.Console.warn(msg);return false;}}
if(this.allOverlays){layer.isBaseLayer=false;}
this.events.triggerEvent("preaddlayer",{layer:layer});layer.div.className="olLayerDiv";layer.div.style.overflow="";this.setLayerZIndex(layer,this.layers.length);if(layer.isFixed){this.viewPortDiv.appendChild(layer.div);}else{this.layerContainerDiv.appendChild(layer.div);}
this.layers.push(layer);layer.setMap(this);if(layer.isBaseLayer||(this.allOverlays&&!this.baseLayer)){if(this.baseLayer==null){this.setBaseLayer(layer);}else{layer.setVisibility(false);}}else{layer.redraw();}
this.events.triggerEvent("addlayer",{layer:layer});layer.afterAdd();},addLayers:function(layers){for(var i=0,len=layers.length;i<len;i++){this.addLayer(layers[i]);}},removeLayer:function(layer,setNewBaseLayer){if(setNewBaseLayer==null){setNewBaseLayer=true;}
if(layer.isFixed){this.viewPortDiv.removeChild(layer.div);}else{this.layerContainerDiv.removeChild(layer.div);}
OpenLayers.Util.removeItem(this.layers,layer);layer.removeMap(this);layer.map=null;if(this.baseLayer==layer){this.baseLayer=null;if(setNewBaseLayer){for(var i=0,len=this.layers.length;i<len;i++){var iLayer=this.layers[i];if(iLayer.isBaseLayer||this.allOverlays){this.setBaseLayer(iLayer);break;}}}}
this.resetLayersZIndex();this.events.triggerEvent("removelayer",{layer:layer});},getNumLayers:function(){return this.layers.length;},getLayerIndex:function(layer){return OpenLayers.Util.indexOf(this.layers,layer);},setLayerIndex:function(layer,idx){var base=this.getLayerIndex(layer);if(idx<0){idx=0;}else if(idx>this.layers.length){idx=this.layers.length;}
if(base!=idx){this.layers.splice(base,1);this.layers.splice(idx,0,layer);for(var i=0,len=this.layers.length;i<len;i++){this.setLayerZIndex(this.layers[i],i);}
this.events.triggerEvent("changelayer",{layer:layer,property:"order"});if(this.allOverlays){if(idx===0){this.setBaseLayer(layer);}else if(this.baseLayer!==this.layers[0]){this.setBaseLayer(this.layers[0]);}}}},raiseLayer:function(layer,delta){var idx=this.getLayerIndex(layer)+delta;this.setLayerIndex(layer,idx);},setBaseLayer:function(newBaseLayer){var oldExtent=null;if(this.baseLayer){oldExtent=this.baseLayer.getExtent();}
if(newBaseLayer!=this.baseLayer){if(OpenLayers.Util.indexOf(this.layers,newBaseLayer)!=-1){if(this.baseLayer!=null&&!this.allOverlays){this.baseLayer.setVisibility(false);}
this.baseLayer=newBaseLayer;this.viewRequestID++;if(!this.allOverlays){this.baseLayer.visibility=true;}
var center=this.getCenter();if(center!=null){var newCenter=(oldExtent)?oldExtent.getCenterLonLat():center;var newZoom=(oldExtent)?this.getZoomForExtent(oldExtent,true):this.getZoomForResolution(this.resolution,true);this.setCenter(newCenter,newZoom,false,true);}
this.events.triggerEvent("changebaselayer",{layer:this.baseLayer});}}},addControl:function(control,px){this.controls.push(control);this.addControlToMap(control,px);},addControlToMap:function(control,px){control.outsideViewport=(control.div!=null);if(this.displayProjection&&!control.displayProjection){control.displayProjection=this.displayProjection;}
control.setMap(this);var div=control.draw(px);if(div){if(!control.outsideViewport){div.style.zIndex=this.Z_INDEX_BASE['Control']+
this.controls.length;this.viewPortDiv.appendChild(div);}}},getControl:function(id){var returnControl=null;for(var i=0,len=this.controls.length;i<len;i++){var control=this.controls[i];if(control.id==id){returnControl=control;break;}}
return returnControl;},removeControl:function(control){if((control)&&(control==this.getControl(control.id))){if(control.div&&(control.div.parentNode==this.viewPortDiv)){this.viewPortDiv.removeChild(control.div);}
OpenLayers.Util.removeItem(this.controls,control);}},addPopup:function(popup,exclusive){if(exclusive){for(var i=this.popups.length-1;i>=0;--i){this.removePopup(this.popups[i]);}}
popup.map=this;this.popups.push(popup);var popupDiv=popup.draw();if(popupDiv){popupDiv.style.zIndex=this.Z_INDEX_BASE['Popup']+
this.popups.length;this.layerContainerDiv.appendChild(popupDiv);}},removePopup:function(popup){OpenLayers.Util.removeItem(this.popups,popup);if(popup.div){try{this.layerContainerDiv.removeChild(popup.div);}
catch(e){}}
popup.map=null;},getSize:function(){var size=null;if(this.size!=null){size=this.size.clone();}
return size;},updateSize:function(){this.events.clearMouseCache();var newSize=this.getCurrentSize();var oldSize=this.getSize();if(oldSize==null){this.size=oldSize=newSize;}
if(!newSize.equals(oldSize)){this.size=newSize;for(var i=0,len=this.layers.length;i<len;i++){this.layers[i].onMapResize();}
if(this.baseLayer!=null){var center=new OpenLayers.Pixel(newSize.w/2,newSize.h/2);var centerLL=this.getLonLatFromViewPortPx(center);var zoom=this.getZoom();this.zoom=null;this.setCenter(this.getCenter(),zoom);}}},getCurrentSize:function(){var size=new OpenLayers.Size(this.div.clientWidth,this.div.clientHeight);if(size.w==0&&size.h==0||isNaN(size.w)&&isNaN(size.h)){var dim=OpenLayers.Element.getDimensions(this.div);size.w=dim.width;size.h=dim.height;}
if(size.w==0&&size.h==0||isNaN(size.w)&&isNaN(size.h)){size.w=parseInt(this.div.style.width);size.h=parseInt(this.div.style.height);}
return size;},calculateBounds:function(center,resolution){var extent=null;if(center==null){center=this.getCenter();}
if(resolution==null){resolution=this.getResolution();}
if((center!=null)&&(resolution!=null)){var size=this.getSize();var w_deg=size.w*resolution;var h_deg=size.h*resolution;extent=new OpenLayers.Bounds(center.lon-w_deg/2,center.lat-h_deg/2,center.lon+w_deg/2,center.lat+h_deg/2);}
return extent;},getCenter:function(){var center=null;if(this.center){center=this.center.clone();}
return center;},getZoom:function(){return this.zoom;},pan:function(dx,dy,options){options=OpenLayers.Util.applyDefaults(options,{animate:true,dragging:false});var centerPx=this.getViewPortPxFromLonLat(this.getCenter());var newCenterPx=centerPx.add(dx,dy);if(!options.dragging||!newCenterPx.equals(centerPx)){var newCenterLonLat=this.getLonLatFromViewPortPx(newCenterPx);if(options.animate){this.panTo(newCenterLonLat);}else{this.setCenter(newCenterLonLat,null,options.dragging);}}},panTo:function(lonlat){if(this.panMethod&&this.getExtent().scale(this.panRatio).containsLonLat(lonlat)){if(!this.panTween){this.panTween=new OpenLayers.Tween(this.panMethod);}
var center=this.getCenter();if(lonlat.lon==center.lon&&lonlat.lat==center.lat){return;}
var from={lon:center.lon,lat:center.lat};var to={lon:lonlat.lon,lat:lonlat.lat};this.panTween.start(from,to,this.panDuration,{callbacks:{start:OpenLayers.Function.bind(function(lonlat){this.events.triggerEvent("movestart");},this),eachStep:OpenLayers.Function.bind(function(lonlat){lonlat=new OpenLayers.LonLat(lonlat.lon,lonlat.lat);this.moveTo(lonlat,this.zoom,{'dragging':true,'noEvent':true});},this),done:OpenLayers.Function.bind(function(lonlat){lonlat=new OpenLayers.LonLat(lonlat.lon,lonlat.lat);this.moveTo(lonlat,this.zoom,{'noEvent':true});this.events.triggerEvent("moveend");},this)}});}else{this.setCenter(lonlat);}},setCenter:function(lonlat,zoom,dragging,forceZoomChange){this.moveTo(lonlat,zoom,{'dragging':dragging,'forceZoomChange':forceZoomChange,'caller':'setCenter'});},moveTo:function(lonlat,zoom,options){if(!options){options={};}
var dragging=options.dragging;var forceZoomChange=options.forceZoomChange;var noEvent=options.noEvent;if(this.panTween&&options.caller=="setCenter"){this.panTween.stop();}
if(!this.center&&!this.isValidLonLat(lonlat)){lonlat=this.maxExtent.getCenterLonLat();}
if(this.restrictedExtent!=null){if(lonlat==null){lonlat=this.getCenter();}
if(zoom==null){zoom=this.getZoom();}
var resolution=this.getResolutionForZoom(zoom);var extent=this.calculateBounds(lonlat,resolution);if(!this.restrictedExtent.containsBounds(extent)){var maxCenter=this.restrictedExtent.getCenterLonLat();if(extent.getWidth()>this.restrictedExtent.getWidth()){lonlat=new OpenLayers.LonLat(maxCenter.lon,lonlat.lat);}else if(extent.left<this.restrictedExtent.left){lonlat=lonlat.add(this.restrictedExtent.left-
extent.left,0);}else if(extent.right>this.restrictedExtent.right){lonlat=lonlat.add(this.restrictedExtent.right-
extent.right,0);}
if(extent.getHeight()>this.restrictedExtent.getHeight()){lonlat=new OpenLayers.LonLat(lonlat.lon,maxCenter.lat);}else if(extent.bottom<this.restrictedExtent.bottom){lonlat=lonlat.add(0,this.restrictedExtent.bottom-
extent.bottom);}
else if(extent.top>this.restrictedExtent.top){lonlat=lonlat.add(0,this.restrictedExtent.top-
extent.top);}}}
var zoomChanged=forceZoomChange||((this.isValidZoomLevel(zoom))&&(zoom!=this.getZoom()));var centerChanged=(this.isValidLonLat(lonlat))&&(!lonlat.equals(this.center));if(zoomChanged||centerChanged||!dragging){if(!this.dragging&&!noEvent){this.events.triggerEvent("movestart");}
if(centerChanged){if((!zoomChanged)&&(this.center)){this.centerLayerContainer(lonlat);}
this.center=lonlat.clone();}
if((zoomChanged)||(this.layerContainerOrigin==null)){this.layerContainerOrigin=this.center.clone();this.layerContainerDiv.style.left="0px";this.layerContainerDiv.style.top="0px";}
if(zoomChanged){this.zoom=zoom;this.resolution=this.getResolutionForZoom(zoom);this.viewRequestID++;}
var bounds=this.getExtent();if(this.baseLayer.visibility){this.baseLayer.moveTo(bounds,zoomChanged,dragging);if(dragging){this.baseLayer.events.triggerEvent("move");}else{this.baseLayer.events.triggerEvent("moveend",{"zoomChanged":zoomChanged});}}
bounds=this.baseLayer.getExtent();for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];if(layer!==this.baseLayer&&!layer.isBaseLayer){var inRange=layer.calculateInRange();if(layer.inRange!=inRange){layer.inRange=inRange;if(!inRange){layer.display(false);}
this.events.triggerEvent("changelayer",{layer:layer,property:"visibility"});}
if(inRange&&layer.visibility){layer.moveTo(bounds,zoomChanged,dragging);if(dragging){layer.events.triggerEvent("move");}else{layer.events.triggerEvent("moveend",{"zoomChanged":zoomChanged});}}}}
if(zoomChanged){for(var i=0,len=this.popups.length;i<len;i++){this.popups[i].updatePosition();}}
this.events.triggerEvent("move");if(zoomChanged){this.events.triggerEvent("zoomend");}}
if(!dragging&&!noEvent){this.events.triggerEvent("moveend");}
this.dragging=!!dragging;},centerLayerContainer:function(lonlat){var originPx=this.getViewPortPxFromLonLat(this.layerContainerOrigin);var newPx=this.getViewPortPxFromLonLat(lonlat);if((originPx!=null)&&(newPx!=null)){this.layerContainerDiv.style.left=Math.round(originPx.x-newPx.x)+"px";this.layerContainerDiv.style.top=Math.round(originPx.y-newPx.y)+"px";}},isValidZoomLevel:function(zoomLevel){return((zoomLevel!=null)&&(zoomLevel>=0)&&(zoomLevel<this.getNumZoomLevels()));},isValidLonLat:function(lonlat){var valid=false;if(lonlat!=null){var maxExtent=this.getMaxExtent();valid=maxExtent.containsLonLat(lonlat);}
return valid;},getProjection:function(){var projection=this.getProjectionObject();return projection?projection.getCode():null;},getProjectionObject:function(){var projection=null;if(this.baseLayer!=null){projection=this.baseLayer.projection;}
return projection;},getMaxResolution:function(){var maxResolution=null;if(this.baseLayer!=null){maxResolution=this.baseLayer.maxResolution;}
return maxResolution;},getMaxExtent:function(options){var maxExtent=null;if(options&&options.restricted&&this.restrictedExtent){maxExtent=this.restrictedExtent;}else if(this.baseLayer!=null){maxExtent=this.baseLayer.maxExtent;}
return maxExtent;},getNumZoomLevels:function(){var numZoomLevels=null;if(this.baseLayer!=null){numZoomLevels=this.baseLayer.numZoomLevels;}
return numZoomLevels;},getExtent:function(){var extent=null;if(this.baseLayer!=null){extent=this.baseLayer.getExtent();}
return extent;},getResolution:function(){var resolution=null;if(this.baseLayer!=null){resolution=this.baseLayer.getResolution();}
return resolution;},getUnits:function(){var units=null;if(this.baseLayer!=null){units=this.baseLayer.units;}
return units;},getScale:function(){var scale=null;if(this.baseLayer!=null){var res=this.getResolution();var units=this.baseLayer.units;scale=OpenLayers.Util.getScaleFromResolution(res,units);}
return scale;},getZoomForExtent:function(bounds,closest){var zoom=null;if(this.baseLayer!=null){zoom=this.baseLayer.getZoomForExtent(bounds,closest);}
return zoom;},getResolutionForZoom:function(zoom){var resolution=null;if(this.baseLayer){resolution=this.baseLayer.getResolutionForZoom(zoom);}
return resolution;},getZoomForResolution:function(resolution,closest){var zoom=null;if(this.baseLayer!=null){zoom=this.baseLayer.getZoomForResolution(resolution,closest);}
return zoom;},zoomTo:function(zoom){if(this.isValidZoomLevel(zoom)){this.setCenter(null,zoom);}},zoomIn:function(){this.zoomTo(this.getZoom()+1);},zoomOut:function(){this.zoomTo(this.getZoom()-1);},zoomToExtent:function(bounds,closest){var center=bounds.getCenterLonLat();if(this.baseLayer.wrapDateLine){var maxExtent=this.getMaxExtent();bounds=bounds.clone();while(bounds.right<bounds.left){bounds.right+=maxExtent.getWidth();}
center=bounds.getCenterLonLat().wrapDateLine(maxExtent);}
this.setCenter(center,this.getZoomForExtent(bounds,closest));},zoomToMaxExtent:function(options){var restricted=(options)?options.restricted:true;var maxExtent=this.getMaxExtent({'restricted':restricted});this.zoomToExtent(maxExtent);},zoomToScale:function(scale,closest){var res=OpenLayers.Util.getResolutionFromScale(scale,this.baseLayer.units);var size=this.getSize();var w_deg=size.w*res;var h_deg=size.h*res;var center=this.getCenter();var extent=new OpenLayers.Bounds(center.lon-w_deg/2,center.lat-h_deg/2,center.lon+w_deg/2,center.lat+h_deg/2);this.zoomToExtent(extent,closest);},getLonLatFromViewPortPx:function(viewPortPx){var lonlat=null;if(this.baseLayer!=null){lonlat=this.baseLayer.getLonLatFromViewPortPx(viewPortPx);}
return lonlat;},getViewPortPxFromLonLat:function(lonlat){var px=null;if(this.baseLayer!=null){px=this.baseLayer.getViewPortPxFromLonLat(lonlat);}
return px;},getLonLatFromPixel:function(px){return this.getLonLatFromViewPortPx(px);},getPixelFromLonLat:function(lonlat){var px=this.getViewPortPxFromLonLat(lonlat);px.x=Math.round(px.x);px.y=Math.round(px.y);return px;},getViewPortPxFromLayerPx:function(layerPx){var viewPortPx=null;if(layerPx!=null){var dX=parseInt(this.layerContainerDiv.style.left);var dY=parseInt(this.layerContainerDiv.style.top);viewPortPx=layerPx.add(dX,dY);}
return viewPortPx;},getLayerPxFromViewPortPx:function(viewPortPx){var layerPx=null;if(viewPortPx!=null){var dX=-parseInt(this.layerContainerDiv.style.left);var dY=-parseInt(this.layerContainerDiv.style.top);layerPx=viewPortPx.add(dX,dY);if(isNaN(layerPx.x)||isNaN(layerPx.y)){layerPx=null;}}
return layerPx;},getLonLatFromLayerPx:function(px){px=this.getViewPortPxFromLayerPx(px);return this.getLonLatFromViewPortPx(px);},getLayerPxFromLonLat:function(lonlat){var px=this.getPixelFromLonLat(lonlat);return this.getLayerPxFromViewPortPx(px);},CLASS_NAME:"OpenLayers.Map"});OpenLayers.Map.TILE_WIDTH=256;OpenLayers.Map.TILE_HEIGHT=256;OpenLayers.Marker=OpenLayers.Class({icon:null,lonlat:null,events:null,map:null,initialize:function(lonlat,icon){this.lonlat=lonlat;var newIcon=(icon)?icon:OpenLayers.Marker.defaultIcon();if(this.icon==null){this.icon=newIcon;}else{this.icon.url=newIcon.url;this.icon.size=newIcon.size;this.icon.offset=newIcon.offset;this.icon.calculateOffset=newIcon.calculateOffset;}
this.events=new OpenLayers.Events(this,this.icon.imageDiv,null);},destroy:function(){this.erase();this.map=null;this.events.destroy();this.events=null;if(this.icon!=null){this.icon.destroy();this.icon=null;}},draw:function(px){return this.icon.draw(px);},erase:function(){if(this.icon!=null){this.icon.erase();}},moveTo:function(px){if((px!=null)&&(this.icon!=null)){this.icon.moveTo(px);}
this.lonlat=this.map.getLonLatFromLayerPx(px);},isDrawn:function(){var isDrawn=(this.icon&&this.icon.isDrawn());return isDrawn;},onScreen:function(){var onScreen=false;if(this.map){var screenBounds=this.map.getExtent();onScreen=screenBounds.containsLonLat(this.lonlat);}
return onScreen;},inflate:function(inflate){if(this.icon){var newSize=new OpenLayers.Size(this.icon.size.w*inflate,this.icon.size.h*inflate);this.icon.setSize(newSize);}},setOpacity:function(opacity){this.icon.setOpacity(opacity);},setUrl:function(url){this.icon.setUrl(url);},display:function(display){this.icon.display(display);},CLASS_NAME:"OpenLayers.Marker"});OpenLayers.Marker.defaultIcon=function(){var url=OpenLayers.Util.getImagesLocation()+"marker.png";var size=new OpenLayers.Size(21,25);var calculateOffset=function(size){return new OpenLayers.Pixel(-(size.w/2),-size.h);};return new OpenLayers.Icon(url,size,null,calculateOffset);};OpenLayers.Popup.FramedCloud=OpenLayers.Class(OpenLayers.Popup.Framed,{contentDisplayClass:"olFramedCloudPopupContent",autoSize:true,panMapIfOutOfView:true,imageSize:new OpenLayers.Size(676,736),isAlphaImage:false,fixedRelativePosition:false,positionBlocks:{"tl":{'offset':new OpenLayers.Pixel(44,0),'padding':new OpenLayers.Bounds(8,40,8,9),'blocks':[{size:new OpenLayers.Size('auto','auto'),anchor:new OpenLayers.Bounds(0,51,22,0),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,'auto'),anchor:new OpenLayers.Bounds(null,50,0,0),position:new OpenLayers.Pixel(-638,0)},{size:new OpenLayers.Size('auto',19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,18),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-638,-632)},{size:new OpenLayers.Size(81,35),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(0,-688)}]},"tr":{'offset':new OpenLayers.Pixel(-45,0),'padding':new OpenLayers.Bounds(8,40,8,9),'blocks':[{size:new OpenLayers.Size('auto','auto'),anchor:new OpenLayers.Bounds(0,51,22,0),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,'auto'),anchor:new OpenLayers.Bounds(null,50,0,0),position:new OpenLayers.Pixel(-638,0)},{size:new OpenLayers.Size('auto',19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,19),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-638,-631)},{size:new OpenLayers.Size(81,35),anchor:new OpenLayers.Bounds(0,0,null,null),position:new OpenLayers.Pixel(-215,-687)}]},"bl":{'offset':new OpenLayers.Pixel(45,0),'padding':new OpenLayers.Bounds(8,9,8,40),'blocks':[{size:new OpenLayers.Size('auto','auto'),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,'auto'),anchor:new OpenLayers.Bounds(null,21,0,32),position:new OpenLayers.Pixel(-638,0)},{size:new OpenLayers.Size('auto',21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0,-629)},{size:new OpenLayers.Size(22,21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-638,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(null,null,0,0),position:new OpenLayers.Pixel(-101,-674)}]},"br":{'offset':new OpenLayers.Pixel(-44,0),'padding':new OpenLayers.Bounds(8,9,8,40),'blocks':[{size:new OpenLayers.Size('auto','auto'),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,'auto'),anchor:new OpenLayers.Bounds(null,21,0,32),position:new OpenLayers.Pixel(-638,0)},{size:new OpenLayers.Size('auto',21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0,-629)},{size:new OpenLayers.Size(22,21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-638,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(0,null,null,0),position:new OpenLayers.Pixel(-311,-674)}]}},minSize:new OpenLayers.Size(105,10),maxSize:new OpenLayers.Size(600,660),initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){this.imageSrc=OpenLayers.Util.getImagesLocation()+'cloud-popup-relative.png';OpenLayers.Popup.Framed.prototype.initialize.apply(this,arguments);this.contentDiv.className=this.contentDisplayClass;},destroy:function(){OpenLayers.Popup.Framed.prototype.destroy.apply(this,arguments);},CLASS_NAME:"OpenLayers.Popup.FramedCloud"});OpenLayers.Request={DEFAULT_CONFIG:{method:"GET",url:window.location.href,async:true,user:undefined,password:undefined,params:null,proxy:OpenLayers.ProxyHost,headers:{},data:null,callback:function(){},success:null,failure:null,scope:null},events:new OpenLayers.Events(this,null,["complete","success","failure"]),issue:function(config){var defaultConfig=OpenLayers.Util.extend(this.DEFAULT_CONFIG,{proxy:OpenLayers.ProxyHost});config=OpenLayers.Util.applyDefaults(config,defaultConfig);var request=new OpenLayers.Request.XMLHttpRequest();var url=config.url;if(config.params){var paramString=OpenLayers.Util.getParameterString(config.params);if(paramString.length>0){var separator=(url.indexOf('?')>-1)?'&':'?';url+=separator+paramString;}}
if(config.proxy&&(url.indexOf("http")==0)){url=config.proxy+encodeURIComponent(url);}
request.open(config.method,url,config.async,config.user,config.password);for(var header in config.headers){request.setRequestHeader(header,config.headers[header]);}
var complete=(config.scope)?OpenLayers.Function.bind(config.callback,config.scope):config.callback;var success;if(config.success){success=(config.scope)?OpenLayers.Function.bind(config.success,config.scope):config.success;}
var failure;if(config.failure){failure=(config.scope)?OpenLayers.Function.bind(config.failure,config.scope):config.failure;}
var events=this.events;request.onreadystatechange=function(){if(request.readyState==OpenLayers.Request.XMLHttpRequest.DONE){var proceed=events.triggerEvent("complete",{request:request,config:config,requestUrl:url});if(proceed!==false){complete(request);if(!request.status||(request.status>=200&&request.status<300)){events.triggerEvent("success",{request:request,config:config,requestUrl:url});if(success){success(request);}}
if(request.status&&(request.status<200||request.status>=300)){events.triggerEvent("failure",{request:request,config:config,requestUrl:url});if(failure){failure(request);}}}}};if(config.async===false){request.send(config.data);}else{window.setTimeout(function(){request.send(config.data);},0);}
return request;},GET:function(config){config=OpenLayers.Util.extend(config,{method:"GET"});return OpenLayers.Request.issue(config);},POST:function(config){config=OpenLayers.Util.extend(config,{method:"POST"});config.headers=config.headers?config.headers:{};if(!("CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(config.headers))){config.headers["Content-Type"]="application/xml";}
return OpenLayers.Request.issue(config);},PUT:function(config){config=OpenLayers.Util.extend(config,{method:"PUT"});config.headers=config.headers?config.headers:{};if(!("CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(config.headers))){config.headers["Content-Type"]="application/xml";}
return OpenLayers.Request.issue(config);},DELETE:function(config){config=OpenLayers.Util.extend(config,{method:"DELETE"});return OpenLayers.Request.issue(config);},HEAD:function(config){config=OpenLayers.Util.extend(config,{method:"HEAD"});return OpenLayers.Request.issue(config);},OPTIONS:function(config){config=OpenLayers.Util.extend(config,{method:"OPTIONS"});return OpenLayers.Request.issue(config);}};OpenLayers.Tile.Image=OpenLayers.Class(OpenLayers.Tile,{url:null,imgDiv:null,frame:null,layerAlphaHack:null,isBackBuffer:false,lastRatio:1,isFirstDraw:true,backBufferTile:null,initialize:function(layer,position,bounds,url,size){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=url;this.frame=document.createElement('div');this.frame.style.overflow='hidden';this.frame.style.position='absolute';this.layerAlphaHack=this.layer.alpha&&OpenLayers.Util.alphaHack();},destroy:function(){if(this.imgDiv!=null){if(this.layerAlphaHack){OpenLayers.Event.stopObservingElement(this.imgDiv.childNodes[0].id);}
OpenLayers.Event.stopObservingElement(this.imgDiv.id);if(this.imgDiv.parentNode==this.frame){this.frame.removeChild(this.imgDiv);this.imgDiv.map=null;}
this.imgDiv.urls=null;this.imgDiv.src=OpenLayers.Util.getImagesLocation()+"blank.gif";}
this.imgDiv=null;if((this.frame!=null)&&(this.frame.parentNode==this.layer.div)){this.layer.div.removeChild(this.frame);}
this.frame=null;if(this.backBufferTile){this.backBufferTile.destroy();this.backBufferTile=null;}
this.layer.events.unregister("loadend",this,this.resetBackBuffer);OpenLayers.Tile.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile.Image(this.layer,this.position,this.bounds,this.url,this.size);}
obj=OpenLayers.Tile.prototype.clone.apply(this,[obj]);obj.imgDiv=null;return obj;},draw:function(){if(this.layer!=this.layer.map.baseLayer&&this.layer.reproject){this.bounds=this.getBoundsFromBaseLayer(this.position);}
var drawTile=OpenLayers.Tile.prototype.draw.apply(this,arguments);if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){if(drawTile){if(!this.backBufferTile){this.backBufferTile=this.clone();this.backBufferTile.hide();this.backBufferTile.isBackBuffer=true;this.events.register('loadend',this,this.resetBackBuffer);this.layer.events.register("loadend",this,this.resetBackBuffer);}
this.startTransition();}else{if(this.backBufferTile){this.backBufferTile.clear();}}}else{if(drawTile&&this.isFirstDraw){this.events.register('loadend',this,this.showTile);this.isFirstDraw=false;}}
if(!drawTile){return false;}
if(this.isLoading){this.events.triggerEvent("reload");}else{this.isLoading=true;this.events.triggerEvent("loadstart");}
return this.renderTile();},resetBackBuffer:function(){this.showTile();if(this.backBufferTile&&(this.isFirstDraw||!this.layer.numLoadingTiles)){this.isFirstDraw=false;var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));if(withinMaxExtent){this.backBufferTile.position=this.position;this.backBufferTile.bounds=this.bounds;this.backBufferTile.size=this.size;this.backBufferTile.imageSize=this.layer.imageSize||this.size;this.backBufferTile.imageOffset=this.layer.imageOffset;this.backBufferTile.resolution=this.layer.getResolution();this.backBufferTile.renderTile();}
this.backBufferTile.hide();}},renderTile:function(){if(this.imgDiv==null){this.initImgDiv();}
this.imgDiv.viewRequestID=this.layer.map.viewRequestID;if(this.layer.async){this.layer.getURLasync(this.bounds,this,"url",this.positionImage);}else{if(this.layer.url instanceof Array){this.imgDiv.urls=this.layer.url.slice();}
this.url=this.layer.getURL(this.bounds);this.positionImage();}
return true;},positionImage:function(){if(this.layer==null)
return;OpenLayers.Util.modifyDOMElement(this.frame,null,this.position,this.size);var imageSize=this.layer.getImageSize();if(this.layerAlphaHack){OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv,null,null,imageSize,this.url);}else{OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,imageSize);this.imgDiv.src=this.url;}},clear:function(){if(this.imgDiv){this.hide();if(OpenLayers.Tile.Image.useBlankTile){this.imgDiv.src=OpenLayers.Util.getImagesLocation()+"blank.gif";}}},initImgDiv:function(){var offset=this.layer.imageOffset;var size=this.layer.getImageSize();if(this.layerAlphaHack){this.imgDiv=OpenLayers.Util.createAlphaImageDiv(null,offset,size,null,"relative",null,null,null,true);}else{this.imgDiv=OpenLayers.Util.createImage(null,offset,size,null,"relative",null,null,true);}
this.imgDiv.className='olTileImage';this.frame.style.zIndex=this.isBackBuffer?0:1;this.frame.appendChild(this.imgDiv);this.layer.div.appendChild(this.frame);if(this.layer.opacity!=null){OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,null,null,null,null,this.layer.opacity);}
this.imgDiv.map=this.layer.map;var onload=function(){if(this.isLoading){this.isLoading=false;this.events.triggerEvent("loadend");}};if(this.layerAlphaHack){OpenLayers.Event.observe(this.imgDiv.childNodes[0],'load',OpenLayers.Function.bind(onload,this));}else{OpenLayers.Event.observe(this.imgDiv,'load',OpenLayers.Function.bind(onload,this));}
var onerror=function(){if(this.imgDiv._attempts>OpenLayers.IMAGE_RELOAD_ATTEMPTS){onload.call(this);}};OpenLayers.Event.observe(this.imgDiv,"error",OpenLayers.Function.bind(onerror,this));},checkImgURL:function(){if(this.layer){var loaded=this.layerAlphaHack?this.imgDiv.firstChild.src:this.imgDiv.src;if(!OpenLayers.Util.isEquivalentUrl(loaded,this.url)){this.hide();}}},startTransition:function(){if(!this.backBufferTile||!this.backBufferTile.imgDiv){return;}
var ratio=1;if(this.backBufferTile.resolution){ratio=this.backBufferTile.resolution/this.layer.getResolution();}
if(ratio!=this.lastRatio){if(this.layer.transitionEffect=='resize'){var upperLeft=new OpenLayers.LonLat(this.backBufferTile.bounds.left,this.backBufferTile.bounds.top);var size=new OpenLayers.Size(this.backBufferTile.size.w*ratio,this.backBufferTile.size.h*ratio);var px=this.layer.map.getLayerPxFromLonLat(upperLeft);OpenLayers.Util.modifyDOMElement(this.backBufferTile.frame,null,px,size);var imageSize=this.backBufferTile.imageSize;imageSize=new OpenLayers.Size(imageSize.w*ratio,imageSize.h*ratio);var imageOffset=this.backBufferTile.imageOffset;if(imageOffset){imageOffset=new OpenLayers.Pixel(imageOffset.x*ratio,imageOffset.y*ratio);}
OpenLayers.Util.modifyDOMElement(this.backBufferTile.imgDiv,null,imageOffset,imageSize);this.backBufferTile.show();}}else{if(this.layer.singleTile){this.backBufferTile.show();}else{this.backBufferTile.hide();}}
this.lastRatio=ratio;},show:function(){this.frame.style.display='';if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){if(navigator.userAgent.toLowerCase().indexOf("gecko")!=-1){this.frame.scrollLeft=this.frame.scrollLeft;}}},hide:function(){this.frame.style.display='none';},CLASS_NAME:"OpenLayers.Tile.Image"});OpenLayers.Tile.Image.useBlankTile=(OpenLayers.Util.getBrowserName()=="safari"||OpenLayers.Util.getBrowserName()=="opera");OpenLayers.Control.OverviewMap=OpenLayers.Class(OpenLayers.Control,{element:null,ovmap:null,size:new OpenLayers.Size(180,90),layers:null,minRectSize:15,minRectDisplayClass:"RectReplacement",minRatio:8,maxRatio:32,mapOptions:null,autoPan:false,handlers:null,resolutionFactor:1,initialize:function(options){this.layers=[];this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,[options]);},destroy:function(){if(!this.mapDiv){return;}
this.handlers.click.destroy();this.mapDiv.removeChild(this.extentRectangle);this.extentRectangle=null;this.rectEvents.destroy();this.rectEvents=null;this.ovmap.destroy();this.ovmap=null;this.element.removeChild(this.mapDiv);this.mapDiv=null;this.div.removeChild(this.element);this.element=null;if(this.maximizeDiv){OpenLayers.Event.stopObservingElement(this.maximizeDiv);this.div.removeChild(this.maximizeDiv);this.maximizeDiv=null;}
if(this.minimizeDiv){OpenLayers.Event.stopObservingElement(this.minimizeDiv);this.div.removeChild(this.minimizeDiv);this.minimizeDiv=null;}
this.map.events.un({"moveend":this.update,"changebaselayer":this.baseLayerDraw,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!(this.layers.length>0)){if(this.map.baseLayer){var layer=this.map.baseLayer.clone();this.layers=[layer];}else{this.map.events.register("changebaselayer",this,this.baseLayerDraw);return this.div;}}
this.element=document.createElement('div');this.element.className=this.displayClass+'Element';this.element.style.display='none';this.mapDiv=document.createElement('div');this.mapDiv.style.width=this.size.w+'px';this.mapDiv.style.height=this.size.h+'px';this.mapDiv.style.position='relative';this.mapDiv.style.overflow='hidden';this.mapDiv.id=OpenLayers.Util.createUniqueID('overviewMap');this.extentRectangle=document.createElement('div');this.extentRectangle.style.position='absolute';this.extentRectangle.style.zIndex=1000;this.extentRectangle.className=this.displayClass+'ExtentRectangle';this.mapDiv.appendChild(this.extentRectangle);this.element.appendChild(this.mapDiv);this.div.appendChild(this.element);if(!this.outsideViewport){this.div.className+=" "+this.displayClass+'Container';var imgLocation=OpenLayers.Util.getImagesLocation();var img=imgLocation+'layer-switcher-maximize.png';this.maximizeDiv=OpenLayers.Util.createAlphaImageDiv(this.displayClass+'MaximizeButton',null,new OpenLayers.Size(18,18),img,'absolute');this.maximizeDiv.style.display='none';this.maximizeDiv.className=this.displayClass+'MaximizeButton';OpenLayers.Event.observe(this.maximizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.maximizeControl,this));this.div.appendChild(this.maximizeDiv);var img=imgLocation+'layer-switcher-minimize.png';this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv('OpenLayers_Control_minimizeDiv',null,new OpenLayers.Size(18,18),img,'absolute');this.minimizeDiv.style.display='none';this.minimizeDiv.className=this.displayClass+'MinimizeButton';OpenLayers.Event.observe(this.minimizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.minimizeControl,this));this.div.appendChild(this.minimizeDiv);var eventsToStop=['dblclick','mousedown'];for(var i=0,len=eventsToStop.length;i<len;i++){OpenLayers.Event.observe(this.maximizeDiv,eventsToStop[i],OpenLayers.Event.stop);OpenLayers.Event.observe(this.minimizeDiv,eventsToStop[i],OpenLayers.Event.stop);}
this.minimizeControl();}else{this.element.style.display='';}
if(this.map.getExtent()){this.update();}
this.map.events.register('moveend',this,this.update);return this.div;},baseLayerDraw:function(){this.draw();this.map.events.unregister("changebaselayer",this,this.baseLayerDraw);},rectDrag:function(px){var deltaX=this.handlers.drag.last.x-px.x;var deltaY=this.handlers.drag.last.y-px.y;if(deltaX!=0||deltaY!=0){var rectTop=this.rectPxBounds.top;var rectLeft=this.rectPxBounds.left;var rectHeight=Math.abs(this.rectPxBounds.getHeight());var rectWidth=this.rectPxBounds.getWidth();var newTop=Math.max(0,(rectTop-deltaY));newTop=Math.min(newTop,this.ovmap.size.h-this.hComp-rectHeight);var newLeft=Math.max(0,(rectLeft-deltaX));newLeft=Math.min(newLeft,this.ovmap.size.w-this.wComp-rectWidth);this.setRectPxBounds(new OpenLayers.Bounds(newLeft,newTop+rectHeight,newLeft+rectWidth,newTop));}},mapDivClick:function(evt){var pxCenter=this.rectPxBounds.getCenterPixel();var deltaX=evt.xy.x-pxCenter.x;var deltaY=evt.xy.y-pxCenter.y;var top=this.rectPxBounds.top;var left=this.rectPxBounds.left;var height=Math.abs(this.rectPxBounds.getHeight());var width=this.rectPxBounds.getWidth();var newTop=Math.max(0,(top+deltaY));newTop=Math.min(newTop,this.ovmap.size.h-height);var newLeft=Math.max(0,(left+deltaX));newLeft=Math.min(newLeft,this.ovmap.size.w-width);this.setRectPxBounds(new OpenLayers.Bounds(newLeft,newTop+height,newLeft+width,newTop));this.updateMapToRect();},maximizeControl:function(e){this.element.style.display='';this.showToggle(false);if(e!=null){OpenLayers.Event.stop(e);}},minimizeControl:function(e){this.element.style.display='none';this.showToggle(true);if(e!=null){OpenLayers.Event.stop(e);}},showToggle:function(minimize){this.maximizeDiv.style.display=minimize?'':'none';this.minimizeDiv.style.display=minimize?'none':'';},update:function(){if(this.ovmap==null){this.createMap();}
if(this.autoPan||!this.isSuitableOverview()){this.updateOverview();}
this.updateRectToMap();},isSuitableOverview:function(){var mapExtent=this.map.getExtent();var maxExtent=this.map.maxExtent;var testExtent=new OpenLayers.Bounds(Math.max(mapExtent.left,maxExtent.left),Math.max(mapExtent.bottom,maxExtent.bottom),Math.min(mapExtent.right,maxExtent.right),Math.min(mapExtent.top,maxExtent.top));if(this.ovmap.getProjection()!=this.map.getProjection()){testExtent=testExtent.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}
var resRatio=this.ovmap.getResolution()/this.map.getResolution();return((resRatio>this.minRatio)&&(resRatio<=this.maxRatio)&&(this.ovmap.getExtent().containsBounds(testExtent)));},updateOverview:function(){var mapRes=this.map.getResolution();var targetRes=this.ovmap.getResolution();var resRatio=targetRes/mapRes;if(resRatio>this.maxRatio){targetRes=this.minRatio*mapRes;}else if(resRatio<=this.minRatio){targetRes=this.maxRatio*mapRes;}
var center;if(this.ovmap.getProjection()!=this.map.getProjection()){center=this.map.center.clone();center.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}else{center=this.map.center;}
this.ovmap.setCenter(center,this.ovmap.getZoomForResolution(targetRes*this.resolutionFactor));this.updateRectToMap();},createMap:function(){var options=OpenLayers.Util.extend({controls:[],maxResolution:'auto',fallThrough:false},this.mapOptions);this.ovmap=new OpenLayers.Map(this.mapDiv,options);OpenLayers.Event.stopObserving(window,'unload',this.ovmap.unloadDestroy);this.ovmap.addLayers(this.layers);this.ovmap.zoomToMaxExtent();this.wComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-left-width'))+
parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-right-width'));this.wComp=(this.wComp)?this.wComp:2;this.hComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-top-width'))+
parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-bottom-width'));this.hComp=(this.hComp)?this.hComp:2;this.handlers.drag=new OpenLayers.Handler.Drag(this,{move:this.rectDrag,done:this.updateMapToRect},{map:this.ovmap});this.handlers.click=new OpenLayers.Handler.Click(this,{"click":this.mapDivClick},{"single":true,"double":false,"stopSingle":true,"stopDouble":true,"pixelTolerance":1,map:this.ovmap});this.handlers.click.activate();this.rectEvents=new OpenLayers.Events(this,this.extentRectangle,null,true);this.rectEvents.register("mouseover",this,function(e){if(!this.handlers.drag.active&&!this.map.dragging){this.handlers.drag.activate();}});this.rectEvents.register("mouseout",this,function(e){if(!this.handlers.drag.dragging){this.handlers.drag.deactivate();}});if(this.ovmap.getProjection()!=this.map.getProjection()){var sourceUnits=this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units;var targetUnits=this.ovmap.getProjectionObject().getUnits()||this.ovmap.units||this.ovmap.baseLayer.units;this.resolutionFactor=sourceUnits&&targetUnits?OpenLayers.INCHES_PER_UNIT[sourceUnits]/OpenLayers.INCHES_PER_UNIT[targetUnits]:1;}},updateRectToMap:function(){var bounds;if(this.ovmap.getProjection()!=this.map.getProjection()){bounds=this.map.getExtent().transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}else{bounds=this.map.getExtent();}
var pxBounds=this.getRectBoundsFromMapBounds(bounds);if(pxBounds){this.setRectPxBounds(pxBounds);}},updateMapToRect:function(){var lonLatBounds=this.getMapBoundsFromRectBounds(this.rectPxBounds);if(this.ovmap.getProjection()!=this.map.getProjection()){lonLatBounds=lonLatBounds.transform(this.ovmap.getProjectionObject(),this.map.getProjectionObject());}
this.map.panTo(lonLatBounds.getCenterLonLat());},setRectPxBounds:function(pxBounds){var top=Math.max(pxBounds.top,0);var left=Math.max(pxBounds.left,0);var bottom=Math.min(pxBounds.top+Math.abs(pxBounds.getHeight()),this.ovmap.size.h-this.hComp);var right=Math.min(pxBounds.left+pxBounds.getWidth(),this.ovmap.size.w-this.wComp);var width=Math.max(right-left,0);var height=Math.max(bottom-top,0);if(width<this.minRectSize||height<this.minRectSize){this.extentRectangle.className=this.displayClass+
this.minRectDisplayClass;var rLeft=left+(width/2)-(this.minRectSize/2);var rTop=top+(height/2)-(this.minRectSize/2);this.extentRectangle.style.top=Math.round(rTop)+'px';this.extentRectangle.style.left=Math.round(rLeft)+'px';this.extentRectangle.style.height=this.minRectSize+'px';this.extentRectangle.style.width=this.minRectSize+'px';}else{this.extentRectangle.className=this.displayClass+'ExtentRectangle';this.extentRectangle.style.top=Math.round(top)+'px';this.extentRectangle.style.left=Math.round(left)+'px';this.extentRectangle.style.height=Math.round(height)+'px';this.extentRectangle.style.width=Math.round(width)+'px';}
this.rectPxBounds=new OpenLayers.Bounds(Math.round(left),Math.round(bottom),Math.round(right),Math.round(top));},getRectBoundsFromMapBounds:function(lonLatBounds){var leftBottomLonLat=new OpenLayers.LonLat(lonLatBounds.left,lonLatBounds.bottom);var rightTopLonLat=new OpenLayers.LonLat(lonLatBounds.right,lonLatBounds.top);var leftBottomPx=this.getOverviewPxFromLonLat(leftBottomLonLat);var rightTopPx=this.getOverviewPxFromLonLat(rightTopLonLat);var bounds=null;if(leftBottomPx&&rightTopPx){bounds=new OpenLayers.Bounds(leftBottomPx.x,leftBottomPx.y,rightTopPx.x,rightTopPx.y);}
return bounds;},getMapBoundsFromRectBounds:function(pxBounds){var leftBottomPx=new OpenLayers.Pixel(pxBounds.left,pxBounds.bottom);var rightTopPx=new OpenLayers.Pixel(pxBounds.right,pxBounds.top);var leftBottomLonLat=this.getLonLatFromOverviewPx(leftBottomPx);var rightTopLonLat=this.getLonLatFromOverviewPx(rightTopPx);return new OpenLayers.Bounds(leftBottomLonLat.lon,leftBottomLonLat.lat,rightTopLonLat.lon,rightTopLonLat.lat);},getLonLatFromOverviewPx:function(overviewMapPx){var size=this.ovmap.size;var res=this.ovmap.getResolution();var center=this.ovmap.getExtent().getCenterLonLat();var delta_x=overviewMapPx.x-(size.w/2);var delta_y=overviewMapPx.y-(size.h/2);return new OpenLayers.LonLat(center.lon+delta_x*res,center.lat-delta_y*res);},getOverviewPxFromLonLat:function(lonlat){var res=this.ovmap.getResolution();var extent=this.ovmap.getExtent();var px=null;if(extent){px=new OpenLayers.Pixel(Math.round(1/res*(lonlat.lon-extent.left)),Math.round(1/res*(extent.top-lonlat.lat)));}
return px;},CLASS_NAME:'OpenLayers.Control.OverviewMap'});OpenLayers.Feature=OpenLayers.Class({layer:null,id:null,lonlat:null,data:null,marker:null,popupClass:OpenLayers.Popup.AnchoredBubble,popup:null,initialize:function(layer,lonlat,data){this.layer=layer;this.lonlat=lonlat;this.data=(data!=null)?data:{};this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){if((this.layer!=null)&&(this.layer.map!=null)){if(this.popup!=null){this.layer.map.removePopup(this.popup);}}
this.layer=null;this.id=null;this.lonlat=null;this.data=null;if(this.marker!=null){this.destroyMarker(this.marker);this.marker=null;}
if(this.popup!=null){this.destroyPopup(this.popup);this.popup=null;}},onScreen:function(){var onScreen=false;if((this.layer!=null)&&(this.layer.map!=null)){var screenBounds=this.layer.map.getExtent();onScreen=screenBounds.containsLonLat(this.lonlat);}
return onScreen;},createMarker:function(){if(this.lonlat!=null){this.marker=new OpenLayers.Marker(this.lonlat,this.data.icon);}
return this.marker;},destroyMarker:function(){this.marker.destroy();},createPopup:function(closeBox){if(this.lonlat!=null){var id=this.id+"_popup";var anchor=(this.marker)?this.marker.icon:null;if(!this.popup){this.popup=new this.popupClass(id,this.lonlat,this.data.popupSize,this.data.popupContentHTML,anchor,closeBox);}
if(this.data.overflow!=null){this.popup.contentDiv.style.overflow=this.data.overflow;}
this.popup.feature=this;}
return this.popup;},destroyPopup:function(){if(this.popup){this.popup.feature=null;this.popup.destroy();this.popup=null;}},CLASS_NAME:"OpenLayers.Feature"});OpenLayers.Format.WFSCapabilities=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.1.0",version:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var root=data.documentElement;var version=this.version;if(!version){version=root.getAttribute("version");if(!version){version=this.defaultVersion;}}
var constr=OpenLayers.Format.WFSCapabilities["v"+version.replace(/\./g,"_")];if(!constr){throw"Can't find a WFS capabilities parser for version "+version;}
var parser=new constr(this.options);var capabilities=parser.read(data);capabilities.version=version;return capabilities;},CLASS_NAME:"OpenLayers.Format.WFSCapabilities"});OpenLayers.Format.WFSDescribeFeatureType=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xsd:"http://www.w3.org/2001/XMLSchema"},initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},readers:{"xsd":{"schema":function(node,obj){var complexTypes=[];var customTypes={};var schema={complexTypes:complexTypes,customTypes:customTypes};this.readChildNodes(node,schema);var attributes=node.attributes;var attr,name;for(var i=0,len=attributes.length;i<len;++i){attr=attributes[i];name=attr.name;if(name.indexOf("xmlns")==0){this.setNamespace(name.split(":")[1]||"",attr.value);}else{obj[name]=attr.value;}}
obj.featureTypes=complexTypes;obj.targetPrefix=this.namespaceAlias[obj.targetNamespace];var complexType,customType;for(var i=0,len=complexTypes.length;i<len;++i){complexType=complexTypes[i];customType=customTypes[complexType.typeName];if(customTypes[complexType.typeName]){complexType.typeName=customType.name;}}},"complexType":function(node,obj){var complexType={"typeName":node.getAttribute("name")};this.readChildNodes(node,complexType);obj.complexTypes.push(complexType);},"complexContent":function(node,obj){this.readChildNodes(node,obj);},"extension":function(node,obj){this.readChildNodes(node,obj);},"sequence":function(node,obj){var sequence={elements:[]};this.readChildNodes(node,sequence);obj.properties=sequence.elements;},"element":function(node,obj){if(obj.elements){var element={};var attributes=node.attributes;var attr;for(var i=0,len=attributes.length;i<len;++i){attr=attributes[i];element[attr.name]=attr.value;}
var type=element.type;if(!type){type={};this.readChildNodes(node,type);element.restriction=type;element.type=type.base;}
var fullType=type.base||type;element.localType=fullType.split(":").pop();obj.elements.push(element);}
if(obj.complexTypes){var type=node.getAttribute("type");var localType=type.split(":").pop();obj.customTypes[localType]={"name":node.getAttribute("name"),"type":type};}},"simpleType":function(node,obj){this.readChildNodes(node,obj);},"restriction":function(node,obj){obj.base=node.getAttribute("base");this.readRestriction(node,obj);}}},readRestriction:function(node,obj){var children=node.childNodes;var child,nodeName,value;for(var i=0,len=children.length;i<len;++i){child=children[i];if(child.nodeType==1){nodeName=child.nodeName.split(":").pop();value=child.getAttribute("value");if(!obj[nodeName]){obj[nodeName]=value;}else{if(typeof obj[nodeName]=="string"){obj[nodeName]=[obj[nodeName]];}
obj[nodeName].push(value);}}}},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
if(data&&data.nodeType==9){data=data.documentElement;}
var schema={};this.readNode(data,schema);return schema;},CLASS_NAME:"OpenLayers.Format.WFSDescribeFeatureType"});OpenLayers.Format.WFST.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",wfs:"http://www.opengis.net/wfs",gml:"http://www.opengis.net/gml",ogc:"http://www.opengis.net/ogc"},defaultPrefix:"wfs",version:null,schemaLocations:null,srsName:null,extractAttributes:true,xy:true,stateName:null,initialize:function(options){this.stateName={};this.stateName[OpenLayers.State.INSERT]="wfs:Insert";this.stateName[OpenLayers.State.UPDATE]="wfs:Update";this.stateName[OpenLayers.State.DELETE]="wfs:Delete";OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},getSrsName:function(feature,options){var srsName=options&&options.srsName;if(!srsName){if(feature&&feature.layer){srsName=feature.layer.projection.getCode();}else{srsName=this.srsName;}}
return srsName;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
if(data&&data.nodeType==9){data=data.documentElement;}
var obj={};this.readNode(data,obj);if(obj.features){obj=obj.features;}
return obj;},readers:{"wfs":{"FeatureCollection":function(node,obj){obj.features=[];this.readChildNodes(node,obj);}}},write:function(features){var node=this.writeNode("wfs:Transaction",features);var value=this.schemaLocationAttr();if(value){this.setAttributeNS(node,this.namespaces["xsi"],"xsi:schemaLocation",value)}
return OpenLayers.Format.XML.prototype.write.apply(this,[node]);},writers:{"wfs":{"GetFeature":function(options){var node=this.createElementNSPlus("wfs:GetFeature",{attributes:{service:"WFS",version:this.version,maxFeatures:options&&options.maxFeatures,"xsi:schemaLocation":this.schemaLocationAttr(options)}});this.writeNode("Query",options,node);return node;},"Query":function(options){options=OpenLayers.Util.extend({featureNS:this.featureNS,featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName},options);var node=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(options.featureNS?options.featurePrefix+":":"")+
options.featureType,srsName:options.srsName}});if(options.featureNS){node.setAttribute("xmlns:"+options.featurePrefix,options.featureNS);}
if(options.filter){this.setFilterProperty(options.filter);this.writeNode("ogc:Filter",options.filter,node);}
return node;},"Transaction":function(features){var node=this.createElementNSPlus("wfs:Transaction",{attributes:{service:"WFS",version:this.version}});if(features){var name,feature;for(var i=0,len=features.length;i<len;++i){feature=features[i];name=this.stateName[feature.state];if(name){this.writeNode(name,feature,node);}}}
return node;},"Insert":function(feature){var node=this.createElementNSPlus("wfs:Insert");this.srsName=this.getSrsName(feature);this.writeNode("feature:_typeName",feature,node);return node;},"Update":function(feature){var node=this.createElementNSPlus("wfs:Update",{attributes:{typeName:(this.featureNS?this.featurePrefix+":":"")+
this.featureType}});if(this.featureNS){node.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);}
this.writeNode("Property",{name:this.geometryName,value:feature},node);for(var key in feature.attributes){this.writeNode("Property",{name:key,value:feature.attributes[key]},node);}
this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[feature.fid]}),node);return node;},"Property":function(obj){var node=this.createElementNSPlus("wfs:Property");this.writeNode("Name",obj.name,node);this.writeNode("Value",obj.value,node);return node;},"Name":function(name){return this.createElementNSPlus("wfs:Name",{value:name});},"Value":function(obj){var node;if(obj instanceof OpenLayers.Feature.Vector){node=this.createElementNSPlus("wfs:Value");this.srsName=this.getSrsName(obj);var geom=this.writeNode("feature:_geometry",obj.geometry).firstChild;node.appendChild(geom);}else{node=this.createElementNSPlus("wfs:Value",{value:obj});}
return node;},"Delete":function(feature){var node=this.createElementNSPlus("wfs:Delete",{attributes:{typeName:(this.featureNS?this.featurePrefix+":":"")+
this.featureType}});if(this.featureNS){node.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);}
this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[feature.fid]}),node);return node;}}},schemaLocationAttr:function(options){options=OpenLayers.Util.extend({featurePrefix:this.featurePrefix,schema:this.schema},options);var schemaLocations=OpenLayers.Util.extend({},this.schemaLocations);if(options.schema){schemaLocations[options.featurePrefix]=options.schema;}
var parts=[];var uri;for(var key in schemaLocations){uri=this.namespaces[key];if(uri){parts.push(uri+" "+schemaLocations[key]);}}
var value=parts.join(" ")||undefined;return value;},setFilterProperty:function(filter){if(filter.filters){for(var i=0,len=filter.filters.length;i<len;++i){this.setFilterProperty(filter.filters[i]);}}else{if(filter instanceof OpenLayers.Filter.Spatial){filter.property=this.geometryName;}}},CLASS_NAME:"OpenLayers.Format.WFST.v1"});OpenLayers.Format.WMC=OpenLayers.Class({defaultVersion:"1.1.0",version:null,layerOptions:null,layerParams:null,parser:null,initialize:function(options){OpenLayers.Util.extend(this,options);this.options=options;},read:function(data,options){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var root=data.documentElement;var version=this.version;if(!version){version=root.getAttribute("version");if(!version){version=this.defaultVersion;}}
if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.WMC["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a WMC parser for version "+
version;}
this.parser=new format(this.options);}
var context=this.parser.read(data,options);var map;if(options.map){this.context=context;if(options.map instanceof OpenLayers.Map){map=this.mergeContextToMap(context,options.map);}else{map=this.contextToMap(context,options.map);}}else{map=context;}
return map;},contextToMap:function(context,id){var map=new OpenLayers.Map(id,{maxExtent:context.maxExtent,projection:context.projection});map.addLayers(context.layers);map.setCenter(context.bounds.getCenterLonLat(),map.getZoomForExtent(context.bounds,true));return map;},mergeContextToMap:function(context,map){map.addLayers(context.layers);return map;},write:function(obj,options){if(obj.CLASS_NAME=="OpenLayers.Map"){obj=this.mapToContext(obj);}
var version=(options&&options.version)||this.version||this.defaultVersion;if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.WMC["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a WMS capabilities parser for version "+
version;}
this.parser=new format(this.options);}
var wmc=this.parser.write(obj,options);return wmc;},mapToContext:function(map){var context={bounds:map.getExtent(),maxExtent:map.maxExtent,projection:map.projection,layers:map.layers,size:map.getSize()};return context;},CLASS_NAME:"OpenLayers.Format.WMC"});OpenLayers.Format.WMSCapabilities.v1_1=OpenLayers.Class(OpenLayers.Format.XML,{initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var capabilities={};var root=data.documentElement;this.runChildNodes(capabilities,root);return capabilities;},runChildNodes:function(obj,node){var children=node.childNodes;var childNode,processor;for(var i=0;i<children.length;++i){childNode=children[i];if(childNode.nodeType==1){processor=this["read_cap_"+childNode.nodeName];if(processor){processor.apply(this,[obj,childNode]);}}}},read_cap_Capability:function(capabilities,node){var capability={layers:[]};this.runChildNodes(capability,node);capabilities.capability=capability;},read_cap_Request:function(obj,node){var request={};this.runChildNodes(request,node);obj.request=request;},read_cap_GetMap:function(request,node){var getmap={formats:[]};this.runChildNodes(getmap,node);request.getmap=getmap;},read_cap_Format:function(obj,node){if(obj.formats){obj.formats.push(this.getChildValue(node));}},read_cap_DCPType:function(obj,node){var children=node.getElementsByTagName("OnlineResource");if(children.length>0){this.read_cap_OnlineResource(obj,children[0]);}},read_cap_Service:function(capabilities,node){var service={};this.runChildNodes(service,node);capabilities.service=service;},read_cap_Layer:function(capability,node,parentLayer){var layer={formats:capability.request.getmap.formats||[],styles:[],queryable:(node.getAttribute("queryable")==="1"||node.getAttribute("queryable")==="true")};if(parentLayer){layer.styles=layer.styles.concat(parentLayer.styles);layer.llbbox=parentLayer.llbbox;layer.minScale=parentLayer.minScale;layer.maxScale=parentLayer.maxScale;}
var children=node.childNodes;var childNode,nodeName,processor;for(var i=0;i<children.length;++i){childNode=children[i];nodeName=childNode.nodeName;processor=this["read_cap_"+childNode.nodeName];if(processor){if(nodeName=="Layer"){processor.apply(this,[capability,childNode,layer]);}else{processor.apply(this,[layer,childNode]);}}}
if(layer.name){var index=layer.name.indexOf(":");if(index>0){layer.prefix=layer.name.substring(0,index);}
capability.layers.push(layer);}},read_cap_ScaleHint:function(layer,node){var min=node.getAttribute("min");var max=node.getAttribute("max");var rad2=Math.pow(2,0.5);var ipm=OpenLayers.INCHES_PER_UNIT["m"];layer.maxScale=parseFloat(((rad2*min)*ipm*OpenLayers.DOTS_PER_INCH).toPrecision(13));layer.minScale=parseFloat(((rad2*max)*ipm*OpenLayers.DOTS_PER_INCH).toPrecision(13));},read_cap_Name:function(obj,node){var name=this.getChildValue(node);if(name){obj.name=name;}},read_cap_Title:function(obj,node){var title=this.getChildValue(node);if(title){obj.title=title;}},read_cap_Abstract:function(obj,node){var abst=this.getChildValue(node);if(abst){obj["abstract"]=abst;}},read_cap_LatLonBoundingBox:function(layer,node){layer.llbbox=[parseFloat(node.getAttribute("minx")),parseFloat(node.getAttribute("miny")),parseFloat(node.getAttribute("maxx")),parseFloat(node.getAttribute("maxy"))];},read_cap_Style:function(layer,node){var style={};this.runChildNodes(style,node);layer.styles.push(style);},read_cap_LegendURL:function(style,node){var legend={width:node.getAttribute('width'),height:node.getAttribute('height')};var links=node.getElementsByTagName("OnlineResource");if(links.length>0){this.read_cap_OnlineResource(legend,links[0]);}
style.legend=legend;},read_cap_OnlineResource:function(obj,node){obj.href=this.getAttributeNS(node,"http://www.w3.org/1999/xlink","href");},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1"});OpenLayers.Format.WMSDescribeLayer=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.1.1",version:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var root=data.documentElement;var version=this.version;if(!version){version=root.getAttribute("version");if(!version){version=this.defaultVersion;}}
if(version=="1.1.1"||version=="1.1.0"){version="1.1";}
var constructor=OpenLayers.Format.WMSDescribeLayer["v"+version.replace(/\./g,"_")];if(!constructor){throw"Can't find a WMS DescribeLayer parser for version "+
version;}
var parser=new constructor(this.options);var describelayer=parser.read(data);describelayer.version=version;return describelayer;},CLASS_NAME:"OpenLayers.Format.WMSDescribeLayer"});OpenLayers.Format.WMSGetFeatureInfo=OpenLayers.Class(OpenLayers.Format.XML,{layerIdentifier:'_layer',featureIdentifier:'_feature',regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},gmlFormat:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,arguments);OpenLayers.Util.extend(this,options);this.options=options;},read:function(data){var result;if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var root=data.documentElement;if(root){var scope=this;var read=this["read_"+root.nodeName];if(read){result=read.call(this,root);}else{result=new OpenLayers.Format.GML((this.options?this.options:{})).read(data);}}else{result=data;}
return result;},read_msGMLOutput:function(data){var response=[];var layerNodes=this.getSiblingNodesByTagCriteria(data,this.layerIdentifier);if(layerNodes){for(var i=0,len=layerNodes.length;i<len;++i){var node=layerNodes[i];var layerName=node.nodeName;if(node.prefix){layerName=layerName.split(':')[1];}
var layerName=layerName.replace(this.layerIdentifier,'');var featureNodes=this.getSiblingNodesByTagCriteria(node,this.featureIdentifier);if(featureNodes){for(var j=0;j<featureNodes.length;j++){var featureNode=featureNodes[j];var geom=this.parseGeometry(featureNode);var attributes=this.parseAttributes(featureNode);var feature=new OpenLayers.Feature.Vector(geom,attributes,null);feature.type=layerName;response.push(feature);}}}}
return response;},read_FeatureInfoResponse:function(data){var response=[];var featureNodes=this.getElementsByTagNameNS(data,'*','FIELDS');for(var i=0,len=featureNodes.length;i<len;i++){var featureNode=featureNodes[i];var geom=null;var attributes={};for(var j=0,jlen=featureNode.attributes.length;j<jlen;j++){var attribute=featureNode.attributes[j];attributes[attribute.nodeName]=attribute.nodeValue;}
response.push(new OpenLayers.Feature.Vector(geom,attributes,null));}
return response;},getSiblingNodesByTagCriteria:function(node,criteria){var nodes=[];var children,tagName,n,matchNodes,child;if(node&&node.hasChildNodes()){children=node.childNodes;n=children.length;for(var k=0;k<n;k++){child=children[k];while(child&&child.nodeType!=1){child=child.nextSibling;k++;}
tagName=(child?child.nodeName:'');if(tagName.length>0&&tagName.indexOf(criteria)>-1){nodes.push(child);}else{matchNodes=this.getSiblingNodesByTagCriteria(child,criteria);if(matchNodes.length>0){(nodes.length==0)?nodes=matchNodes:nodes.push(matchNodes);}}}}
return nodes;},parseAttributes:function(node){var attributes={};if(node.nodeType==1){var children=node.childNodes;n=children.length;for(var i=0;i<n;++i){var child=children[i];if(child.nodeType==1){var grandchildren=child.childNodes;if(grandchildren.length==1){var grandchild=grandchildren[0];if(grandchild.nodeType==3||grandchild.nodeType==4){var name=(child.prefix)?child.nodeName.split(":")[1]:child.nodeName;var value=grandchild.nodeValue.replace(this.regExes.trimSpace,"");attributes[name]=value;}}}}}
return attributes;},parseGeometry:function(node){if(!this.gmlFormat){this.gmlFormat=new OpenLayers.Format.GML();}
var feature=this.gmlFormat.parseFeature(node);var geometry=null;if(feature&&feature.geometry){geometry=feature.geometry.clone();feature.destroy();}
return geometry;},CLASS_NAME:"OpenLayers.Format.WMSGetFeatureInfo"});OpenLayers.Handler.Click=OpenLayers.Class(OpenLayers.Handler,{delay:300,single:true,'double':false,pixelTolerance:0,stopSingle:false,stopDouble:false,timerId:null,down:null,rightclickTimerId:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);if(this.pixelTolerance!=null){this.mousedown=function(evt){this.down=evt.xy;return true;};}},mousedown:null,mouseup:function(evt){var propagate=true;if(this.checkModifiers(evt)&&this.control.handleRightClicks&&OpenLayers.Event.isRightClick(evt)){propagate=this.rightclick(evt);}
return propagate;},rightclick:function(evt){if(this.passesTolerance(evt)){if(this.rightclickTimerId!=null){this.clearTimer();this.callback('dblrightclick',[evt]);return!this.stopDouble;}else{var clickEvent=this['double']?OpenLayers.Util.extend({},evt):this.callback('rightclick',[evt]);var delayedRightCall=OpenLayers.Function.bind(this.delayedRightCall,this,clickEvent);this.rightclickTimerId=window.setTimeout(delayedRightCall,this.delay);}}
return!this.stopSingle;},delayedRightCall:function(evt){this.rightclickTimerId=null;if(evt){this.callback('rightclick',[evt]);}
return!this.stopSingle;},dblclick:function(evt){if(this.passesTolerance(evt)){if(this["double"]){this.callback('dblclick',[evt]);}
this.clearTimer();}
return!this.stopDouble;},click:function(evt){if(this.passesTolerance(evt)){if(this.timerId!=null){this.clearTimer();}else{var clickEvent=this.single?OpenLayers.Util.extend({},evt):null;this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,this,clickEvent),this.delay);}}
return!this.stopSingle;},passesTolerance:function(evt){var passes=true;if(this.pixelTolerance!=null&&this.down){var dpx=Math.sqrt(Math.pow(this.down.x-evt.xy.x,2)+
Math.pow(this.down.y-evt.xy.y,2));if(dpx>this.pixelTolerance){passes=false;}}
return passes;},clearTimer:function(){if(this.timerId!=null){window.clearTimeout(this.timerId);this.timerId=null;}
if(this.rightclickTimerId!=null){window.clearTimeout(this.rightclickTimerId);this.rightclickTimerId=null;}},delayedCall:function(evt){this.timerId=null;if(evt){this.callback('click',[evt]);}},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.clearTimer();this.down=null;deactivated=true;}
return deactivated;},CLASS_NAME:"OpenLayers.Handler.Click"});OpenLayers.Handler.Drag=OpenLayers.Class(OpenLayers.Handler,{started:false,stopDown:true,dragging:false,last:null,start:null,oldOnselectstart:null,interval:0,timeoutId:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);},down:function(evt){},move:function(evt){},up:function(evt){},out:function(evt){},mousedown:function(evt){var propagate=true;this.dragging=false;if(this.checkModifiers(evt)&&OpenLayers.Event.isLeftClick(evt)){this.started=true;this.start=evt.xy;this.last=evt.xy;OpenLayers.Element.addClass(this.map.viewPortDiv,"olDragDown");this.down(evt);this.callback("down",[evt.xy]);OpenLayers.Event.stop(evt);if(!this.oldOnselectstart){this.oldOnselectstart=(document.onselectstart)?document.onselectstart:function(){return true;};document.onselectstart=function(){return false;};}
propagate=!this.stopDown;}else{this.started=false;this.start=null;this.last=null;}
return propagate;},mousemove:function(evt){if(this.started&&!this.timeoutId&&(evt.xy.x!=this.last.x||evt.xy.y!=this.last.y)){if(this.interval>0){this.timeoutId=setTimeout(OpenLayers.Function.bind(this.removeTimeout,this),this.interval);}
this.dragging=true;this.move(evt);this.callback("move",[evt.xy]);if(!this.oldOnselectstart){this.oldOnselectstart=document.onselectstart;document.onselectstart=function(){return false;};}
this.last=this.evt.xy;}
return true;},removeTimeout:function(){this.timeoutId=null;},mouseup:function(evt){if(this.started){var dragged=(this.start!=this.last);this.started=false;this.dragging=false;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.up(evt);this.callback("up",[evt.xy]);if(dragged){this.callback("done",[evt.xy]);}
document.onselectstart=this.oldOnselectstart;}
return true;},mouseout:function(evt){if(this.started&&OpenLayers.Util.mouseLeft(evt,this.map.div)){var dragged=(this.start!=this.last);this.started=false;this.dragging=false;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.out(evt);this.callback("out",[]);if(dragged){this.callback("done",[evt.xy]);}
if(document.onselectstart){document.onselectstart=this.oldOnselectstart;}}
return true;},click:function(evt){return(this.start==this.last);},activate:function(){var activated=false;if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.dragging=false;activated=true;}
return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.started=false;this.dragging=false;this.start=null;this.last=null;deactivated=true;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");}
return deactivated;},CLASS_NAME:"OpenLayers.Handler.Drag"});OpenLayers.Handler.Feature=OpenLayers.Class(OpenLayers.Handler,{EVENTMAP:{'click':{'in':'click','out':'clickout'},'mousemove':{'in':'over','out':'out'},'dblclick':{'in':'dblclick','out':null},'mousedown':{'in':null,'out':null},'mouseup':{'in':null,'out':null}},feature:null,lastFeature:null,down:null,up:null,clickTolerance:4,geometryTypes:null,stopClick:true,stopDown:true,stopUp:false,initialize:function(control,layer,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,[control,callbacks,options]);this.layer=layer;},mousedown:function(evt){this.down=evt.xy;return this.handle(evt)?!this.stopDown:true;},mouseup:function(evt){this.up=evt.xy;return this.handle(evt)?!this.stopUp:true;},click:function(evt){return this.handle(evt)?!this.stopClick:true;},mousemove:function(evt){if(!this.callbacks['over']&&!this.callbacks['out']){return true;}
this.handle(evt);return true;},dblclick:function(evt){return!this.handle(evt);},geometryTypeMatches:function(feature){return this.geometryTypes==null||OpenLayers.Util.indexOf(this.geometryTypes,feature.geometry.CLASS_NAME)>-1;},handle:function(evt){if(this.feature&&!this.feature.layer){this.feature=null;}
var type=evt.type;var handled=false;var previouslyIn=!!(this.feature);var click=(type=="click"||type=="dblclick");this.feature=this.layer.getFeatureFromEvent(evt);if(this.feature&&!this.feature.layer){this.feature=null;}
if(this.lastFeature&&!this.lastFeature.layer){this.lastFeature=null;}
if(this.feature){var inNew=(this.feature!=this.lastFeature);if(this.geometryTypeMatches(this.feature)){if(previouslyIn&&inNew){if(this.lastFeature){this.triggerCallback(type,'out',[this.lastFeature]);}
this.triggerCallback(type,'in',[this.feature]);}else if(!previouslyIn||click){this.triggerCallback(type,'in',[this.feature]);}
this.lastFeature=this.feature;handled=true;}else{if(this.lastFeature&&(previouslyIn&&inNew||click)){this.triggerCallback(type,'out',[this.lastFeature]);}
this.feature=null;}}else{if(this.lastFeature&&(previouslyIn||click)){this.triggerCallback(type,'out',[this.lastFeature]);}}
return handled;},triggerCallback:function(type,mode,args){var key=this.EVENTMAP[type][mode];if(key){if(type=='click'&&this.up&&this.down){var dpx=Math.sqrt(Math.pow(this.up.x-this.down.x,2)+
Math.pow(this.up.y-this.down.y,2));if(dpx<=this.clickTolerance){this.callback(key,args);}}else{this.callback(key,args);}}},activate:function(){var activated=false;if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.moveLayerToTop();this.map.events.on({"removelayer":this.handleMapEvents,"changelayer":this.handleMapEvents,scope:this});activated=true;}
return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.moveLayerBack();this.feature=null;this.lastFeature=null;this.down=null;this.up=null;this.map.events.un({"removelayer":this.handleMapEvents,"changelayer":this.handleMapEvents,scope:this});deactivated=true;}
return deactivated;},handleMapEvents:function(evt){if(!evt.property||evt.property=="order"){this.moveLayerToTop();}},moveLayerToTop:function(){var index=Math.max(this.map.Z_INDEX_BASE['Feature']-1,this.layer.getZIndex())+1;this.layer.setZIndex(index);},moveLayerBack:function(){var index=this.layer.getZIndex()-1;if(index>=this.map.Z_INDEX_BASE['Feature']){this.layer.setZIndex(index);}else{this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer));}},CLASS_NAME:"OpenLayers.Handler.Feature"});OpenLayers.Handler.Hover=OpenLayers.Class(OpenLayers.Handler,{delay:500,pixelTolerance:null,stopMove:false,px:null,timerId:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);},mousemove:function(evt){if(this.passesTolerance(evt.xy)){this.clearTimer();this.callback('move',[evt]);this.px=evt.xy;evt=OpenLayers.Util.extend({},evt);this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,this,evt),this.delay);}
return!this.stopMove;},mouseout:function(evt){if(OpenLayers.Util.mouseLeft(evt,this.map.div)){this.clearTimer();this.callback('move',[evt]);}
return true;},passesTolerance:function(px){var passes=true;if(this.pixelTolerance&&this.px){var dpx=Math.sqrt(Math.pow(this.px.x-px.x,2)+
Math.pow(this.px.y-px.y,2));if(dpx<this.pixelTolerance){passes=false;}}
return passes;},clearTimer:function(){if(this.timerId!=null){window.clearTimeout(this.timerId);this.timerId=null;}},delayedCall:function(evt){this.callback('pause',[evt]);},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.clearTimer();deactivated=true;}
return deactivated;},CLASS_NAME:"OpenLayers.Handler.Hover"});OpenLayers.Handler.Keyboard=OpenLayers.Class(OpenLayers.Handler,{KEY_EVENTS:["keydown","keyup"],eventListener:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.eventListener=OpenLayers.Function.bindAsEventListener(this.handleKeyEvent,this);},destroy:function(){this.deactivate();this.eventListener=null;OpenLayers.Handler.prototype.destroy.apply(this,arguments);},activate:function(){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){for(var i=0,len=this.KEY_EVENTS.length;i<len;i++){OpenLayers.Event.observe(document,this.KEY_EVENTS[i],this.eventListener);}
return true;}else{return false;}},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){for(var i=0,len=this.KEY_EVENTS.length;i<len;i++){OpenLayers.Event.stopObserving(document,this.KEY_EVENTS[i],this.eventListener);}
deactivated=true;}
return deactivated;},handleKeyEvent:function(evt){if(this.checkModifiers(evt)){this.callback(evt.type,[evt]);}},CLASS_NAME:"OpenLayers.Handler.Keyboard"});OpenLayers.Handler.MouseWheel=OpenLayers.Class(OpenLayers.Handler,{wheelListener:null,mousePosition:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.wheelListener=OpenLayers.Function.bindAsEventListener(this.onWheelEvent,this);},destroy:function(){OpenLayers.Handler.prototype.destroy.apply(this,arguments);this.wheelListener=null;},onWheelEvent:function(e){if(!this.map||!this.checkModifiers(e)){return;}
var overScrollableDiv=false;var overLayerDiv=false;var overMapDiv=false;var elem=OpenLayers.Event.element(e);while((elem!=null)&&!overMapDiv&&!overScrollableDiv){if(!overScrollableDiv){try{if(elem.currentStyle){overflow=elem.currentStyle["overflow"];}else{var style=document.defaultView.getComputedStyle(elem,null);var overflow=style.getPropertyValue("overflow");}
overScrollableDiv=(overflow&&(overflow=="auto")||(overflow=="scroll"));}catch(err){}}
if(!overLayerDiv){for(var i=0,len=this.map.layers.length;i<len;i++){if(elem==this.map.layers[i].div||elem==this.map.layers[i].pane){overLayerDiv=true;break;}}}
overMapDiv=(elem==this.map.div);elem=elem.parentNode;}
if(!overScrollableDiv&&overMapDiv){if(overLayerDiv){this.wheelZoom(e);}
OpenLayers.Event.stop(e);}},wheelZoom:function(e){var delta=0;if(!e){e=window.event;}
if(e.wheelDelta){delta=e.wheelDelta/120;if(window.opera&&window.opera.version()<9.2){delta=-delta;}}else if(e.detail){delta=-e.detail/3;}
if(delta){if(this.mousePosition){e.xy=this.mousePosition;}
if(!e.xy){e.xy=this.map.getPixelFromLonLat(this.map.getCenter());}
if(delta<0){this.callback("down",[e,delta]);}else{this.callback("up",[e,delta]);}}},mousemove:function(evt){this.mousePosition=evt.xy;},activate:function(evt){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var wheelListener=this.wheelListener;OpenLayers.Event.observe(window,"DOMMouseScroll",wheelListener);OpenLayers.Event.observe(window,"mousewheel",wheelListener);OpenLayers.Event.observe(document,"mousewheel",wheelListener);return true;}else{return false;}},deactivate:function(evt){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){var wheelListener=this.wheelListener;OpenLayers.Event.stopObserving(window,"DOMMouseScroll",wheelListener);OpenLayers.Event.stopObserving(window,"mousewheel",wheelListener);OpenLayers.Event.stopObserving(document,"mousewheel",wheelListener);return true;}else{return false;}},CLASS_NAME:"OpenLayers.Handler.MouseWheel"});OpenLayers.Layer=OpenLayers.Class({id:null,name:null,div:null,opacity:null,alwaysInRange:null,EVENT_TYPES:["loadstart","loadend","loadcancel","visibilitychanged","move","moveend"],events:null,map:null,isBaseLayer:false,alpha:false,displayInLayerSwitcher:true,visibility:true,attribution:null,inRange:false,imageSize:null,imageOffset:null,options:null,eventListeners:null,gutter:0,projection:null,units:null,scales:null,resolutions:null,maxExtent:null,minExtent:null,maxResolution:null,minResolution:null,numZoomLevels:null,minScale:null,maxScale:null,displayOutsideMaxExtent:false,wrapDateLine:false,transitionEffect:null,SUPPORTED_TRANSITIONS:['resize'],initialize:function(name,options){this.addOptions(options);this.name=name;if(this.id==null){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");this.div=OpenLayers.Util.createDiv(this.id);this.div.style.width="100%";this.div.style.height="100%";this.div.dir="ltr";this.events=new OpenLayers.Events(this,this.div,this.EVENT_TYPES);if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}}
if(this.wrapDateLine){this.displayOutsideMaxExtent=true;}},destroy:function(setNewBaseLayer){if(setNewBaseLayer==null){setNewBaseLayer=true;}
if(this.map!=null){this.map.removeLayer(this,setNewBaseLayer);}
this.projection=null;this.map=null;this.name=null;this.div=null;this.options=null;if(this.events){if(this.eventListeners){this.events.un(this.eventListeners);}
this.events.destroy();}
this.eventListeners=null;this.events=null;},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer(this.name,this.options);}
OpenLayers.Util.applyDefaults(obj,this);obj.map=null;return obj;},setName:function(newName){if(newName!=this.name){this.name=newName;if(this.map!=null){this.map.events.triggerEvent("changelayer",{layer:this,property:"name"});}}},addOptions:function(newOptions){if(this.options==null){this.options={};}
OpenLayers.Util.extend(this.options,newOptions);OpenLayers.Util.extend(this,newOptions);},onMapResize:function(){},redraw:function(){var redrawn=false;if(this.map){this.inRange=this.calculateInRange();var extent=this.getExtent();if(extent&&this.inRange&&this.visibility){var zoomChanged=true;this.moveTo(extent,zoomChanged,false);this.events.triggerEvent("moveend",{"zoomChanged":zoomChanged});redrawn=true;}}
return redrawn;},moveTo:function(bounds,zoomChanged,dragging){var display=this.visibility;if(!this.isBaseLayer){display=display&&this.inRange;}
this.display(display);},setMap:function(map){if(this.map==null){this.map=map;this.maxExtent=this.maxExtent||this.map.maxExtent;this.projection=this.projection||this.map.projection;if(this.projection&&typeof this.projection=="string"){this.projection=new OpenLayers.Projection(this.projection);}
this.units=this.projection.getUnits()||this.units||this.map.units;this.initResolutions();if(!this.isBaseLayer){this.inRange=this.calculateInRange();var show=((this.visibility)&&(this.inRange));this.div.style.display=show?"":"none";}
this.setTileSize();}},afterAdd:function(){},removeMap:function(map){},getImageSize:function(){return(this.imageSize||this.tileSize);},setTileSize:function(size){var tileSize=(size)?size:((this.tileSize)?this.tileSize:this.map.getTileSize());this.tileSize=tileSize;if(this.gutter){this.imageOffset=new OpenLayers.Pixel(-this.gutter,-this.gutter);this.imageSize=new OpenLayers.Size(tileSize.w+(2*this.gutter),tileSize.h+(2*this.gutter));}},getVisibility:function(){return this.visibility;},setVisibility:function(visibility){if(visibility!=this.visibility){this.visibility=visibility;this.display(visibility);this.redraw();if(this.map!=null){this.map.events.triggerEvent("changelayer",{layer:this,property:"visibility"});}
this.events.triggerEvent("visibilitychanged");}},display:function(display){var inRange=this.calculateInRange();if(display!=(this.div.style.display!="none")){this.div.style.display=(display&&inRange)?"block":"none";}},calculateInRange:function(){var inRange=false;if(this.alwaysInRange){inRange=true;}else{if(this.map){var resolution=this.map.getResolution();inRange=((resolution>=this.minResolution)&&(resolution<=this.maxResolution));}}
return inRange;},setIsBaseLayer:function(isBaseLayer){if(isBaseLayer!=this.isBaseLayer){this.isBaseLayer=isBaseLayer;if(this.map!=null){this.map.events.triggerEvent("changebaselayer",{layer:this});}}},initResolutions:function(){var props=new Array('projection','units','scales','resolutions','maxScale','minScale','maxResolution','minResolution','minExtent','maxExtent','numZoomLevels','maxZoomLevel');var notScaleProps=['projection','units'];var useInRange=false;var confProps={};for(var i=0,len=props.length;i<len;i++){var property=props[i];if(this.options[property]&&OpenLayers.Util.indexOf(notScaleProps,property)==-1){useInRange=true;}
confProps[property]=this.options[property]||this.map[property];}
if(this.alwaysInRange==null){this.alwaysInRange=!useInRange;}
if((this.options.minScale!=null||this.options.maxScale!=null)&&this.options.scales==null){confProps.scales=null;}
if((this.options.minResolution!=null||this.options.maxResolution!=null)&&this.options.resolutions==null){confProps.resolutions=null;}
if((!confProps.numZoomLevels)&&(confProps.maxZoomLevel)){confProps.numZoomLevels=confProps.maxZoomLevel+1;}
if((confProps.scales!=null)||(confProps.resolutions!=null)){if(confProps.scales!=null){confProps.resolutions=[];for(var i=0,len=confProps.scales.length;i<len;i++){var scale=confProps.scales[i];confProps.resolutions[i]=OpenLayers.Util.getResolutionFromScale(scale,confProps.units);}}
confProps.numZoomLevels=confProps.resolutions.length;}else{if(confProps.minScale){confProps.maxResolution=OpenLayers.Util.getResolutionFromScale(confProps.minScale,confProps.units);}else if(confProps.maxResolution=="auto"){var viewSize=this.map.getSize();var wRes=confProps.maxExtent.getWidth()/viewSize.w;var hRes=confProps.maxExtent.getHeight()/viewSize.h;confProps.maxResolution=Math.max(wRes,hRes);}
if(confProps.maxScale!=null){confProps.minResolution=OpenLayers.Util.getResolutionFromScale(confProps.maxScale,confProps.units);}else if((confProps.minResolution=="auto")&&(confProps.minExtent!=null)){var viewSize=this.map.getSize();var wRes=confProps.minExtent.getWidth()/viewSize.w;var hRes=confProps.minExtent.getHeight()/viewSize.h;confProps.minResolution=Math.max(wRes,hRes);}
if(confProps.minResolution!=null&&this.options.numZoomLevels==undefined){var ratio=confProps.maxResolution/confProps.minResolution;confProps.numZoomLevels=Math.floor(Math.log(ratio)/Math.log(2))+1;}
confProps.resolutions=new Array(confProps.numZoomLevels);var base=2;if(typeof confProps.minResolution=="number"&&confProps.numZoomLevels>1){base=Math.pow((confProps.maxResolution/confProps.minResolution),(1/(confProps.numZoomLevels-1)));}
for(var i=0;i<confProps.numZoomLevels;i++){var res=confProps.maxResolution/Math.pow(base,i);confProps.resolutions[i]=res;}}
confProps.resolutions.sort(function(a,b){return(b-a);});this.resolutions=confProps.resolutions;this.maxResolution=confProps.resolutions[0];var lastIndex=confProps.resolutions.length-1;this.minResolution=confProps.resolutions[lastIndex];this.scales=[];for(var i=0,len=confProps.resolutions.length;i<len;i++){this.scales[i]=OpenLayers.Util.getScaleFromResolution(confProps.resolutions[i],confProps.units);}
this.minScale=this.scales[0];this.maxScale=this.scales[this.scales.length-1];this.numZoomLevels=confProps.numZoomLevels;},getResolution:function(){var zoom=this.map.getZoom();return this.getResolutionForZoom(zoom);},getExtent:function(){return this.map.calculateBounds();},getZoomForExtent:function(extent,closest){var viewSize=this.map.getSize();var idealResolution=Math.max(extent.getWidth()/viewSize.w,extent.getHeight()/viewSize.h);return this.getZoomForResolution(idealResolution,closest);},getDataExtent:function(){},getResolutionForZoom:function(zoom){zoom=Math.max(0,Math.min(zoom,this.resolutions.length-1));var resolution;if(this.map.fractionalZoom){var low=Math.floor(zoom);var high=Math.ceil(zoom);resolution=this.resolutions[low]-
((zoom-low)*(this.resolutions[low]-this.resolutions[high]));}else{resolution=this.resolutions[Math.round(zoom)];}
return resolution;},getZoomForResolution:function(resolution,closest){var zoom;if(this.map.fractionalZoom){var lowZoom=0;var highZoom=this.resolutions.length-1;var highRes=this.resolutions[lowZoom];var lowRes=this.resolutions[highZoom];var res;for(var i=0,len=this.resolutions.length;i<len;++i){res=this.resolutions[i];if(res>=resolution){highRes=res;lowZoom=i;}
if(res<=resolution){lowRes=res;highZoom=i;break;}}
var dRes=highRes-lowRes;if(dRes>0){zoom=lowZoom+((highRes-resolution)/dRes);}else{zoom=lowZoom;}}else{var diff;var minDiff=Number.POSITIVE_INFINITY;for(var i=0,len=this.resolutions.length;i<len;i++){if(closest){diff=Math.abs(this.resolutions[i]-resolution);if(diff>minDiff){break;}
minDiff=diff;}else{if(this.resolutions[i]<resolution){break;}}}
zoom=Math.max(0,i-1);}
return zoom;},getLonLatFromViewPortPx:function(viewPortPx){var lonlat=null;if(viewPortPx!=null){var size=this.map.getSize();var center=this.map.getCenter();if(center){var res=this.map.getResolution();var delta_x=viewPortPx.x-(size.w/2);var delta_y=viewPortPx.y-(size.h/2);lonlat=new OpenLayers.LonLat(center.lon+delta_x*res,center.lat-delta_y*res);if(this.wrapDateLine){lonlat=lonlat.wrapDateLine(this.maxExtent);}}}
return lonlat;},getViewPortPxFromLonLat:function(lonlat){var px=null;if(lonlat!=null){var resolution=this.map.getResolution();var extent=this.map.getExtent();px=new OpenLayers.Pixel((1/resolution*(lonlat.lon-extent.left)),(1/resolution*(extent.top-lonlat.lat)));}
return px;},setOpacity:function(opacity){if(opacity!=this.opacity){this.opacity=opacity;for(var i=0,len=this.div.childNodes.length;i<len;++i){var element=this.div.childNodes[i].firstChild;OpenLayers.Util.modifyDOMElement(element,null,null,null,null,null,null,opacity);}}},getZIndex:function(){return this.div.style.zIndex;},setZIndex:function(zIndex){this.div.style.zIndex=zIndex;},adjustBounds:function(bounds){if(this.gutter){var mapGutter=this.gutter*this.map.getResolution();bounds=new OpenLayers.Bounds(bounds.left-mapGutter,bounds.bottom-mapGutter,bounds.right+mapGutter,bounds.top+mapGutter);}
if(this.wrapDateLine){var wrappingOptions={'rightTolerance':this.getResolution()};bounds=bounds.wrapDateLine(this.maxExtent,wrappingOptions);}
return bounds;},CLASS_NAME:"OpenLayers.Layer"});OpenLayers.Marker.Box=OpenLayers.Class(OpenLayers.Marker,{bounds:null,div:null,initialize:function(bounds,borderColor,borderWidth){this.bounds=bounds;this.div=OpenLayers.Util.createDiv();this.div.style.overflow='hidden';this.events=new OpenLayers.Events(this,this.div,null);this.setBorder(borderColor,borderWidth);},destroy:function(){this.bounds=null;this.div=null;OpenLayers.Marker.prototype.destroy.apply(this,arguments);},setBorder:function(color,width){if(!color){color="red";}
if(!width){width=2;}
this.div.style.border=width+"px solid "+color;},draw:function(px,sz){OpenLayers.Util.modifyDOMElement(this.div,null,px,sz);return this.div;},onScreen:function(){var onScreen=false;if(this.map){var screenBounds=this.map.getExtent();onScreen=screenBounds.containsBounds(this.bounds,true,true);}
return onScreen;},display:function(display){this.div.style.display=(display)?"":"none";},CLASS_NAME:"OpenLayers.Marker.Box"});(function(){var oXMLHttpRequest=window.XMLHttpRequest;var bGecko=!!window.controllers,bIE=window.document.all&&!window.opera;function cXMLHttpRequest(){this._object=oXMLHttpRequest?new oXMLHttpRequest:new window.ActiveXObject('Microsoft.XMLHTTP');};if(bGecko&&oXMLHttpRequest.wrapped)
cXMLHttpRequest.wrapped=oXMLHttpRequest.wrapped;cXMLHttpRequest.UNSENT=0;cXMLHttpRequest.OPENED=1;cXMLHttpRequest.HEADERS_RECEIVED=2;cXMLHttpRequest.LOADING=3;cXMLHttpRequest.DONE=4;cXMLHttpRequest.prototype.readyState=cXMLHttpRequest.UNSENT;cXMLHttpRequest.prototype.responseText="";cXMLHttpRequest.prototype.responseXML=null;cXMLHttpRequest.prototype.status=0;cXMLHttpRequest.prototype.statusText="";cXMLHttpRequest.prototype.onreadystatechange=null;cXMLHttpRequest.onreadystatechange=null;cXMLHttpRequest.onopen=null;cXMLHttpRequest.onsend=null;cXMLHttpRequest.onabort=null;cXMLHttpRequest.prototype.open=function(sMethod,sUrl,bAsync,sUser,sPassword){this._async=bAsync;var oRequest=this,nState=this.readyState;if(bIE){var fOnUnload=function(){if(oRequest._object.readyState!=cXMLHttpRequest.DONE)
fCleanTransport(oRequest);};if(bAsync)
window.attachEvent("onunload",fOnUnload);}
this._object.onreadystatechange=function(){if(bGecko&&!bAsync)
return;oRequest.readyState=oRequest._object.readyState;fSynchronizeValues(oRequest);if(oRequest._aborted){oRequest.readyState=cXMLHttpRequest.UNSENT;return;}
if(oRequest.readyState==cXMLHttpRequest.DONE){fCleanTransport(oRequest);if(bIE&&bAsync)
window.detachEvent("onunload",fOnUnload);}
if(nState!=oRequest.readyState)
fReadyStateChange(oRequest);nState=oRequest.readyState;};if(cXMLHttpRequest.onopen)
cXMLHttpRequest.onopen.apply(this,arguments);this._object.open(sMethod,sUrl,bAsync,sUser,sPassword);if(!bAsync&&bGecko){this.readyState=cXMLHttpRequest.OPENED;fReadyStateChange(this);}};cXMLHttpRequest.prototype.send=function(vData){if(cXMLHttpRequest.onsend)
cXMLHttpRequest.onsend.apply(this,arguments);if(vData&&vData.nodeType){vData=window.XMLSerializer?new window.XMLSerializer().serializeToString(vData):vData.xml;if(!this._headers["Content-Type"])
this._object.setRequestHeader("Content-Type","application/xml");}
this._object.send(vData);if(bGecko&&!this._async){this.readyState=cXMLHttpRequest.OPENED;fSynchronizeValues(this);while(this.readyState<cXMLHttpRequest.DONE){this.readyState++;fReadyStateChange(this);if(this._aborted)
return;}}};cXMLHttpRequest.prototype.abort=function(){if(cXMLHttpRequest.onabort)
cXMLHttpRequest.onabort.apply(this,arguments);if(this.readyState>cXMLHttpRequest.UNSENT)
this._aborted=true;this._object.abort();fCleanTransport(this);};cXMLHttpRequest.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders();};cXMLHttpRequest.prototype.getResponseHeader=function(sName){return this._object.getResponseHeader(sName);};cXMLHttpRequest.prototype.setRequestHeader=function(sName,sValue){if(!this._headers)
this._headers={};this._headers[sName]=sValue;return this._object.setRequestHeader(sName,sValue);};cXMLHttpRequest.prototype.toString=function(){return'['+"object"+' '+"XMLHttpRequest"+']';};cXMLHttpRequest.toString=function(){return'['+"XMLHttpRequest"+']';};function fReadyStateChange(oRequest){if(oRequest.onreadystatechange)
oRequest.onreadystatechange.apply(oRequest);if(cXMLHttpRequest.onreadystatechange)
cXMLHttpRequest.onreadystatechange.apply(oRequest);};function fGetDocument(oRequest){var oDocument=oRequest.responseXML;if(bIE&&oDocument&&!oDocument.documentElement&&oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)){oDocument=new ActiveXObject('Microsoft.XMLDOM');oDocument.loadXML(oRequest.responseText);}
if(oDocument)
if((bIE&&oDocument.parseError!=0)||(oDocument.documentElement&&oDocument.documentElement.tagName=="parsererror"))
return null;return oDocument;};function fSynchronizeValues(oRequest){try{oRequest.responseText=oRequest._object.responseText;}catch(e){}
try{oRequest.responseXML=fGetDocument(oRequest._object);}catch(e){}
try{oRequest.status=oRequest._object.status;}catch(e){}
try{oRequest.statusText=oRequest._object.statusText;}catch(e){}};function fCleanTransport(oRequest){oRequest._object.onreadystatechange=new window.Function;delete oRequest._headers;};if(!window.Function.prototype.apply){window.Function.prototype.apply=function(oRequest,oArguments){if(!oArguments)
oArguments=[];oRequest.__func=this;oRequest.__func(oArguments[0],oArguments[1],oArguments[2],oArguments[3],oArguments[4]);delete oRequest.__func;};};OpenLayers.Request.XMLHttpRequest=cXMLHttpRequest;})();OpenLayers.ProxyHost="";OpenLayers.nullHandler=function(request){OpenLayers.Console.userError(OpenLayers.i18n("unhandledRequest",{'statusText':request.statusText}));};OpenLayers.loadURL=function(uri,params,caller,onComplete,onFailure){if(typeof params=='string'){params=OpenLayers.Util.getParameters(params);}
var success=(onComplete)?onComplete:OpenLayers.nullHandler;var failure=(onFailure)?onFailure:OpenLayers.nullHandler;return OpenLayers.Request.GET({url:uri,params:params,success:success,failure:failure,scope:caller});};OpenLayers.parseXMLString=function(text){var index=text.indexOf('<');if(index>0){text=text.substring(index);}
var ajaxResponse=OpenLayers.Util.Try(function(){var xmldom=new ActiveXObject('Microsoft.XMLDOM');xmldom.loadXML(text);return xmldom;},function(){return new DOMParser().parseFromString(text,'text/xml');},function(){var req=new XMLHttpRequest();req.open("GET","data:"+"text/xml"+";charset=utf-8,"+encodeURIComponent(text),false);if(req.overrideMimeType){req.overrideMimeType("text/xml");}
req.send(null);return req.responseXML;});return ajaxResponse;};OpenLayers.Ajax={emptyFunction:function(){},getTransport:function(){return OpenLayers.Util.Try(function(){return new XMLHttpRequest();},function(){return new ActiveXObject('Msxml2.XMLHTTP');},function(){return new ActiveXObject('Microsoft.XMLHTTP');})||false;},activeRequestCount:0};OpenLayers.Ajax.Responders={responders:[],register:function(responderToAdd){for(var i=0;i<this.responders.length;i++){if(responderToAdd==this.responders[i]){return;}}
this.responders.push(responderToAdd);},unregister:function(responderToRemove){OpenLayers.Util.removeItem(this.reponders,responderToRemove);},dispatch:function(callback,request,transport){var responder;for(var i=0;i<this.responders.length;i++){responder=this.responders[i];if(responder[callback]&&typeof responder[callback]=='function'){try{responder[callback].apply(responder,[request,transport]);}catch(e){}}}}};OpenLayers.Ajax.Responders.register({onCreate:function(){OpenLayers.Ajax.activeRequestCount++;},onComplete:function(){OpenLayers.Ajax.activeRequestCount--;}});OpenLayers.Ajax.Base=OpenLayers.Class({initialize:function(options){this.options={method:'post',asynchronous:true,contentType:'application/xml',parameters:''};OpenLayers.Util.extend(this.options,options||{});this.options.method=this.options.method.toLowerCase();if(typeof this.options.parameters=='string'){this.options.parameters=OpenLayers.Util.getParameters(this.options.parameters);}}});OpenLayers.Ajax.Request=OpenLayers.Class(OpenLayers.Ajax.Base,{_complete:false,initialize:function(url,options){OpenLayers.Ajax.Base.prototype.initialize.apply(this,[options]);if(OpenLayers.ProxyHost&&OpenLayers.String.startsWith(url,"http")){url=OpenLayers.ProxyHost+encodeURIComponent(url);}
this.transport=OpenLayers.Ajax.getTransport();this.request(url);},request:function(url){this.url=url;this.method=this.options.method;var params=OpenLayers.Util.extend({},this.options.parameters);if(this.method!='get'&&this.method!='post'){params['_method']=this.method;this.method='post';}
this.parameters=params;if(params=OpenLayers.Util.getParameterString(params)){if(this.method=='get'){this.url+=((this.url.indexOf('?')>-1)?'&':'?')+params;}else if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)){params+='&_=';}}
try{var response=new OpenLayers.Ajax.Response(this);if(this.options.onCreate){this.options.onCreate(response);}
OpenLayers.Ajax.Responders.dispatch('onCreate',this,response);this.transport.open(this.method.toUpperCase(),this.url,this.options.asynchronous);if(this.options.asynchronous){window.setTimeout(OpenLayers.Function.bind(this.respondToReadyState,this,1),10);}
this.transport.onreadystatechange=OpenLayers.Function.bind(this.onStateChange,this);this.setRequestHeaders();this.body=this.method=='post'?(this.options.postBody||params):null;this.transport.send(this.body);if(!this.options.asynchronous&&this.transport.overrideMimeType){this.onStateChange();}}catch(e){this.dispatchException(e);}},onStateChange:function(){var readyState=this.transport.readyState;if(readyState>1&&!((readyState==4)&&this._complete)){this.respondToReadyState(this.transport.readyState);}},setRequestHeaders:function(){var headers={'X-Requested-With':'XMLHttpRequest','Accept':'text/javascript, text/html, application/xml, text/xml, */*','OpenLayers':true};if(this.method=='post'){headers['Content-type']=this.options.contentType+
(this.options.encoding?'; charset='+this.options.encoding:'');if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005){headers['Connection']='close';}}
if(typeof this.options.requestHeaders=='object'){var extras=this.options.requestHeaders;if(typeof extras.push=='function'){for(var i=0,length=extras.length;i<length;i+=2){headers[extras[i]]=extras[i+1];}}else{for(var i in extras){headers[i]=extras[i];}}}
for(var name in headers){this.transport.setRequestHeader(name,headers[name]);}},success:function(){var status=this.getStatus();return!status||(status>=200&&status<300);},getStatus:function(){try{return this.transport.status||0;}catch(e){return 0;}},respondToReadyState:function(readyState){var state=OpenLayers.Ajax.Request.Events[readyState];var response=new OpenLayers.Ajax.Response(this);if(state=='Complete'){try{this._complete=true;(this.options['on'+response.status]||this.options['on'+(this.success()?'Success':'Failure')]||OpenLayers.Ajax.emptyFunction)(response);}catch(e){this.dispatchException(e);}
var contentType=response.getHeader('Content-type');}
try{(this.options['on'+state]||OpenLayers.Ajax.emptyFunction)(response);OpenLayers.Ajax.Responders.dispatch('on'+state,this,response);}catch(e){this.dispatchException(e);}
if(state=='Complete'){this.transport.onreadystatechange=OpenLayers.Ajax.emptyFunction;}},getHeader:function(name){try{return this.transport.getResponseHeader(name);}catch(e){return null;}},dispatchException:function(exception){var handler=this.options.onException;if(handler){handler(this,exception);OpenLayers.Ajax.Responders.dispatch('onException',this,exception);}else{var listener=false;var responders=OpenLayers.Ajax.Responders.responders;for(var i=0;i<responders.length;i++){if(responders[i].onException){listener=true;break;}}
if(listener){OpenLayers.Ajax.Responders.dispatch('onException',this,exception);}else{throw exception;}}}});OpenLayers.Ajax.Request.Events=['Uninitialized','Loading','Loaded','Interactive','Complete'];OpenLayers.Ajax.Response=OpenLayers.Class({status:0,statusText:'',initialize:function(request){this.request=request;var transport=this.transport=request.transport,readyState=this.readyState=transport.readyState;if((readyState>2&&!(!!(window.attachEvent&&!window.opera)))||readyState==4){this.status=this.getStatus();this.statusText=this.getStatusText();this.responseText=transport.responseText==null?'':String(transport.responseText);}
if(readyState==4){var xml=transport.responseXML;this.responseXML=xml===undefined?null:xml;}},getStatus:OpenLayers.Ajax.Request.prototype.getStatus,getStatusText:function(){try{return this.transport.statusText||'';}catch(e){return'';}},getHeader:OpenLayers.Ajax.Request.prototype.getHeader,getResponseHeader:function(name){return this.transport.getResponseHeader(name);}});OpenLayers.Ajax.getElementsByTagNameNS=function(parentnode,nsuri,nsprefix,tagname){var elem=null;if(parentnode.getElementsByTagNameNS){elem=parentnode.getElementsByTagNameNS(nsuri,tagname);}else{elem=parentnode.getElementsByTagName(nsprefix+':'+tagname);}
return elem;};OpenLayers.Ajax.serializeXMLToString=function(xmldom){var serializer=new XMLSerializer();var data=serializer.serializeToString(xmldom);return data;};OpenLayers.Control.DragFeature=OpenLayers.Class(OpenLayers.Control,{geometryTypes:null,onStart:function(feature,pixel){},onDrag:function(feature,pixel){},onComplete:function(feature,pixel){},layer:null,feature:null,dragCallbacks:{},featureCallbacks:{},lastPixel:null,initialize:function(layer,options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.layer=layer;this.handlers={drag:new OpenLayers.Handler.Drag(this,OpenLayers.Util.extend({down:this.downFeature,move:this.moveFeature,up:this.upFeature,out:this.cancel,done:this.doneDragging},this.dragCallbacks)),feature:new OpenLayers.Handler.Feature(this,this.layer,OpenLayers.Util.extend({over:this.overFeature,out:this.outFeature},this.featureCallbacks),{geometryTypes:this.geometryTypes})};},destroy:function(){this.layer=null;OpenLayers.Control.prototype.destroy.apply(this,[]);},activate:function(){return(this.handlers.feature.activate()&&OpenLayers.Control.prototype.activate.apply(this,arguments));},deactivate:function(){this.handlers.drag.deactivate();this.handlers.feature.deactivate();this.feature=null;this.dragging=false;this.lastPixel=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass+"Over");return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},overFeature:function(feature){if(!this.handlers.drag.dragging){this.feature=feature;this.handlers.drag.activate();this.over=true;OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass+"Over");}else{if(this.feature.id==feature.id){this.over=true;}else{this.over=false;}}},downFeature:function(pixel){this.lastPixel=pixel;this.onStart(this.feature,pixel);},moveFeature:function(pixel){var res=this.map.getResolution();this.feature.geometry.move(res*(pixel.x-this.lastPixel.x),res*(this.lastPixel.y-pixel.y));this.layer.drawFeature(this.feature);this.lastPixel=pixel;this.onDrag(this.feature,pixel);},upFeature:function(pixel){if(!this.over){this.handlers.drag.deactivate();}},doneDragging:function(pixel){this.onComplete(this.feature,pixel);},outFeature:function(feature){if(!this.handlers.drag.dragging){this.over=false;this.handlers.drag.deactivate();OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass+"Over");this.feature=null;}else{if(this.feature.id==feature.id){this.over=false;}}},cancel:function(){this.handlers.drag.deactivate();this.over=false;},setMap:function(map){this.handlers.drag.setMap(map);this.handlers.feature.setMap(map);OpenLayers.Control.prototype.setMap.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.DragFeature"});OpenLayers.Control.DragPan=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,panned:false,interval:25,draw:function(){this.handler=new OpenLayers.Handler.Drag(this,{"move":this.panMap,"done":this.panMapDone},{interval:this.interval});},panMap:function(xy){this.panned=true;this.map.pan(this.handler.last.x-xy.x,this.handler.last.y-xy.y,{dragging:this.handler.dragging,animate:false});},panMapDone:function(xy){if(this.panned){this.panMap(xy);this.panned=false;}},CLASS_NAME:"OpenLayers.Control.DragPan"});OpenLayers.Control.KeyboardDefaults=OpenLayers.Class(OpenLayers.Control,{slideFactor:75,initialize:function(){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){if(this.handler){this.handler.destroy();}
this.handler=null;OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){this.handler=new OpenLayers.Handler.Keyboard(this,{"keydown":this.defaultKeyPress});this.activate();},defaultKeyPress:function(evt){switch(evt.keyCode){case OpenLayers.Event.KEY_LEFT:this.map.pan(-this.slideFactor,0);break;case OpenLayers.Event.KEY_RIGHT:this.map.pan(this.slideFactor,0);break;case OpenLayers.Event.KEY_UP:this.map.pan(0,-this.slideFactor);break;case OpenLayers.Event.KEY_DOWN:this.map.pan(0,this.slideFactor);break;case 33:var size=this.map.getSize();this.map.pan(0,-0.75*size.h);break;case 34:var size=this.map.getSize();this.map.pan(0,0.75*size.h);break;case 35:var size=this.map.getSize();this.map.pan(0.75*size.w,0);break;case 36:var size=this.map.getSize();this.map.pan(-0.75*size.w,0);break;case 43:case 61:case 187:case 107:this.map.zoomIn();break;case 45:case 109:case 189:case 95:this.map.zoomOut();break;}},CLASS_NAME:"OpenLayers.Control.KeyboardDefaults"});OpenLayers.Control.WMSGetFeatureInfo=OpenLayers.Class(OpenLayers.Control,{hover:false,maxFeatures:10,layers:null,queryVisible:false,url:null,layerUrls:null,infoFormat:'text/html',vendorParams:{},format:null,formatOptions:null,handlerOptions:null,handler:null,hoverRequest:null,EVENT_TYPES:["getfeatureinfo"],initialize:function(options){this.EVENT_TYPES=OpenLayers.Control.WMSGetFeatureInfo.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);options=options||{};options.handlerOptions=options.handlerOptions||{};OpenLayers.Control.prototype.initialize.apply(this,[options]);if(!this.format){this.format=new OpenLayers.Format.WMSGetFeatureInfo(options.formatOptions);}
if(this.hover){this.handler=new OpenLayers.Handler.Hover(this,{'move':this.cancelHover,'pause':this.getInfoForHover},OpenLayers.Util.extend(this.handlerOptions.hover||{},{'delay':250}));}else{this.handler=new OpenLayers.Handler.Click(this,{click:this.getInfoForClick},this.handlerOptions.click||{});}},activate:function(){if(!this.active){this.handler.activate();}
return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},getInfoForClick:function(evt){OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");this.request(evt.xy,{});},getInfoForHover:function(evt){this.request(evt.xy,{hover:true});},cancelHover:function(){if(this.hoverRequest){this.hoverRequest.abort();this.hoverRequest=null;}},findLayers:function(){var layers=[];var candidates=this.layers||this.map.layers;var layer,url;for(var i=0,len=candidates.length;i<len;++i){layer=candidates[i];if(layer instanceof OpenLayers.Layer.WMS&&(!this.queryVisible||layer.getVisibility())){url=layer.url instanceof Array?layer.url[0]:layer.url;if(!this.url){this.url=url;}
if(this.urlMatches(url)){layers.push(layer);}}}
return layers;},urlMatches:function(url){var matches=OpenLayers.Util.isEquivalentUrl(this.url,url);if(!matches&&this.layerUrls){for(var i=0,len=this.layerUrls.length;i<len;++i){if(OpenLayers.Util.isEquivalentUrl(this.layerUrls[i],url)){matches=true;break;}}}
return matches;},request:function(clickPosition,options){options=options||{};var layerNames=[];var styleNames=[];var layers=this.findLayers();if(layers.length>0){for(var i=0,len=layers.length;i<len;i++){layerNames=layerNames.concat(layers[i].params.LAYERS);if(layers[i].params.STYLES){styleNames=styleNames.concat(layers[i].params.STYLES);}else{if(layers[i].params.LAYERS instanceof Array){styleNames=styleNames.concat(new Array(layers[i].params.LAYERS.length));}else{styleNames=styleNames.concat(layers[i].params.LAYERS.replace(/[^,]/g,""));}}}
var wmsOptions={url:this.url,params:OpenLayers.Util.applyDefaults({service:"WMS",version:"1.1.0",request:"GetFeatureInfo",layers:layerNames,query_layers:layerNames,styles:styleNames,bbox:this.map.getExtent().toBBOX(),srs:this.map.getProjection(),feature_count:this.maxFeatures,x:clickPosition.x,y:clickPosition.y,height:this.map.getSize().h,width:this.map.getSize().w,info_format:this.infoFormat},this.vendorParams),callback:function(request){this.handleResponse(clickPosition,request);},scope:this};var response=OpenLayers.Request.GET(wmsOptions);if(options.hover===true){this.hoverRequest=response.priv;}}else{OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");}},handleResponse:function(xy,request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
var features=this.format.read(doc);this.events.triggerEvent("getfeatureinfo",{text:request.responseText,features:features,request:request,xy:xy});OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");},setMap:function(map){this.handler.setMap(map);OpenLayers.Control.prototype.setMap.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.WMSGetFeatureInfo"});OpenLayers.State={UNKNOWN:'Unknown',INSERT:'Insert',UPDATE:'Update',DELETE:'Delete'};OpenLayers.Feature.Vector=OpenLayers.Class(OpenLayers.Feature,{fid:null,geometry:null,attributes:null,bounds:null,state:null,style:null,renderIntent:"default",initialize:function(geometry,attributes,style){OpenLayers.Feature.prototype.initialize.apply(this,[null,null,attributes]);this.lonlat=null;this.geometry=geometry?geometry:null;this.state=null;this.attributes={};if(attributes){this.attributes=OpenLayers.Util.extend(this.attributes,attributes);}
this.style=style?style:null;},destroy:function(){if(this.layer){this.layer.removeFeatures(this);this.layer=null;}
this.geometry=null;OpenLayers.Feature.prototype.destroy.apply(this,arguments);},clone:function(){return new OpenLayers.Feature.Vector(this.geometry?this.geometry.clone():null,this.attributes,this.style);},onScreen:function(boundsOnly){var onScreen=false;if(this.layer&&this.layer.map){var screenBounds=this.layer.map.getExtent();if(boundsOnly){var featureBounds=this.geometry.getBounds();onScreen=screenBounds.intersectsBounds(featureBounds);}else{var screenPoly=screenBounds.toGeometry();onScreen=screenPoly.intersects(this.geometry);}}
return onScreen;},createMarker:function(){return null;},destroyMarker:function(){},createPopup:function(){return null;},atPoint:function(lonlat,toleranceLon,toleranceLat){var atPoint=false;if(this.geometry){atPoint=this.geometry.atPoint(lonlat,toleranceLon,toleranceLat);}
return atPoint;},destroyPopup:function(){},move:function(location){if(!this.layer||!this.geometry.move){return;}
var pixel;if(location.CLASS_NAME=="OpenLayers.LonLat"){pixel=this.layer.getViewPortPxFromLonLat(location);}else{pixel=location;}
var lastPixel=this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());var res=this.layer.map.getResolution();this.geometry.move(res*(pixel.x-lastPixel.x),res*(lastPixel.y-pixel.y));this.layer.drawFeature(this);return lastPixel;},toState:function(state){if(state==OpenLayers.State.UPDATE){switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.DELETE:this.state=state;break;case OpenLayers.State.UPDATE:case OpenLayers.State.INSERT:break;}}else if(state==OpenLayers.State.INSERT){switch(this.state){case OpenLayers.State.UNKNOWN:break;default:this.state=state;break;}}else if(state==OpenLayers.State.DELETE){switch(this.state){case OpenLayers.State.INSERT:break;case OpenLayers.State.DELETE:break;case OpenLayers.State.UNKNOWN:case OpenLayers.State.UPDATE:this.state=state;break;}}else if(state==OpenLayers.State.UNKNOWN){this.state=state;}},CLASS_NAME:"OpenLayers.Feature.Vector"});OpenLayers.Feature.Vector.style={'default':{fillColor:"#ee9900",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#ee9900",strokeOpacity:1,strokeWidth:1,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit"},'select':{fillColor:"blue",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"blue",strokeOpacity:1,strokeWidth:2,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"pointer"},'temporary':{fillColor:"#66cccc",fillOpacity:0.2,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#66cccc",strokeOpacity:1,strokeLinecap:"round",strokeWidth:2,strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit"},'delete':{display:"none"}};OpenLayers.Feature.WFS=OpenLayers.Class(OpenLayers.Feature,{initialize:function(layer,xmlNode){var newArguments=arguments;var data=this.processXMLNode(xmlNode);newArguments=new Array(layer,data.lonlat,data);OpenLayers.Feature.prototype.initialize.apply(this,newArguments);this.createMarker();this.layer.addMarker(this.marker);},destroy:function(){if(this.marker!=null){this.layer.removeMarker(this.marker);}
OpenLayers.Feature.prototype.destroy.apply(this,arguments);},processXMLNode:function(xmlNode){var point=OpenLayers.Ajax.getElementsByTagNameNS(xmlNode,"http://www.opengis.net/gml","gml","Point");var text=OpenLayers.Util.getXmlNodeValue(OpenLayers.Ajax.getElementsByTagNameNS(point[0],"http://www.opengis.net/gml","gml","coordinates")[0]);var floats=text.split(",");return{lonlat:new OpenLayers.LonLat(parseFloat(floats[0]),parseFloat(floats[1])),id:null};},CLASS_NAME:"OpenLayers.Feature.WFS"});OpenLayers.Format.WFSCapabilities.v1=OpenLayers.Class(OpenLayers.Format.WFSCapabilities,{initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var capabilities={};var root=data.documentElement;this.runChildNodes(capabilities,root);return capabilities;},runChildNodes:function(obj,node){var children=node.childNodes;var childNode,processor;for(var i=0;i<children.length;++i){childNode=children[i];if(childNode.nodeType==1){processor=this["read_cap_"+childNode.nodeName];if(processor){processor.apply(this,[obj,childNode]);}}}},read_cap_FeatureTypeList:function(request,node){var featureTypeList={featureTypes:[]};this.runChildNodes(featureTypeList,node);request.featureTypeList=featureTypeList;},read_cap_FeatureType:function(featureTypeList,node,parentLayer){var featureType={};this.runChildNodes(featureType,node);featureTypeList.featureTypes.push(featureType);},read_cap_Name:function(obj,node){var name=this.getChildValue(node);if(name){obj.name=name;}},read_cap_Title:function(obj,node){var title=this.getChildValue(node);if(title){obj.title=title;}},read_cap_Abstract:function(obj,node){var abst=this.getChildValue(node);if(abst){obj["abstract"]=abst;}},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1"});OpenLayers.Format.WMC.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ol:"http://openlayers.org/context",wmc:"http://www.opengis.net/context",sld:"http://www.opengis.net/sld",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},schemaLocation:"",getNamespacePrefix:function(uri){var prefix=null;if(uri==null){prefix=this.namespaces[this.defaultPrefix];}else{for(prefix in this.namespaces){if(this.namespaces[prefix]==uri){break;}}}
return prefix;},defaultPrefix:"wmc",rootPrefix:null,defaultStyleName:"",defaultStyleTitle:"Default",initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var root=data.documentElement;this.rootPrefix=root.prefix;var context={version:root.getAttribute("version")};this.runChildNodes(context,root);return context;},runChildNodes:function(obj,node){var children=node.childNodes;var childNode,processor,prefix,local;for(var i=0,len=children.length;i<len;++i){childNode=children[i];if(childNode.nodeType==1){prefix=this.getNamespacePrefix(childNode.namespaceURI);local=childNode.nodeName.split(":").pop();processor=this["read_"+prefix+"_"+local];if(processor){processor.apply(this,[obj,childNode]);}}}},read_wmc_General:function(context,node){this.runChildNodes(context,node);},read_wmc_BoundingBox:function(context,node){context.projection=node.getAttribute("SRS");context.bounds=new OpenLayers.Bounds(parseFloat(node.getAttribute("minx")),parseFloat(node.getAttribute("miny")),parseFloat(node.getAttribute("maxx")),parseFloat(node.getAttribute("maxy")));},read_wmc_LayerList:function(context,node){context.layers=[];this.runChildNodes(context,node);},read_wmc_Layer:function(context,node){var layerInfo={params:this.layerParams||{},options:{visibility:(node.getAttribute("hidden")!="1"),queryable:(node.getAttribute("queryable")=="1")},formats:[],styles:[]};this.runChildNodes(layerInfo,node);layerInfo.params.layers=layerInfo.name;layerInfo.options.maxExtent=layerInfo.maxExtent;var layer=this.getLayerFromInfo(layerInfo);context.layers.push(layer);},getLayerFromInfo:function(layerInfo){var options=layerInfo.options;if(this.layerOptions){OpenLayers.Util.applyDefaults(options,this.layerOptions);}
var layer=new OpenLayers.Layer.WMS(layerInfo.title,layerInfo.href,layerInfo.params,options);return layer;},read_wmc_Extension:function(obj,node){this.runChildNodes(obj,node);},read_ol_units:function(layerInfo,node){layerInfo.options.units=this.getChildValue(node);},read_ol_maxExtent:function(obj,node){var bounds=new OpenLayers.Bounds(node.getAttribute("minx"),node.getAttribute("miny"),node.getAttribute("maxx"),node.getAttribute("maxy"));obj.maxExtent=bounds;},read_ol_transparent:function(layerInfo,node){layerInfo.params.transparent=this.getChildValue(node);},read_ol_numZoomLevels:function(layerInfo,node){layerInfo.options.numZoomLevels=parseInt(this.getChildValue(node));},read_ol_opacity:function(layerInfo,node){layerInfo.options.opacity=parseFloat(this.getChildValue(node));},read_ol_singleTile:function(layerInfo,node){layerInfo.options.singleTile=(this.getChildValue(node)=="true");},read_ol_isBaseLayer:function(layerInfo,node){layerInfo.options.isBaseLayer=(this.getChildValue(node)=="true");},read_ol_displayInLayerSwitcher:function(layerInfo,node){layerInfo.options.displayInLayerSwitcher=(this.getChildValue(node)=="true");},read_wmc_Server:function(layerInfo,node){layerInfo.params.version=node.getAttribute("version");this.runChildNodes(layerInfo,node);},read_wmc_FormatList:function(layerInfo,node){this.runChildNodes(layerInfo,node);},read_wmc_Format:function(layerInfo,node){var format=this.getChildValue(node);layerInfo.formats.push(format);if(node.getAttribute("current")=="1"){layerInfo.params.format=format;}},read_wmc_StyleList:function(layerInfo,node){this.runChildNodes(layerInfo,node);},read_wmc_Style:function(layerInfo,node){var style={};this.runChildNodes(style,node);if(node.getAttribute("current")=="1"){if(style.href){layerInfo.params.sld=style.href;}else if(style.body){layerInfo.params.sld_body=style.body;}else{layerInfo.params.styles=style.name;}}
layerInfo.styles.push(style);},read_wmc_SLD:function(style,node){this.runChildNodes(style,node);},read_sld_StyledLayerDescriptor:function(sld,node){var xml=OpenLayers.Format.XML.prototype.write.apply(this,[node]);sld.body=xml;},read_wmc_OnlineResource:function(obj,node){obj.href=this.getAttributeNS(node,this.namespaces.xlink,"href");},read_wmc_Name:function(obj,node){var name=this.getChildValue(node);if(name){obj.name=name;}},read_wmc_Title:function(obj,node){var title=this.getChildValue(node);if(title){obj.title=title;}},read_wmc_MetadataURL:function(layerInfo,node){var metadataURL={};var links=node.getElementsByTagName("OnlineResource");if(links.length>0){this.read_wmc_OnlineResource(metadataURL,links[0]);}
layerInfo.options.metadataURL=metadataURL.href;},read_wmc_Abstract:function(obj,node){var abst=this.getChildValue(node);if(abst){obj["abstract"]=abst;}},read_wmc_LatLonBoundingBox:function(layer,node){layer.llbbox=[parseFloat(node.getAttribute("minx")),parseFloat(node.getAttribute("miny")),parseFloat(node.getAttribute("maxx")),parseFloat(node.getAttribute("maxy"))];},read_wmc_LegendURL:function(style,node){var legend={width:node.getAttribute('width'),height:node.getAttribute('height')};var links=node.getElementsByTagName("OnlineResource");if(links.length>0){this.read_wmc_OnlineResource(legend,links[0]);}
style.legend=legend;},write:function(context,options){var root=this.createElementDefaultNS("ViewContext");this.setAttributes(root,{version:this.VERSION,id:(options&&typeof options.id=="string")?options.id:OpenLayers.Util.createUniqueID("OpenLayers_Context_")});this.setAttributeNS(root,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);root.appendChild(this.write_wmc_General(context));root.appendChild(this.write_wmc_LayerList(context));return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},createElementDefaultNS:function(name,childValue,attributes){var node=this.createElementNS(this.namespaces[this.defaultPrefix],name);if(childValue){node.appendChild(this.createTextNode(childValue));}
if(attributes){this.setAttributes(node,attributes);}
return node;},setAttributes:function(node,obj){var value;for(var name in obj){value=obj[name].toString();if(value.match(/[A-Z]/)){this.setAttributeNS(node,null,name,value);}else{node.setAttribute(name,value);}}},write_wmc_General:function(context){var node=this.createElementDefaultNS("General");if(context.size){node.appendChild(this.createElementDefaultNS("Window",null,{width:context.size.w,height:context.size.h}));}
var bounds=context.bounds;node.appendChild(this.createElementDefaultNS("BoundingBox",null,{minx:bounds.left.toPrecision(10),miny:bounds.bottom.toPrecision(10),maxx:bounds.right.toPrecision(10),maxy:bounds.top.toPrecision(10),SRS:context.projection}));node.appendChild(this.createElementDefaultNS("Title",context.title));node.appendChild(this.write_ol_MapExtension(context));return node;},write_ol_MapExtension:function(context){var node=this.createElementDefaultNS("Extension");var bounds=context.maxExtent;if(bounds){var maxExtent=this.createElementNS(this.namespaces.ol,"ol:maxExtent");this.setAttributes(maxExtent,{minx:bounds.left.toPrecision(10),miny:bounds.bottom.toPrecision(10),maxx:bounds.right.toPrecision(10),maxy:bounds.top.toPrecision(10)});node.appendChild(maxExtent);}
return node;},write_wmc_LayerList:function(context){var list=this.createElementDefaultNS("LayerList");var layer;for(var i=0,len=context.layers.length;i<len;++i){layer=context.layers[i];if(layer instanceof OpenLayers.Layer.WMS){list.appendChild(this.write_wmc_Layer(layer));}}
return list;},write_wmc_Layer:function(layer){var node=this.createElementDefaultNS("Layer",null,{queryable:layer.queryable?"1":"0",hidden:layer.visibility?"0":"1"});node.appendChild(this.write_wmc_Server(layer));node.appendChild(this.createElementDefaultNS("Name",layer.params["LAYERS"]));node.appendChild(this.createElementDefaultNS("Title",layer.name));if(layer.metadataURL){node.appendChild(this.write_wmc_MetadataURL(layer));}
node.appendChild(this.write_wmc_FormatList(layer));node.appendChild(this.write_wmc_StyleList(layer));node.appendChild(this.write_wmc_LayerExtension(layer));return node;},write_wmc_LayerExtension:function(layer){var node=this.createElementDefaultNS("Extension");var bounds=layer.maxExtent;var maxExtent=this.createElementNS(this.namespaces.ol,"ol:maxExtent");this.setAttributes(maxExtent,{minx:bounds.left.toPrecision(10),miny:bounds.bottom.toPrecision(10),maxx:bounds.right.toPrecision(10),maxy:bounds.top.toPrecision(10)});node.appendChild(maxExtent);var param=layer.params["TRANSPARENT"];if(param){var trans=this.createElementNS(this.namespaces.ol,"ol:transparent");trans.appendChild(this.createTextNode(param));node.appendChild(trans);}
var properties=["numZoomLevels","units","isBaseLayer","opacity","displayInLayerSwitcher","singleTile"];var child;for(var i=0,len=properties.length;i<len;++i){child=this.createOLPropertyNode(layer,properties[i]);if(child){node.appendChild(child);}}
return node;},createOLPropertyNode:function(obj,prop){var node=null;if(obj[prop]!=null){node=this.createElementNS(this.namespaces.ol,"ol:"+prop);node.appendChild(this.createTextNode(obj[prop].toString()));}
return node;},write_wmc_Server:function(layer){var node=this.createElementDefaultNS("Server");this.setAttributes(node,{service:"OGC:WMS",version:layer.params["VERSION"]});node.appendChild(this.write_wmc_OnlineResource(layer.url));return node;},write_wmc_MetadataURL:function(layer){var node=this.createElementDefaultNS("MetadataURL");node.appendChild(this.write_wmc_OnlineResource(layer.metadataURL));return node;},write_wmc_FormatList:function(layer){var node=this.createElementDefaultNS("FormatList");node.appendChild(this.createElementDefaultNS("Format",layer.params["FORMAT"],{current:"1"}));return node;},write_wmc_StyleList:function(layer){var node=this.createElementDefaultNS("StyleList");var style=this.createElementDefaultNS("Style",null,{current:"1"});if(layer.params["SLD"]){var sld=this.createElementDefaultNS("SLD");var link=this.write_wmc_OnlineResource(layer.params["SLD"]);sld.appendChild(link);style.appendChild(sld);}else if(layer.params["SLD_BODY"]){var sld=this.createElementDefaultNS("SLD");var body=layer.params["SLD_BODY"];var doc=OpenLayers.Format.XML.prototype.read.apply(this,[body]);var imported=doc.documentElement;if(sld.ownerDocument&&sld.ownerDocument.importNode){imported=sld.ownerDocument.importNode(imported,true);}
sld.appendChild(imported);style.appendChild(sld);}else{var name=layer.params["STYLES"]?layer.params["STYLES"]:this.defaultStyleName;style.appendChild(this.createElementDefaultNS("Name",name));style.appendChild(this.createElementDefaultNS("Title",this.defaultStyleTitle));}
node.appendChild(style);return node;},write_wmc_OnlineResource:function(href){var node=this.createElementDefaultNS("OnlineResource");this.setAttributeNS(node,this.namespaces.xlink,"xlink:type","simple");this.setAttributeNS(node,this.namespaces.xlink,"xlink:href",href);return node;},CLASS_NAME:"OpenLayers.Format.WMC.v1"});OpenLayers.Format.WMSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_1,{version:"1.1.0",initialize:function(options){OpenLayers.Format.WMSCapabilities.v1_1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1_0"});OpenLayers.Format.WMSCapabilities.v1_1_1=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_1,{version:"1.1.1",initialize:function(options){OpenLayers.Format.WMSCapabilities.v1_1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1_1"});OpenLayers.Format.WMSDescribeLayer.v1_1=OpenLayers.Class(OpenLayers.Format.WMSDescribeLayer,{initialize:function(options){OpenLayers.Format.WMSDescribeLayer.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var root=data.documentElement;var children=root.childNodes;var describelayer=[];for(var i=0;i<children.length;++i){childNode=children[i];nodeName=childNode.nodeName;if(nodeName=='LayerDescription'){var owsType='';var owsURL='';var typeName='';if(childNode.getAttribute('owsType')){owsType=childNode.getAttribute('owsType');owsURL=childNode.getAttribute('owsURL');}else{if(childNode.getAttribute('wfs')!=''){owsType='WFS';owsURL=childNode.getAttribute('wfs');}else if(childNode.getAttribute('wcs')!=''){owsType='WCS';owsURL=childNode.getAttribute('wcs');}}
query=childNode.getElementsByTagName('Query');if(query.length>0){typeName=query[0].getAttribute('typeName');if(!typeName){typeName=query[0].getAttribute('typename');}}
describelayer.push({owsType:owsType,owsURL:owsURL,typeName:typeName});}}
return describelayer;},CLASS_NAME:"OpenLayers.Format.WMSDescribeLayer.v1_1"});OpenLayers.Handler.Box=OpenLayers.Class(OpenLayers.Handler,{dragHandler:null,boxDivClassName:'olHandlerBoxZoomBox',boxCharacteristics:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);var callbacks={"down":this.startBox,"move":this.moveBox,"out":this.removeBox,"up":this.endBox};this.dragHandler=new OpenLayers.Handler.Drag(this,callbacks,{keyMask:this.keyMask});},setMap:function(map){OpenLayers.Handler.prototype.setMap.apply(this,arguments);if(this.dragHandler){this.dragHandler.setMap(map);}},startBox:function(xy){this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.dragHandler.start);this.zoomBox.className=this.boxDivClassName;this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);OpenLayers.Element.addClass(this.map.viewPortDiv,"olDrawBox");},moveBox:function(xy){var startX=this.dragHandler.start.x;var startY=this.dragHandler.start.y;var deltaX=Math.abs(startX-xy.x);var deltaY=Math.abs(startY-xy.y);this.zoomBox.style.width=Math.max(1,deltaX)+"px";this.zoomBox.style.height=Math.max(1,deltaY)+"px";this.zoomBox.style.left=xy.x<startX?xy.x+"px":startX+"px";this.zoomBox.style.top=xy.y<startY?xy.y+"px":startY+"px";var box=this.getBoxCharacteristics();if(box.newBoxModel){if(xy.x>startX){this.zoomBox.style.width=Math.max(1,deltaX-box.xOffset)+"px";}
if(xy.y>startY){this.zoomBox.style.height=Math.max(1,deltaY-box.yOffset)+"px";}}},endBox:function(end){var result;if(Math.abs(this.dragHandler.start.x-end.x)>5||Math.abs(this.dragHandler.start.y-end.y)>5){var start=this.dragHandler.start;var top=Math.min(start.y,end.y);var bottom=Math.max(start.y,end.y);var left=Math.min(start.x,end.x);var right=Math.max(start.x,end.x);result=new OpenLayers.Bounds(left,bottom,right,top);}else{result=this.dragHandler.start.clone();}
this.removeBox();this.callback("done",[result]);},removeBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.zoomBox=null;this.boxCharacteristics=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDrawBox");},activate:function(){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.dragHandler.activate();return true;}else{return false;}},deactivate:function(){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.dragHandler.deactivate();return true;}else{return false;}},getBoxCharacteristics:function(){if(!this.boxCharacteristics){var xOffset=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-left-width"))+parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-right-width"))+1;var yOffset=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-top-width"))+parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-bottom-width"))+1;var newBoxModel=OpenLayers.Util.getBrowserName()=="msie"?document.compatMode!="BackCompat":true;this.boxCharacteristics={xOffset:xOffset,yOffset:yOffset,newBoxModel:newBoxModel};}
return this.boxCharacteristics;},CLASS_NAME:"OpenLayers.Handler.Box"});OpenLayers.Handler.RegularPolygon=OpenLayers.Class(OpenLayers.Handler.Drag,{sides:4,radius:null,snapAngle:null,snapToggle:'shiftKey',persist:false,irregular:false,angle:null,fixedRadius:false,feature:null,layer:null,origin:null,initialize:function(control,callbacks,options){this.style=OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'],{});OpenLayers.Handler.prototype.initialize.apply(this,[control,callbacks,options]);this.options=(options)?options:new Object();},setOptions:function(newOptions){OpenLayers.Util.extend(this.options,newOptions);OpenLayers.Util.extend(this,newOptions);},activate:function(){var activated=false;if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var options={displayInLayerSwitcher:false,calculateInRange:function(){return true;}};this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,options);this.map.addLayer(this.layer);activated=true;}
return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this,arguments)){if(this.dragging){this.cancel();}
if(this.layer.map!=null){this.layer.destroy(false);if(this.feature){this.feature.destroy();}}
this.layer=null;this.feature=null;deactivated=true;}
return deactivated;},down:function(evt){this.fixedRadius=!!(this.radius);var maploc=this.map.getLonLatFromPixel(evt.xy);this.origin=new OpenLayers.Geometry.Point(maploc.lon,maploc.lat);if(!this.fixedRadius||this.irregular){this.radius=this.map.getResolution();}
if(this.persist){this.clear();}
this.feature=new OpenLayers.Feature.Vector();this.createGeometry();this.callback("create",[this.origin,this.feature]);this.layer.addFeatures([this.feature],{silent:true});this.layer.drawFeature(this.feature,this.style);},move:function(evt){var maploc=this.map.getLonLatFromPixel(evt.xy);var point=new OpenLayers.Geometry.Point(maploc.lon,maploc.lat);if(this.irregular){var ry=Math.sqrt(2)*Math.abs(point.y-this.origin.y)/2;this.radius=Math.max(this.map.getResolution()/2,ry);}else if(this.fixedRadius){this.origin=point;}else{this.calculateAngle(point,evt);this.radius=Math.max(this.map.getResolution()/2,point.distanceTo(this.origin));}
this.modifyGeometry();if(this.irregular){var dx=point.x-this.origin.x;var dy=point.y-this.origin.y;var ratio;if(dy==0){ratio=dx/(this.radius*Math.sqrt(2));}else{ratio=dx/dy;}
this.feature.geometry.resize(1,this.origin,ratio);this.feature.geometry.move(dx/2,dy/2);}
this.layer.drawFeature(this.feature,this.style);},up:function(evt){this.finalize();if(this.start==this.last){this.callback("done",[evt.xy]);}},out:function(evt){this.finalize();},createGeometry:function(){this.angle=Math.PI*((1/this.sides)-(1/2));if(this.snapAngle){this.angle+=this.snapAngle*(Math.PI/180);}
this.feature.geometry=OpenLayers.Geometry.Polygon.createRegularPolygon(this.origin,this.radius,this.sides,this.snapAngle);},modifyGeometry:function(){var angle,dx,dy,point;var ring=this.feature.geometry.components[0];if(ring.components.length!=(this.sides+1)){this.createGeometry();ring=this.feature.geometry.components[0];}
for(var i=0;i<this.sides;++i){point=ring.components[i];angle=this.angle+(i*2*Math.PI/this.sides);point.x=this.origin.x+(this.radius*Math.cos(angle));point.y=this.origin.y+(this.radius*Math.sin(angle));point.clearBounds();}},calculateAngle:function(point,evt){var alpha=Math.atan2(point.y-this.origin.y,point.x-this.origin.x);if(this.snapAngle&&(this.snapToggle&&!evt[this.snapToggle])){var snapAngleRad=(Math.PI/180)*this.snapAngle;this.angle=Math.round(alpha/snapAngleRad)*snapAngleRad;}else{this.angle=alpha;}},cancel:function(){this.callback("cancel",null);this.finalize();},finalize:function(){this.origin=null;this.radius=this.options.radius;},clear:function(){this.layer.renderer.clear();this.layer.destroyFeatures();},callback:function(name,args){if(this.callbacks[name]){this.callbacks[name].apply(this.control,[this.feature.geometry.clone()]);}
if(!this.persist&&(name=="done"||name=="cancel")){this.clear();}},CLASS_NAME:"OpenLayers.Handler.RegularPolygon"});OpenLayers.Layer.EventPane=OpenLayers.Class(OpenLayers.Layer,{smoothDragPan:true,isBaseLayer:true,isFixed:true,pane:null,mapObject:null,initialize:function(name,options){OpenLayers.Layer.prototype.initialize.apply(this,arguments);if(this.pane==null){this.pane=OpenLayers.Util.createDiv(this.div.id+"_EventPane");}},destroy:function(){this.mapObject=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},setMap:function(map){OpenLayers.Layer.prototype.setMap.apply(this,arguments);this.pane.style.zIndex=parseInt(this.div.style.zIndex)+1;this.pane.style.display=this.div.style.display;this.pane.style.width="100%";this.pane.style.height="100%";if(OpenLayers.Util.getBrowserName()=="msie"){this.pane.style.background="url("+OpenLayers.Util.getImagesLocation()+"blank.gif)";}
if(this.isFixed){this.map.viewPortDiv.appendChild(this.pane);}else{this.map.layerContainerDiv.appendChild(this.pane);}
this.loadMapObject();if(this.mapObject==null){this.loadWarningMessage();}},removeMap:function(map){if(this.pane&&this.pane.parentNode){this.pane.parentNode.removeChild(this.pane);this.pane=null;}
OpenLayers.Layer.prototype.removeMap.apply(this,arguments);},loadWarningMessage:function(){this.div.style.backgroundColor="darkblue";var viewSize=this.map.getSize();var msgW=Math.min(viewSize.w,300);var msgH=Math.min(viewSize.h,200);var size=new OpenLayers.Size(msgW,msgH);var centerPx=new OpenLayers.Pixel(viewSize.w/2,viewSize.h/2);var topLeft=centerPx.add(-size.w/2,-size.h/2);var div=OpenLayers.Util.createDiv(this.name+"_warning",topLeft,size,null,null,null,"auto");div.style.padding="7px";div.style.backgroundColor="yellow";div.innerHTML=this.getWarningHTML();this.div.appendChild(div);},getWarningHTML:function(){return"";},display:function(display){OpenLayers.Layer.prototype.display.apply(this,arguments);this.pane.style.display=this.div.style.display;},setZIndex:function(zIndex){OpenLayers.Layer.prototype.setZIndex.apply(this,arguments);this.pane.style.zIndex=parseInt(this.div.style.zIndex)+1;},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);if(this.mapObject!=null){var newCenter=this.map.getCenter();var newZoom=this.map.getZoom();if(newCenter!=null){var moOldCenter=this.getMapObjectCenter();var oldCenter=this.getOLLonLatFromMapObjectLonLat(moOldCenter);var moOldZoom=this.getMapObjectZoom();var oldZoom=this.getOLZoomFromMapObjectZoom(moOldZoom);if(!(newCenter.equals(oldCenter))||!(newZoom==oldZoom)){if(dragging&&this.dragPanMapObject&&this.smoothDragPan){var oldPx=this.map.getViewPortPxFromLonLat(oldCenter);var newPx=this.map.getViewPortPxFromLonLat(newCenter);this.dragPanMapObject(newPx.x-oldPx.x,oldPx.y-newPx.y);}else{var center=this.getMapObjectLonLatFromOLLonLat(newCenter);var zoom=this.getMapObjectZoomFromOLZoom(newZoom);this.setMapObjectCenter(center,zoom,dragging);}}}}},getLonLatFromViewPortPx:function(viewPortPx){var lonlat=null;if((this.mapObject!=null)&&(this.getMapObjectCenter()!=null)){var moPixel=this.getMapObjectPixelFromOLPixel(viewPortPx);var moLonLat=this.getMapObjectLonLatFromMapObjectPixel(moPixel);lonlat=this.getOLLonLatFromMapObjectLonLat(moLonLat);}
return lonlat;},getViewPortPxFromLonLat:function(lonlat){var viewPortPx=null;if((this.mapObject!=null)&&(this.getMapObjectCenter()!=null)){var moLonLat=this.getMapObjectLonLatFromOLLonLat(lonlat);var moPixel=this.getMapObjectPixelFromMapObjectLonLat(moLonLat);viewPortPx=this.getOLPixelFromMapObjectPixel(moPixel);}
return viewPortPx;},getOLLonLatFromMapObjectLonLat:function(moLonLat){var olLonLat=null;if(moLonLat!=null){var lon=this.getLongitudeFromMapObjectLonLat(moLonLat);var lat=this.getLatitudeFromMapObjectLonLat(moLonLat);olLonLat=new OpenLayers.LonLat(lon,lat);}
return olLonLat;},getMapObjectLonLatFromOLLonLat:function(olLonLat){var moLatLng=null;if(olLonLat!=null){moLatLng=this.getMapObjectLonLatFromLonLat(olLonLat.lon,olLonLat.lat);}
return moLatLng;},getOLPixelFromMapObjectPixel:function(moPixel){var olPixel=null;if(moPixel!=null){var x=this.getXFromMapObjectPixel(moPixel);var y=this.getYFromMapObjectPixel(moPixel);olPixel=new OpenLayers.Pixel(x,y);}
return olPixel;},getMapObjectPixelFromOLPixel:function(olPixel){var moPixel=null;if(olPixel!=null){moPixel=this.getMapObjectPixelFromXY(olPixel.x,olPixel.y);}
return moPixel;},CLASS_NAME:"OpenLayers.Layer.EventPane"});OpenLayers.Layer.FixedZoomLevels=OpenLayers.Class({initialize:function(){},initResolutions:function(){var props=new Array('minZoomLevel','maxZoomLevel','numZoomLevels');for(var i=0,len=props.length;i<len;i++){var property=props[i];this[property]=(this.options[property]!=null)?this.options[property]:this.map[property];}
if((this.minZoomLevel==null)||(this.minZoomLevel<this.MIN_ZOOM_LEVEL)){this.minZoomLevel=this.MIN_ZOOM_LEVEL;}
var desiredZoomLevels;var limitZoomLevels=this.MAX_ZOOM_LEVEL-this.minZoomLevel+1;if(((this.options.numZoomLevels==null)&&(this.options.maxZoomLevel!=null))||((this.numZoomLevels==null)&&(this.maxZoomLevel!=null))){desiredZoomLevels=this.maxZoomLevel-this.minZoomLevel+1;}else{desiredZoomLevels=this.numZoomLevels;}
if(desiredZoomLevels!=null){this.numZoomLevels=Math.min(desiredZoomLevels,limitZoomLevels);}else{this.numZoomLevels=limitZoomLevels;}
this.maxZoomLevel=this.minZoomLevel+this.numZoomLevels-1;if(this.RESOLUTIONS!=null){var resolutionsIndex=0;this.resolutions=[];for(var i=this.minZoomLevel;i<=this.maxZoomLevel;i++){this.resolutions[resolutionsIndex++]=this.RESOLUTIONS[i];}
this.maxResolution=this.resolutions[0];this.minResolution=this.resolutions[this.resolutions.length-1];}},getResolution:function(){if(this.resolutions!=null){return OpenLayers.Layer.prototype.getResolution.apply(this,arguments);}else{var resolution=null;var viewSize=this.map.getSize();var extent=this.getExtent();if((viewSize!=null)&&(extent!=null)){resolution=Math.max(extent.getWidth()/viewSize.w,extent.getHeight()/viewSize.h);}
return resolution;}},getExtent:function(){var extent=null;var size=this.map.getSize();var tlPx=new OpenLayers.Pixel(0,0);var tlLL=this.getLonLatFromViewPortPx(tlPx);var brPx=new OpenLayers.Pixel(size.w,size.h);var brLL=this.getLonLatFromViewPortPx(brPx);if((tlLL!=null)&&(brLL!=null)){extent=new OpenLayers.Bounds(tlLL.lon,brLL.lat,brLL.lon,tlLL.lat);}
return extent;},getZoomForResolution:function(resolution){if(this.resolutions!=null){return OpenLayers.Layer.prototype.getZoomForResolution.apply(this,arguments);}else{var extent=OpenLayers.Layer.prototype.getExtent.apply(this,[]);return this.getZoomForExtent(extent);}},getOLZoomFromMapObjectZoom:function(moZoom){var zoom=null;if(moZoom!=null){zoom=moZoom-this.minZoomLevel;}
return zoom;},getMapObjectZoomFromOLZoom:function(olZoom){var zoom=null;if(olZoom!=null){zoom=olZoom+this.minZoomLevel;}
return zoom;},CLASS_NAME:"OpenLayers.Layer.FixedZoomLevels"});OpenLayers.Layer.HTTPRequest=OpenLayers.Class(OpenLayers.Layer,{URL_HASH_FACTOR:(Math.sqrt(5)-1)/2,url:null,params:null,reproject:false,initialize:function(name,url,params,options){var newArguments=arguments;newArguments=[name,options];OpenLayers.Layer.prototype.initialize.apply(this,newArguments);this.url=url;this.params=OpenLayers.Util.extend({},params);},destroy:function(){this.url=null;this.params=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.HTTPRequest(this.name,this.url,this.params,this.options);}
obj=OpenLayers.Layer.prototype.clone.apply(this,[obj]);return obj;},setUrl:function(newUrl){this.url=newUrl;},mergeNewParams:function(newParams){this.params=OpenLayers.Util.extend(this.params,newParams);return this.redraw();},redraw:function(force){if(force){return this.mergeNewParams({"_olSalt":Math.random()});}else{return OpenLayers.Layer.prototype.redraw.apply(this,[]);}},selectUrl:function(paramString,urls){var product=1;for(var i=0,len=paramString.length;i<len;i++){product*=paramString.charCodeAt(i)*this.URL_HASH_FACTOR;product-=Math.floor(product);}
return urls[Math.floor(product*urls.length)];},getFullRequestString:function(newParams,altUrl){var url=altUrl||this.url;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var paramsString=OpenLayers.Util.getParameterString(allParams);if(url instanceof Array){url=this.selectUrl(paramsString,url);}
var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}}
paramsString=OpenLayers.Util.getParameterString(allParams);var requestString=url;if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}}
return requestString;},CLASS_NAME:"OpenLayers.Layer.HTTPRequest"});OpenLayers.Layer.Image=OpenLayers.Class(OpenLayers.Layer,{isBaseLayer:true,url:null,extent:null,size:null,tile:null,aspectRatio:null,initialize:function(name,url,extent,size,options){this.url=url;this.extent=extent;this.maxExtent=extent;this.size=size;OpenLayers.Layer.prototype.initialize.apply(this,[name,options]);this.aspectRatio=(this.extent.getHeight()/this.size.h)/(this.extent.getWidth()/this.size.w);},destroy:function(){if(this.tile){this.removeTileMonitoringHooks(this.tile);this.tile.destroy();this.tile=null;}
OpenLayers.Layer.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.Image(this.name,this.url,this.extent,this.size,this.options);}
obj=OpenLayers.Layer.prototype.clone.apply(this,[obj]);return obj;},setMap:function(map){if(this.options.maxResolution==null){this.options.maxResolution=this.aspectRatio*this.extent.getWidth()/this.size.w;}
OpenLayers.Layer.prototype.setMap.apply(this,arguments);},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var firstRendering=(this.tile==null);if(zoomChanged||firstRendering){this.setTileSize();var ul=new OpenLayers.LonLat(this.extent.left,this.extent.top);var ulPx=this.map.getLayerPxFromLonLat(ul);if(firstRendering){this.tile=new OpenLayers.Tile.Image(this,ulPx,this.extent,null,this.tileSize);this.addTileMonitoringHooks(this.tile);}else{this.tile.size=this.tileSize.clone();this.tile.position=ulPx.clone();}
this.tile.draw();}},setTileSize:function(){var tileWidth=this.extent.getWidth()/this.map.getResolution();var tileHeight=this.extent.getHeight()/this.map.getResolution();this.tileSize=new OpenLayers.Size(tileWidth,tileHeight);},addTileMonitoringHooks:function(tile){tile.onLoadStart=function(){this.events.triggerEvent("loadstart");};tile.events.register("loadstart",this,tile.onLoadStart);tile.onLoadEnd=function(){this.events.triggerEvent("loadend");};tile.events.register("loadend",this,tile.onLoadEnd);tile.events.register("unload",this,tile.onLoadEnd);},removeTileMonitoringHooks:function(tile){tile.unload();tile.events.un({"loadstart":tile.onLoadStart,"loadend":tile.onLoadEnd,"unload":tile.onLoadEnd,scope:this});},setUrl:function(newUrl){this.url=newUrl;this.tile.draw();},getURL:function(bounds){return this.url;},CLASS_NAME:"OpenLayers.Layer.Image"});OpenLayers.Layer.Markers=OpenLayers.Class(OpenLayers.Layer,{isBaseLayer:false,markers:null,drawn:false,initialize:function(name,options){OpenLayers.Layer.prototype.initialize.apply(this,arguments);this.markers=[];},destroy:function(){this.clearMarkers();this.markers=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},setOpacity:function(opacity){if(opacity!=this.opacity){this.opacity=opacity;for(var i=0,len=this.markers.length;i<len;i++){this.markers[i].setOpacity(this.opacity);}}},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);if(zoomChanged||!this.drawn){for(var i=0,len=this.markers.length;i<len;i++){this.drawMarker(this.markers[i]);}
this.drawn=true;}},addMarker:function(marker){this.markers.push(marker);if(this.opacity!=null){marker.setOpacity(this.opacity);}
if(this.map&&this.map.getExtent()){marker.map=this.map;this.drawMarker(marker);}},removeMarker:function(marker){if(this.markers&&this.markers.length){OpenLayers.Util.removeItem(this.markers,marker);marker.erase();}},clearMarkers:function(){if(this.markers!=null){while(this.markers.length>0){this.removeMarker(this.markers[0]);}}},drawMarker:function(marker){var px=this.map.getLayerPxFromLonLat(marker.lonlat);if(px==null){marker.display(false);}else{if(!marker.isDrawn()){var markerImg=marker.draw(px);this.div.appendChild(markerImg);}else if(marker.icon){marker.icon.moveTo(px);}}},getDataExtent:function(){var maxExtent=null;if(this.markers&&(this.markers.length>0)){var maxExtent=new OpenLayers.Bounds();for(var i=0,len=this.markers.length;i<len;i++){var marker=this.markers[i];maxExtent.extend(marker.lonlat);}}
return maxExtent;},CLASS_NAME:"OpenLayers.Layer.Markers"});OpenLayers.Layer.SphericalMercator={getExtent:function(){var extent=null;if(this.sphericalMercator){extent=this.map.calculateBounds();}else{extent=OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this);}
return extent;},initMercatorParameters:function(){this.RESOLUTIONS=[];var maxResolution=156543.0339;for(var zoom=0;zoom<=this.MAX_ZOOM_LEVEL;++zoom){this.RESOLUTIONS[zoom]=maxResolution/Math.pow(2,zoom);}
this.units="m";this.projection="EPSG:900913";},forwardMercator:function(lon,lat){var x=lon*20037508.34/180;var y=Math.log(Math.tan((90+lat)*Math.PI/360))/(Math.PI/180);y=y*20037508.34/180;return new OpenLayers.LonLat(x,y);},inverseMercator:function(x,y){var lon=(x/20037508.34)*180;var lat=(y/20037508.34)*180;lat=180/Math.PI*(2*Math.atan(Math.exp(lat*Math.PI/180))-Math.PI/2);return new OpenLayers.LonLat(lon,lat);},projectForward:function(point){var lonlat=OpenLayers.Layer.SphericalMercator.forwardMercator(point.x,point.y);point.x=lonlat.lon;point.y=lonlat.lat;return point;},projectInverse:function(point){var lonlat=OpenLayers.Layer.SphericalMercator.inverseMercator(point.x,point.y);point.x=lonlat.lon;point.y=lonlat.lat;return point;}};OpenLayers.Projection.addTransform("EPSG:4326","EPSG:900913",OpenLayers.Layer.SphericalMercator.projectForward);OpenLayers.Projection.addTransform("EPSG:900913","EPSG:4326",OpenLayers.Layer.SphericalMercator.projectInverse);OpenLayers.Tile.WFS=OpenLayers.Class(OpenLayers.Tile,{features:null,url:null,request:null,initialize:function(layer,position,bounds,url,size){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=url;this.features=[];},destroy:function(){OpenLayers.Tile.prototype.destroy.apply(this,arguments);this.destroyAllFeatures();this.features=null;this.url=null;if(this.request){this.request.abort();this.request=null;}},clear:function(){this.destroyAllFeatures();},draw:function(){if(OpenLayers.Tile.prototype.draw.apply(this,arguments)){if(this.isLoading){this.events.triggerEvent("reload");}else{this.isLoading=true;this.events.triggerEvent("loadstart");}
this.loadFeaturesForRegion(this.requestSuccess);}},loadFeaturesForRegion:function(success,failure){if(this.request){this.request.abort();}
this.request=OpenLayers.Request.GET({url:this.url,success:success,failure:failure,scope:this});},requestSuccess:function(request){if(this.features){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
if(this.layer.vectorMode){this.layer.addFeatures(this.layer.formatObject.read(doc));}else{var xml=new OpenLayers.Format.XML();if(typeof doc=="string"){doc=xml.read(doc);}
var resultFeatures=xml.getElementsByTagNameNS(doc,"http://www.opengis.net/gml","featureMember");this.addResults(resultFeatures);}}
if(this.events){this.events.triggerEvent("loadend");}
this.request=null;},addResults:function(results){for(var i=0;i<results.length;i++){var feature=new this.layer.featureClass(this.layer,results[i]);this.features.push(feature);}},destroyAllFeatures:function(){while(this.features.length>0){var feature=this.features.shift();feature.destroy();}},CLASS_NAME:"OpenLayers.Tile.WFS"});OpenLayers.Control.DrawFeature=OpenLayers.Class(OpenLayers.Control,{layer:null,callbacks:null,EVENT_TYPES:["featureadded"],featureAdded:function(){},handlerOptions:null,initialize:function(layer,handler,options){this.EVENT_TYPES=OpenLayers.Control.DrawFeature.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.callbacks=OpenLayers.Util.extend({done:this.drawFeature,modify:function(vertex,feature){this.layer.events.triggerEvent("sketchmodified",{vertex:vertex,feature:feature});},create:function(vertex,feature){this.layer.events.triggerEvent("sketchstarted",{vertex:vertex,feature:feature});}},this.callbacks);this.layer=layer;var sketchStyle=this.layer.styleMap&&this.layer.styleMap.styles.temporary;if(sketchStyle){this.handlerOptions=this.handlerOptions||{};this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{styleMap:new OpenLayers.StyleMap({"default":sketchStyle})});}
this.handler=new handler(this,this.callbacks,this.handlerOptions);},drawFeature:function(geometry){var feature=new OpenLayers.Feature.Vector(geometry);var proceed=this.layer.events.triggerEvent("sketchcomplete",{feature:feature});if(proceed!==false){feature.state=OpenLayers.State.INSERT;this.layer.addFeatures([feature]);this.featureAdded(feature);this.events.triggerEvent("featureadded",{feature:feature});}},CLASS_NAME:"OpenLayers.Control.DrawFeature"});OpenLayers.Control.Measure=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:['measure','measurepartial'],handlerOptions:null,callbacks:null,displaySystem:'metric',geodesic:false,displaySystemUnits:{geographic:['dd'],english:['mi','ft','in'],metric:['km','m']},partialDelay:300,delayedTrigger:null,persist:false,initialize:function(handler,options){this.EVENT_TYPES=OpenLayers.Control.Measure.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.callbacks=OpenLayers.Util.extend({done:this.measureComplete,point:this.measurePartial},this.callbacks);this.handlerOptions=OpenLayers.Util.extend({persist:this.persist},this.handlerOptions);this.handler=new handler(this,this.callbacks,this.handlerOptions);},cancel:function(){this.handler.cancel();},updateHandler:function(handler,options){var active=this.active;if(active){this.deactivate();}
this.handler=new handler(this,this.callbacks,options);if(active){this.activate();}},measureComplete:function(geometry){if(this.delayedTrigger){window.clearTimeout(this.delayedTrigger);}
this.measure(geometry,"measure");},measurePartial:function(point,geometry){this.delayedTrigger=window.setTimeout(OpenLayers.Function.bind(function(){this.measure(geometry,"measurepartial");},this),this.partialDelay);},measure:function(geometry,eventType){var stat,order;if(geometry.CLASS_NAME.indexOf('LineString')>-1){stat=this.getBestLength(geometry);order=1;}else{stat=this.getBestArea(geometry);order=2;}
this.events.triggerEvent(eventType,{measure:stat[0],units:stat[1],order:order,geometry:geometry});},getBestArea:function(geometry){var units=this.displaySystemUnits[this.displaySystem];var unit,area;for(var i=0,len=units.length;i<len;++i){unit=units[i];area=this.getArea(geometry,unit);if(area>1){break;}}
return[area,unit];},getArea:function(geometry,units){var area,geomUnits;if(this.geodesic){area=geometry.getGeodesicArea(this.map.getProjectionObject());geomUnits="m";}else{area=geometry.getArea();geomUnits=this.map.getUnits();}
var inPerDisplayUnit=OpenLayers.INCHES_PER_UNIT[units];if(inPerDisplayUnit){var inPerMapUnit=OpenLayers.INCHES_PER_UNIT[geomUnits];area*=Math.pow((inPerMapUnit/inPerDisplayUnit),2);}
return area;},getBestLength:function(geometry){var units=this.displaySystemUnits[this.displaySystem];var unit,length;for(var i=0,len=units.length;i<len;++i){unit=units[i];length=this.getLength(geometry,unit);if(length>1){break;}}
return[length,unit];},getLength:function(geometry,units){var length,geomUnits;if(this.geodesic){length=geometry.getGeodesicLength(this.map.getProjectionObject());geomUnits="m";}else{length=geometry.getLength();geomUnits=this.map.getUnits();}
var inPerDisplayUnit=OpenLayers.INCHES_PER_UNIT[units];if(inPerDisplayUnit){var inPerMapUnit=OpenLayers.INCHES_PER_UNIT[geomUnits];length*=(inPerMapUnit/inPerDisplayUnit);}
return length;},CLASS_NAME:"OpenLayers.Control.Measure"});OpenLayers.Control.ZoomBox=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,out:false,alwaysZoom:false,draw:function(){this.handler=new OpenLayers.Handler.Box(this,{done:this.zoomBox},{keyMask:this.keyMask});},zoomBox:function(position){if(position instanceof OpenLayers.Bounds){if(!this.out){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));var bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);}else{var pixWidth=Math.abs(position.right-position.left);var pixHeight=Math.abs(position.top-position.bottom);var zoomFactor=Math.min((this.map.size.h/pixHeight),(this.map.size.w/pixWidth));var extent=this.map.getExtent();var center=this.map.getLonLatFromPixel(position.getCenterPixel());var xmin=center.lon-(extent.getWidth()/2)*zoomFactor;var xmax=center.lon+(extent.getWidth()/2)*zoomFactor;var ymin=center.lat-(extent.getHeight()/2)*zoomFactor;var ymax=center.lat+(extent.getHeight()/2)*zoomFactor;var bounds=new OpenLayers.Bounds(xmin,ymin,xmax,ymax);}
var lastZoom=this.map.getZoom();this.map.zoomToExtent(bounds);if(lastZoom==this.map.getZoom()&&this.alwaysZoom==true){this.map.zoomTo(lastZoom+(this.out?-1:1));}}else{if(!this.out){this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()+1);}else{this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()-1);}}},CLASS_NAME:"OpenLayers.Control.ZoomBox"});OpenLayers.Format.WFSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{initialize:function(options){OpenLayers.Format.WFSCapabilities.v1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1_0_0"});OpenLayers.Format.WFSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{initialize:function(options){OpenLayers.Format.WFSCapabilities.v1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1_1_0"});OpenLayers.Format.WKT=OpenLayers.Class(OpenLayers.Format,{initialize:function(options){this.regExes={'typeStr':/^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,'spaces':/\s+/,'parenComma':/\)\s*,\s*\(/,'doubleParenComma':/\)\s*\)\s*,\s*\(\s*\(/,'trimParens':/^\s*\(?(.*?)\)?\s*$/};OpenLayers.Format.prototype.initialize.apply(this,[options]);},read:function(wkt){var features,type,str;var matches=this.regExes.typeStr.exec(wkt);if(matches){type=matches[1].toLowerCase();str=matches[2];if(this.parse[type]){features=this.parse[type].apply(this,[str]);}
if(this.internalProjection&&this.externalProjection){if(features&&features.CLASS_NAME=="OpenLayers.Feature.Vector"){features.geometry.transform(this.externalProjection,this.internalProjection);}else if(features&&type!="geometrycollection"&&typeof features=="object"){for(var i=0,len=features.length;i<len;i++){var component=features[i];component.geometry.transform(this.externalProjection,this.internalProjection);}}}}
return features;},write:function(features){var collection,geometry,type,data,isCollection;if(features.constructor==Array){collection=features;isCollection=true;}else{collection=[features];isCollection=false;}
var pieces=[];if(isCollection){pieces.push('GEOMETRYCOLLECTION(');}
for(var i=0,len=collection.length;i<len;++i){if(isCollection&&i>0){pieces.push(',');}
geometry=collection[i].geometry;type=geometry.CLASS_NAME.split('.')[2].toLowerCase();if(!this.extract[type]){return null;}
if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
data=this.extract[type].apply(this,[geometry]);pieces.push(type.toUpperCase()+'('+data+')');}
if(isCollection){pieces.push(')');}
return pieces.join('');},extract:{'point':function(point){return point.x+' '+point.y;},'multipoint':function(multipoint){var array=[];for(var i=0,len=multipoint.components.length;i<len;++i){array.push(this.extract.point.apply(this,[multipoint.components[i]]));}
return array.join(',');},'linestring':function(linestring){var array=[];for(var i=0,len=linestring.components.length;i<len;++i){array.push(this.extract.point.apply(this,[linestring.components[i]]));}
return array.join(',');},'multilinestring':function(multilinestring){var array=[];for(var i=0,len=multilinestring.components.length;i<len;++i){array.push('('+
this.extract.linestring.apply(this,[multilinestring.components[i]])+')');}
return array.join(',');},'polygon':function(polygon){var array=[];for(var i=0,len=polygon.components.length;i<len;++i){array.push('('+
this.extract.linestring.apply(this,[polygon.components[i]])+')');}
return array.join(',');},'multipolygon':function(multipolygon){var array=[];for(var i=0,len=multipolygon.components.length;i<len;++i){array.push('('+
this.extract.polygon.apply(this,[multipolygon.components[i]])+')');}
return array.join(',');}},parse:{'point':function(str){var coords=OpenLayers.String.trim(str).split(this.regExes.spaces);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(coords[0],coords[1]));},'multipoint':function(str){var points=OpenLayers.String.trim(str).split(',');var components=[];for(var i=0,len=points.length;i<len;++i){components.push(this.parse.point.apply(this,[points[i]]).geometry);}
return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPoint(components));},'linestring':function(str){var points=OpenLayers.String.trim(str).split(',');var components=[];for(var i=0,len=points.length;i<len;++i){components.push(this.parse.point.apply(this,[points[i]]).geometry);}
return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString(components));},'multilinestring':function(str){var line;var lines=OpenLayers.String.trim(str).split(this.regExes.parenComma);var components=[];for(var i=0,len=lines.length;i<len;++i){line=lines[i].replace(this.regExes.trimParens,'$1');components.push(this.parse.linestring.apply(this,[line]).geometry);}
return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiLineString(components));},'polygon':function(str){var ring,linestring,linearring;var rings=OpenLayers.String.trim(str).split(this.regExes.parenComma);var components=[];for(var i=0,len=rings.length;i<len;++i){ring=rings[i].replace(this.regExes.trimParens,'$1');linestring=this.parse.linestring.apply(this,[ring]).geometry;linearring=new OpenLayers.Geometry.LinearRing(linestring.components);components.push(linearring);}
return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon(components));},'multipolygon':function(str){var polygon;var polygons=OpenLayers.String.trim(str).split(this.regExes.doubleParenComma);var components=[];for(var i=0,len=polygons.length;i<len;++i){polygon=polygons[i].replace(this.regExes.trimParens,'$1');components.push(this.parse.polygon.apply(this,[polygon]).geometry);}
return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPolygon(components));},'geometrycollection':function(str){str=str.replace(/,\s*([A-Za-z])/g,'|$1');var wktArray=OpenLayers.String.trim(str).split('|');var components=[];for(var i=0,len=wktArray.length;i<len;++i){components.push(OpenLayers.Format.WKT.prototype.read.apply(this,[wktArray[i]]));}
return components;}},CLASS_NAME:"OpenLayers.Format.WKT"});OpenLayers.Format.WMC.v1_0_0=OpenLayers.Class(OpenLayers.Format.WMC.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/context http://schemas.opengis.net/context/1.0.0/context.xsd",initialize:function(options){OpenLayers.Format.WMC.v1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.WMC.v1_0_0"});OpenLayers.Format.WMC.v1_1_0=OpenLayers.Class(OpenLayers.Format.WMC.v1,{VERSION:"1.1.0",schemaLocation:"http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd",initialize:function(options){OpenLayers.Format.WMC.v1.prototype.initialize.apply(this,[options]);},read_sld_MinScaleDenominator:function(layerInfo,node){layerInfo.options.maxScale=this.getChildValue(node);},read_sld_MaxScaleDenominator:function(layerInfo,node){layerInfo.options.minScale=this.getChildValue(node);},write_wmc_Layer:function(layer){var node=OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(this,[layer]);if(layer.options.resolutions||layer.options.scales||layer.options.minResolution||layer.options.maxScale){var minSD=this.createElementNS(this.namespaces.sld,"sld:MinScaleDenominator");minSD.appendChild(this.createTextNode(layer.maxScale.toPrecision(10)));node.insertBefore(minSD,node.childNodes[3]);}
if(layer.options.resolutions||layer.options.scales||layer.options.maxResolution||layer.options.minScale){var maxSD=this.createElementNS(this.namespaces.sld,"sld:MaxScaleDenominator");maxSD.appendChild(this.createTextNode(layer.minScale.toPrecision(10)));node.insertBefore(maxSD,node.childNodes[4]);}
return node;},CLASS_NAME:"OpenLayers.Format.WMC.v1_1_0"});OpenLayers.Layer.Boxes=OpenLayers.Class(OpenLayers.Layer.Markers,{initialize:function(name,options){OpenLayers.Layer.Markers.prototype.initialize.apply(this,arguments);},drawMarker:function(marker){var bounds=marker.bounds;var topleft=this.map.getLayerPxFromLonLat(new OpenLayers.LonLat(bounds.left,bounds.top));var botright=this.map.getLayerPxFromLonLat(new OpenLayers.LonLat(bounds.right,bounds.bottom));if(botright==null||topleft==null){marker.display(false);}else{var sz=new OpenLayers.Size(Math.max(1,botright.x-topleft.x),Math.max(1,botright.y-topleft.y));var markerDiv=marker.draw(topleft,sz);if(!marker.drawn){this.div.appendChild(markerDiv);marker.drawn=true;}}},removeMarker:function(marker){OpenLayers.Util.removeItem(this.markers,marker);if((marker.div!=null)&&(marker.div.parentNode==this.div)){this.div.removeChild(marker.div);}},CLASS_NAME:"OpenLayers.Layer.Boxes"});OpenLayers.Layer.GeoRSS=OpenLayers.Class(OpenLayers.Layer.Markers,{location:null,features:null,formatOptions:null,selectedFeature:null,icon:null,popupSize:null,useFeedTitle:true,initialize:function(name,location,options){OpenLayers.Layer.Markers.prototype.initialize.apply(this,[name,options]);this.location=location;this.features=[];},destroy:function(){OpenLayers.Layer.Markers.prototype.destroy.apply(this,arguments);this.clearFeatures();this.features=null;},loadRSS:function(){if(!this.loaded){this.events.triggerEvent("loadstart");OpenLayers.Request.GET({url:this.location,success:this.parseData,scope:this});this.loaded=true;}},moveTo:function(bounds,zoomChanged,minor){OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);if(this.visibility&&!this.loaded){this.loadRSS();}},parseData:function(ajaxRequest){var doc=ajaxRequest.responseXML;if(!doc||!doc.documentElement){doc=OpenLayers.Format.XML.prototype.read(ajaxRequest.responseText);}
if(this.useFeedTitle){var name=null;try{name=doc.getElementsByTagNameNS('*','title')[0].firstChild.nodeValue;}
catch(e){name=doc.getElementsByTagName('title')[0].firstChild.nodeValue;}
if(name){this.setName(name);}}
var options={};OpenLayers.Util.extend(options,this.formatOptions);if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
var format=new OpenLayers.Format.GeoRSS(options);var features=format.read(doc);for(var i=0,len=features.length;i<len;i++){var data={};var feature=features[i];if(!feature.geometry){continue;}
var title=feature.attributes.title?feature.attributes.title:"Untitled";var description=feature.attributes.description?feature.attributes.description:"No description.";var link=feature.attributes.link?feature.attributes.link:"";var location=feature.geometry.getBounds().getCenterLonLat();data.icon=this.icon==null?OpenLayers.Marker.defaultIcon():this.icon.clone();data.popupSize=this.popupSize?this.popupSize.clone():new OpenLayers.Size(250,120);if(title||description){data.title=title;data.description=description;var contentHTML='<div class="olLayerGeoRSSClose">[x]</div>';contentHTML+='<div class="olLayerGeoRSSTitle">';if(link){contentHTML+='<a class="link" href="'+link+'" target="_blank">';}
contentHTML+=title;if(link){contentHTML+='</a>';}
contentHTML+='</div>';contentHTML+='<div style="" class="olLayerGeoRSSDescription">';contentHTML+=description;contentHTML+='</div>';data['popupContentHTML']=contentHTML;}
var feature=new OpenLayers.Feature(this,location,data);this.features.push(feature);var marker=feature.createMarker();marker.events.register('click',feature,this.markerClick);this.addMarker(marker);}
this.events.triggerEvent("loadend");},markerClick:function(evt){var sameMarkerClicked=(this==this.layer.selectedFeature);this.layer.selectedFeature=(!sameMarkerClicked)?this:null;for(var i=0,len=this.layer.map.popups.length;i<len;i++){this.layer.map.removePopup(this.layer.map.popups[i]);}
if(!sameMarkerClicked){var popup=this.createPopup();OpenLayers.Event.observe(popup.div,"click",OpenLayers.Function.bind(function(){for(var i=0,len=this.layer.map.popups.length;i<len;i++){this.layer.map.removePopup(this.layer.map.popups[i]);}},this));this.layer.map.addPopup(popup);}
OpenLayers.Event.stop(evt);},clearFeatures:function(){if(this.features!=null){while(this.features.length>0){var feature=this.features[0];OpenLayers.Util.removeItem(this.features,feature);feature.destroy();}}},CLASS_NAME:"OpenLayers.Layer.GeoRSS"});OpenLayers.Layer.Google=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:0,MAX_ZOOM_LEVEL:19,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125,0.00002145767211914062,0.00001072883605957031,0.00000536441802978515,0.00000268220901489257],type:null,sphericalMercator:false,dragObject:null,termsOfUse:null,poweredBy:null,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);this.addContainerPxFunction();if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();}},loadMapObject:function(){try{this.mapObject=new GMap2(this.div);if(typeof this.mapObject.getDragObject=="function"){this.dragObject=this.mapObject.getDragObject();}else{this.dragPanMapObject=null;}
this.termsOfUse=this.div.lastChild;this.div.removeChild(this.termsOfUse);if(this.isFixed){this.map.viewPortDiv.appendChild(this.termsOfUse);}else{this.map.layerContainerDiv.appendChild(this.termsOfUse);}
this.termsOfUse.style.zIndex="1100";this.termsOfUse.style.display=this.div.style.display;this.termsOfUse.style.right="";this.termsOfUse.style.bottom="";this.termsOfUse.className="olLayerGoogleCopyright";this.poweredBy=this.div.lastChild;this.div.removeChild(this.poweredBy);if(this.isFixed){this.map.viewPortDiv.appendChild(this.poweredBy);}else{this.map.layerContainerDiv.appendChild(this.poweredBy);}
this.poweredBy.style.zIndex="1100";this.poweredBy.style.display=this.div.style.display;this.poweredBy.style.right="";this.poweredBy.style.bottom="";this.poweredBy.className="olLayerGooglePoweredBy gmnoprint";}catch(e){OpenLayers.Console.error(e);}},setMap:function(map){OpenLayers.Layer.EventPane.prototype.setMap.apply(this,arguments);if(this.type!=null){this.map.events.register("moveend",this,this.setMapType);}},setMapType:function(){if(this.mapObject.getCenter()!=null){if(OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),this.type)==-1){this.mapObject.addMapType(this.type);}
this.mapObject.setMapType(this.type);this.map.events.unregister("moveend",this,this.setMapType);}},onMapResize:function(){if(this.visibility&&this.mapObject.isLoaded()){this.mapObject.checkResize();}else{if(!this._resized){var layer=this;var handle=GEvent.addListener(this.mapObject,"load",function(){GEvent.removeListener(handle);delete layer._resized;layer.mapObject.checkResize();layer.moveTo(layer.map.getCenter(),layer.map.getZoom());})}
this._resized=true;}},display:function(display){OpenLayers.Layer.EventPane.prototype.display.apply(this,arguments);this.termsOfUse.style.display=this.div.style.display;this.poweredBy.style.display=this.div.style.display;},removeMap:function(map){if(this.termsOfUse&&this.termsOfUse.parentNode){this.termsOfUse.parentNode.removeChild(this.termsOfUse);this.termsOfUse=null;}
if(this.poweredBy&&this.poweredBy.parentNode){this.poweredBy.parentNode.removeChild(this.poweredBy);this.poweredBy=null;}
OpenLayers.Layer.EventPane.prototype.removeMap.apply(this,arguments);},getOLBoundsFromMapObjectBounds:function(moBounds){var olBounds=null;if(moBounds!=null){var sw=moBounds.getSouthWest();var ne=moBounds.getNorthEast();if(this.sphericalMercator){sw=this.forwardMercator(sw.lng(),sw.lat());ne=this.forwardMercator(ne.lng(),ne.lat());}else{sw=new OpenLayers.LonLat(sw.lng(),sw.lat());ne=new OpenLayers.LonLat(ne.lng(),ne.lat());}
olBounds=new OpenLayers.Bounds(sw.lon,sw.lat,ne.lon,ne.lat);}
return olBounds;},getMapObjectBoundsFromOLBounds:function(olBounds){var moBounds=null;if(olBounds!=null){var sw=this.sphericalMercator?this.inverseMercator(olBounds.bottom,olBounds.left):new OpenLayers.LonLat(olBounds.bottom,olBounds.left);var ne=this.sphericalMercator?this.inverseMercator(olBounds.top,olBounds.right):new OpenLayers.LonLat(olBounds.top,olBounds.right);moBounds=new GLatLngBounds(new GLatLng(sw.lat,sw.lon),new GLatLng(ne.lat,ne.lon));}
return moBounds;},addContainerPxFunction:function(){if((typeof GMap2!="undefined")&&!GMap2.prototype.fromLatLngToContainerPixel){GMap2.prototype.fromLatLngToContainerPixel=function(gLatLng){var gPoint=this.fromLatLngToDivPixel(gLatLng);var div=this.getContainer().firstChild.firstChild;gPoint.x+=div.offsetLeft;gPoint.y+=div.offsetTop;return gPoint;};}},getWarningHTML:function(){return OpenLayers.i18n("googleWarning");},setMapObjectCenter:function(center,zoom){this.mapObject.setCenter(center,zoom);},dragPanMapObject:function(dX,dY){this.dragObject.moveBy(new GSize(-dX,dY));},getMapObjectCenter:function(){return this.mapObject.getCenter();},getMapObjectZoom:function(){return this.mapObject.getZoom();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){return this.mapObject.fromContainerPixelToLatLng(moPixel);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.fromLatLngToContainerPixel(moLonLat);},getMapObjectZoomFromMapObjectBounds:function(moBounds){return this.mapObject.getBoundsZoomLevel(moBounds);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.lng(),moLonLat.lat()).lon:moLonLat.lng();},getLatitudeFromMapObjectLonLat:function(moLonLat){var lat=this.sphericalMercator?this.forwardMercator(moLonLat.lng(),moLonLat.lat()).lat:moLonLat.lat();return lat;},getMapObjectLonLatFromLonLat:function(lon,lat){var gLatLng;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);gLatLng=new GLatLng(lonlat.lat,lonlat.lon);}else{gLatLng=new GLatLng(lat,lon);}
return gLatLng;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return new GPoint(x,y);},CLASS_NAME:"OpenLayers.Layer.Google"});OpenLayers.Layer.Grid=OpenLayers.Class(OpenLayers.Layer.HTTPRequest,{tileSize:null,grid:null,singleTile:false,ratio:1.5,buffer:2,numLoadingTiles:0,initialize:function(name,url,params,options){OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,arguments);this.events.addEventType("tileloaded");this.grid=[];},destroy:function(){this.clearGrid();this.grid=null;this.tileSize=null;OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this,arguments);},clearGrid:function(){if(this.grid){for(var iRow=0,len=this.grid.length;iRow<len;iRow++){var row=this.grid[iRow];for(var iCol=0,clen=row.length;iCol<clen;iCol++){var tile=row[iCol];this.removeTileMonitoringHooks(tile);tile.destroy();}}
this.grid=[];}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.Grid(this.name,this.url,this.params,this.options);}
obj=OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this,[obj]);if(this.tileSize!=null){obj.tileSize=this.tileSize.clone();}
obj.grid=[];return obj;},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this,arguments);bounds=bounds||this.map.getExtent();if(bounds!=null){var forceReTile=!this.grid.length||zoomChanged;var tilesBounds=this.getTilesBounds();if(this.singleTile){if(forceReTile||(!dragging&&!tilesBounds.containsBounds(bounds))){this.initSingleTile(bounds);}}else{if(forceReTile||!tilesBounds.containsBounds(bounds,true)){this.initGriddedTiles(bounds);}else{this.moveGriddedTiles(bounds);}}}},setTileSize:function(size){if(this.singleTile){size=this.map.getSize().clone();size.h=parseInt(size.h*this.ratio);size.w=parseInt(size.w*this.ratio);}
OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this,[size]);},getGridBounds:function(){var msg="The getGridBounds() function is deprecated. It will be "+"removed in 3.0. Please use getTilesBounds() instead.";OpenLayers.Console.warn(msg);return this.getTilesBounds();},getTilesBounds:function(){var bounds=null;if(this.grid.length){var bottom=this.grid.length-1;var bottomLeftTile=this.grid[bottom][0];var right=this.grid[0].length-1;var topRightTile=this.grid[0][right];bounds=new OpenLayers.Bounds(bottomLeftTile.bounds.left,bottomLeftTile.bounds.bottom,topRightTile.bounds.right,topRightTile.bounds.top);}
return bounds;},initSingleTile:function(bounds){var center=bounds.getCenterLonLat();var tileWidth=bounds.getWidth()*this.ratio;var tileHeight=bounds.getHeight()*this.ratio;var tileBounds=new OpenLayers.Bounds(center.lon-(tileWidth/2),center.lat-(tileHeight/2),center.lon+(tileWidth/2),center.lat+(tileHeight/2));var ul=new OpenLayers.LonLat(tileBounds.left,tileBounds.top);var px=this.map.getLayerPxFromLonLat(ul);if(!this.grid.length){this.grid[0]=[];}
var tile=this.grid[0][0];if(!tile){tile=this.addTile(tileBounds,px);this.addTileMonitoringHooks(tile);tile.draw();this.grid[0][0]=tile;}else{tile.moveTo(tileBounds,px);}
this.removeExcessTiles(1,1);},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left-extent.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=extent.left+tilecol*tilelon;var offsetlat=bounds.top-(extent.bottom+tilelat);var tilerow=Math.ceil(offsetlat/tilelat)+this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=-tilerowremain*this.tileSize.h;var tileoffsetlat=extent.bottom+tilerow*tilelat;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},initGriddedTiles:function(bounds){var viewSize=this.map.getSize();var minRows=Math.ceil(viewSize.h/this.tileSize.h)+
Math.max(1,2*this.buffer);var minCols=Math.ceil(viewSize.w/this.tileSize.w)+
Math.max(1,2*this.buffer);var extent=this.maxExtent;var resolution=this.map.getResolution();var tileLayout=this.calculateGridLayout(bounds,extent,resolution);var tileoffsetx=Math.round(tileLayout.tileoffsetx);var tileoffsety=Math.round(tileLayout.tileoffsety);var tileoffsetlon=tileLayout.tileoffsetlon;var tileoffsetlat=tileLayout.tileoffsetlat;var tilelon=tileLayout.tilelon;var tilelat=tileLayout.tilelat;this.origin=new OpenLayers.Pixel(tileoffsetx,tileoffsety);var startX=tileoffsetx;var startLon=tileoffsetlon;var rowidx=0;var layerContainerDivLeft=parseInt(this.map.layerContainerDiv.style.left);var layerContainerDivTop=parseInt(this.map.layerContainerDiv.style.top);do{var row=this.grid[rowidx++];if(!row){row=[];this.grid.push(row);}
tileoffsetlon=startLon;tileoffsetx=startX;var colidx=0;do{var tileBounds=new OpenLayers.Bounds(tileoffsetlon,tileoffsetlat,tileoffsetlon+tilelon,tileoffsetlat+tilelat);var x=tileoffsetx;x-=layerContainerDivLeft;var y=tileoffsety;y-=layerContainerDivTop;var px=new OpenLayers.Pixel(x,y);var tile=row[colidx++];if(!tile){tile=this.addTile(tileBounds,px);this.addTileMonitoringHooks(tile);row.push(tile);}else{tile.moveTo(tileBounds,px,false);}
tileoffsetlon+=tilelon;tileoffsetx+=this.tileSize.w;}while((tileoffsetlon<=bounds.right+tilelon*this.buffer)||colidx<minCols);tileoffsetlat-=tilelat;tileoffsety+=this.tileSize.h;}while((tileoffsetlat>=bounds.bottom-tilelat*this.buffer)||rowidx<minRows);this.removeExcessTiles(rowidx,colidx);this.spiralTileLoad();},spiralTileLoad:function(){var tileQueue=[];var directions=["right","down","left","up"];var iRow=0;var iCell=-1;var direction=OpenLayers.Util.indexOf(directions,"right");var directionsTried=0;while(directionsTried<directions.length){var testRow=iRow;var testCell=iCell;switch(directions[direction]){case"right":testCell++;break;case"down":testRow++;break;case"left":testCell--;break;case"up":testRow--;break;}
var tile=null;if((testRow<this.grid.length)&&(testRow>=0)&&(testCell<this.grid[0].length)&&(testCell>=0)){tile=this.grid[testRow][testCell];}
if((tile!=null)&&(!tile.queued)){tileQueue.unshift(tile);tile.queued=true;directionsTried=0;iRow=testRow;iCell=testCell;}else{direction=(direction+1)%4;directionsTried++;}}
for(var i=0,len=tileQueue.length;i<len;i++){var tile=tileQueue[i];tile.draw();tile.queued=false;}},addTile:function(bounds,position){},addTileMonitoringHooks:function(tile){tile.onLoadStart=function(){if(this.numLoadingTiles==0){this.events.triggerEvent("loadstart");}
this.numLoadingTiles++;};tile.events.register("loadstart",this,tile.onLoadStart);tile.onLoadEnd=function(){this.numLoadingTiles--;this.events.triggerEvent("tileloaded");if(this.numLoadingTiles==0){this.events.triggerEvent("loadend");}};tile.events.register("loadend",this,tile.onLoadEnd);tile.events.register("unload",this,tile.onLoadEnd);},removeTileMonitoringHooks:function(tile){tile.unload();tile.events.un({"loadstart":tile.onLoadStart,"loadend":tile.onLoadEnd,"unload":tile.onLoadEnd,scope:this});},moveGriddedTiles:function(bounds){var buffer=this.buffer||1;while(true){var tlLayer=this.grid[0][0].position;var tlViewPort=this.map.getViewPortPxFromLayerPx(tlLayer);if(tlViewPort.x>-this.tileSize.w*(buffer-1)){this.shiftColumn(true);}else if(tlViewPort.x<-this.tileSize.w*buffer){this.shiftColumn(false);}else if(tlViewPort.y>-this.tileSize.h*(buffer-1)){this.shiftRow(true);}else if(tlViewPort.y<-this.tileSize.h*buffer){this.shiftRow(false);}else{break;}};},shiftRow:function(prepend){var modelRowIndex=(prepend)?0:(this.grid.length-1);var grid=this.grid;var modelRow=grid[modelRowIndex];var resolution=this.map.getResolution();var deltaY=(prepend)?-this.tileSize.h:this.tileSize.h;var deltaLat=resolution*-deltaY;var row=(prepend)?grid.pop():grid.shift();for(var i=0,len=modelRow.length;i<len;i++){var modelTile=modelRow[i];var bounds=modelTile.bounds.clone();var position=modelTile.position.clone();bounds.bottom=bounds.bottom+deltaLat;bounds.top=bounds.top+deltaLat;position.y=position.y+deltaY;row[i].moveTo(bounds,position);}
if(prepend){grid.unshift(row);}else{grid.push(row);}},shiftColumn:function(prepend){var deltaX=(prepend)?-this.tileSize.w:this.tileSize.w;var resolution=this.map.getResolution();var deltaLon=resolution*deltaX;for(var i=0,len=this.grid.length;i<len;i++){var row=this.grid[i];var modelTileIndex=(prepend)?0:(row.length-1);var modelTile=row[modelTileIndex];var bounds=modelTile.bounds.clone();var position=modelTile.position.clone();bounds.left=bounds.left+deltaLon;bounds.right=bounds.right+deltaLon;position.x=position.x+deltaX;var tile=prepend?this.grid[i].pop():this.grid[i].shift();tile.moveTo(bounds,position);if(prepend){row.unshift(tile);}else{row.push(tile);}}},removeExcessTiles:function(rows,columns){while(this.grid.length>rows){var row=this.grid.pop();for(var i=0,l=row.length;i<l;i++){var tile=row[i];this.removeTileMonitoringHooks(tile);tile.destroy();}}
while(this.grid[0].length>columns){for(var i=0,l=this.grid.length;i<l;i++){var row=this.grid[i];var tile=row.pop();this.removeTileMonitoringHooks(tile);tile.destroy();}}},onMapResize:function(){if(this.singleTile){this.clearGrid();this.setTileSize();}},getTileBounds:function(viewPortPx){var maxExtent=this.maxExtent;var resolution=this.getResolution();var tileMapWidth=resolution*this.tileSize.w;var tileMapHeight=resolution*this.tileSize.h;var mapPoint=this.getLonLatFromViewPortPx(viewPortPx);var tileLeft=maxExtent.left+(tileMapWidth*Math.floor((mapPoint.lon-
maxExtent.left)/tileMapWidth));var tileBottom=maxExtent.bottom+(tileMapHeight*Math.floor((mapPoint.lat-
maxExtent.bottom)/tileMapHeight));return new OpenLayers.Bounds(tileLeft,tileBottom,tileLeft+tileMapWidth,tileBottom+tileMapHeight);},CLASS_NAME:"OpenLayers.Layer.Grid"});OpenLayers.Layer.MultiMap=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:1,MAX_ZOOM_LEVEL:17,RESOLUTIONS:[9,1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125],type:null,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();this.RESOLUTIONS.unshift(10);}},loadMapObject:function(){try{this.mapObject=new MultimapViewer(this.div);}catch(e){}},getWarningHTML:function(){return OpenLayers.i18n("getLayerWarning",{'layerType':"MM",'layerLib':"MultiMap"});},setMapObjectCenter:function(center,zoom){this.mapObject.goToPosition(center,zoom);},getMapObjectCenter:function(){return this.mapObject.getCurrentPosition();},getMapObjectZoom:function(){return this.mapObject.getZoomFactor();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){moPixel.x=moPixel.x-(this.map.getSize().w/2);moPixel.y=moPixel.y-(this.map.getSize().h/2);return this.mapObject.getMapPositionAt(moPixel);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.geoPosToContainerPixels(moLonLat);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.lon,moLonLat.lat).lon:moLonLat.lon;},getLatitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.lon,moLonLat.lat).lat:moLonLat.lat;},getMapObjectLonLatFromLonLat:function(lon,lat){var mmLatLon;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);mmLatLon=new MMLatLon(lonlat.lat,lonlat.lon);}else{mmLatLon=new MMLatLon(lat,lon);}
return mmLatLon;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return new MMPoint(x,y);},CLASS_NAME:"OpenLayers.Layer.MultiMap"});OpenLayers.Layer.Text=OpenLayers.Class(OpenLayers.Layer.Markers,{location:null,features:null,formatOptions:null,selectedFeature:null,initialize:function(name,options){OpenLayers.Layer.Markers.prototype.initialize.apply(this,arguments);this.features=new Array();},destroy:function(){OpenLayers.Layer.Markers.prototype.destroy.apply(this,arguments);this.clearFeatures();this.features=null;},loadText:function(){if(!this.loaded){if(this.location!=null){var onFail=function(e){this.events.triggerEvent("loadend");};this.events.triggerEvent("loadstart");OpenLayers.Request.GET({url:this.location,success:this.parseData,failure:onFail,scope:this});this.loaded=true;}}},moveTo:function(bounds,zoomChanged,minor){OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);if(this.visibility&&!this.loaded){this.loadText();}},parseData:function(ajaxRequest){var text=ajaxRequest.responseText;var options={};OpenLayers.Util.extend(options,this.formatOptions);if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
var parser=new OpenLayers.Format.Text(options);var features=parser.read(text);for(var i=0,len=features.length;i<len;i++){var data={};var feature=features[i];var location;var iconSize,iconOffset;location=new OpenLayers.LonLat(feature.geometry.x,feature.geometry.y);if(feature.style.graphicWidth&&feature.style.graphicHeight){iconSize=new OpenLayers.Size(feature.style.graphicWidth,feature.style.graphicHeight);}
if(feature.style.graphicXOffset!==undefined&&feature.style.graphicYOffset!==undefined){iconOffset=new OpenLayers.Pixel(feature.style.graphicXOffset,feature.style.graphicYOffset);}
if(feature.style.externalGraphic!=null){data.icon=new OpenLayers.Icon(feature.style.externalGraphic,iconSize,iconOffset);}else{data.icon=OpenLayers.Marker.defaultIcon();if(iconSize!=null){data.icon.setSize(iconSize);}}
if((feature.attributes.title!=null)&&(feature.attributes.description!=null)){data['popupContentHTML']='<h2>'+feature.attributes.title+'</h2>'+'<p>'+feature.attributes.description+'</p>';}
data['overflow']=feature.attributes.overflow||"auto";var markerFeature=new OpenLayers.Feature(this,location,data);this.features.push(markerFeature);var marker=markerFeature.createMarker();if((feature.attributes.title!=null)&&(feature.attributes.description!=null)){marker.events.register('click',markerFeature,this.markerClick);}
this.addMarker(marker);}
this.events.triggerEvent("loadend");},markerClick:function(evt){var sameMarkerClicked=(this==this.layer.selectedFeature);this.layer.selectedFeature=(!sameMarkerClicked)?this:null;for(var i=0,len=this.layer.map.popups.length;i<len;i++){this.layer.map.removePopup(this.layer.map.popups[i]);}
if(!sameMarkerClicked){this.layer.map.addPopup(this.createPopup());}
OpenLayers.Event.stop(evt);},clearFeatures:function(){if(this.features!=null){while(this.features.length>0){var feature=this.features[0];OpenLayers.Util.removeItem(this.features,feature);feature.destroy();}}},CLASS_NAME:"OpenLayers.Layer.Text"});OpenLayers.Layer.VirtualEarth=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:1,MAX_ZOOM_LEVEL:17,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125,0.00002145767211914062],type:null,sphericalMercator:false,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();}},loadMapObject:function(){var veDiv=OpenLayers.Util.createDiv(this.name);var sz=this.map.getSize();veDiv.style.width=sz.w+"px";veDiv.style.height=sz.h+"px";this.div.appendChild(veDiv);try{this.mapObject=new VEMap(this.name);}catch(e){}
if(this.mapObject!=null){try{this.mapObject.LoadMap(null,null,this.type,true);this.mapObject.AttachEvent("onmousedown",function(){return true;});}catch(e){}
this.mapObject.HideDashboard();}
if(!this.mapObject||!this.mapObject.vemapcontrol||!this.mapObject.vemapcontrol.PanMap||(typeof this.mapObject.vemapcontrol.PanMap!="function")){this.dragPanMapObject=null;}},getWarningHTML:function(){return OpenLayers.i18n("getLayerWarning",{'layerType':'VE','layerLib':'VirtualEarth'});},setMapObjectCenter:function(center,zoom){this.mapObject.SetCenterAndZoom(center,zoom);},getMapObjectCenter:function(){return this.mapObject.GetCenter();},dragPanMapObject:function(dX,dY){this.mapObject.vemapcontrol.PanMap(dX,-dY);},getMapObjectZoom:function(){return this.mapObject.GetZoomLevel();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){return(typeof VEPixel!='undefined')?this.mapObject.PixelToLatLong(moPixel):this.mapObject.PixelToLatLong(moPixel.x,moPixel.y);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.LatLongToPixel(moLonLat);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Longitude,moLonLat.Latitude).lon:moLonLat.Longitude;},getLatitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Longitude,moLonLat.Latitude).lat:moLonLat.Latitude;},getMapObjectLonLatFromLonLat:function(lon,lat){var veLatLong;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);veLatLong=new VELatLong(lonlat.lat,lonlat.lon);}else{veLatLong=new VELatLong(lat,lon);}
return veLatLong;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return(typeof VEPixel!='undefined')?new VEPixel(x,y):new Msn.VE.Pixel(x,y);},CLASS_NAME:"OpenLayers.Layer.VirtualEarth"});OpenLayers.Layer.Yahoo=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:0,MAX_ZOOM_LEVEL:17,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125,0.00002145767211914062,0.00001072883605957031],type:null,sphericalMercator:false,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();}},loadMapObject:function(){try{var size=this.getMapObjectSizeFromOLSize(this.map.getSize());this.mapObject=new YMap(this.div,this.type,size);this.mapObject.disableKeyControls();this.mapObject.disableDragMap();if(!this.mapObject.moveByXY||(typeof this.mapObject.moveByXY!="function")){this.dragPanMapObject=null;}}catch(e){}},onMapResize:function(){try{var size=this.getMapObjectSizeFromOLSize(this.map.getSize());this.mapObject.resizeTo(size);}catch(e){}},setMap:function(map){OpenLayers.Layer.EventPane.prototype.setMap.apply(this,arguments);this.map.events.register("moveend",this,this.fixYahooEventPane);},fixYahooEventPane:function(){var yahooEventPane=OpenLayers.Util.getElement("ygddfdiv");if(yahooEventPane!=null){if(yahooEventPane.parentNode!=null){yahooEventPane.parentNode.removeChild(yahooEventPane);}
this.map.events.unregister("moveend",this,this.fixYahooEventPane);}},getWarningHTML:function(){return OpenLayers.i18n("getLayerWarning",{'layerType':'Yahoo','layerLib':'Yahoo'});},getOLZoomFromMapObjectZoom:function(moZoom){var zoom=null;if(moZoom!=null){zoom=OpenLayers.Layer.FixedZoomLevels.prototype.getOLZoomFromMapObjectZoom.apply(this,[moZoom]);zoom=18-zoom;}
return zoom;},getMapObjectZoomFromOLZoom:function(olZoom){var zoom=null;if(olZoom!=null){zoom=OpenLayers.Layer.FixedZoomLevels.prototype.getMapObjectZoomFromOLZoom.apply(this,[olZoom]);zoom=18-zoom;}
return zoom;},setMapObjectCenter:function(center,zoom){this.mapObject.drawZoomAndCenter(center,zoom);},getMapObjectCenter:function(){return this.mapObject.getCenterLatLon();},dragPanMapObject:function(dX,dY){this.mapObject.moveByXY({'x':-dX,'y':dY});},getMapObjectZoom:function(){return this.mapObject.getZoomLevel();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){return this.mapObject.convertXYLatLon(moPixel);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.convertLatLonXY(moLonLat);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Lon,moLonLat.Lat).lon:moLonLat.Lon;},getLatitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Lon,moLonLat.Lat).lat:moLonLat.Lat;},getMapObjectLonLatFromLonLat:function(lon,lat){var yLatLong;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);yLatLong=new YGeoPoint(lonlat.lat,lonlat.lon);}else{yLatLong=new YGeoPoint(lat,lon);}
return yLatLong;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return new YCoordPoint(x,y);},getMapObjectSizeFromOLSize:function(olSize){return new YSize(olSize.w,olSize.h);},CLASS_NAME:"OpenLayers.Layer.Yahoo"});OpenLayers.Protocol.HTTP=OpenLayers.Class(OpenLayers.Protocol,{url:null,headers:null,params:null,callback:null,scope:null,readWithPOST:false,initialize:function(options){this.params={};this.headers={};OpenLayers.Protocol.prototype.initialize.apply(this,arguments);},destroy:function(){this.params=null;this.headers=null;OpenLayers.Protocol.prototype.destroy.apply(this);},createCallback:function(method,response,options){return OpenLayers.Function.bind(function(){method.apply(this,[response,options]);},this);},read:function(options){options=OpenLayers.Util.applyDefaults(options,this.options);var readWithPOST=(options.readWithPOST!==undefined)?options.readWithPOST:this.readWithPOST;var resp=new OpenLayers.Protocol.Response({requestType:"read"});if(options.filter&&options.filter instanceof OpenLayers.Filter.Spatial){if(options.filter.type==OpenLayers.Filter.Spatial.BBOX){options.params=OpenLayers.Util.extend(options.params,{bbox:options.filter.value.toArray()});}}
if(readWithPOST){resp.priv=OpenLayers.Request.POST({url:options.url,callback:this.createCallback(this.handleRead,resp,options),data:OpenLayers.Util.getParameterString(options.params),headers:{"Content-Type":"application/x-www-form-urlencoded"}});}else{resp.priv=OpenLayers.Request.GET({url:options.url,callback:this.createCallback(this.handleRead,resp,options),params:options.params,headers:options.headers});}
return resp;},handleRead:function(resp,options){this.handleResponse(resp,options);},create:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=new OpenLayers.Protocol.Response({reqFeatures:features,requestType:"create"});resp.priv=OpenLayers.Request.POST({url:options.url,callback:this.createCallback(this.handleCreate,resp,options),headers:options.headers,data:this.format.write(features)});return resp;},handleCreate:function(resp,options){this.handleResponse(resp,options);},update:function(feature,options){var url=options.url||feature.url||this.options.url;options=OpenLayers.Util.applyDefaults(options,this.options);var resp=new OpenLayers.Protocol.Response({reqFeatures:feature,requestType:"update"});resp.priv=OpenLayers.Request.PUT({url:url,callback:this.createCallback(this.handleUpdate,resp,options),headers:options.headers,data:this.format.write(feature)});return resp;},handleUpdate:function(resp,options){this.handleResponse(resp,options);},"delete":function(feature,options){var url=options.url||feature.url||this.options.url;options=OpenLayers.Util.applyDefaults(options,this.options);var resp=new OpenLayers.Protocol.Response({reqFeatures:feature,requestType:"delete"});resp.priv=OpenLayers.Request.DELETE({url:url,callback:this.createCallback(this.handleDelete,resp,options),headers:options.headers});return resp;},handleDelete:function(resp,options){this.handleResponse(resp,options);},handleResponse:function(resp,options){var request=resp.priv;if(options.callback){if(request.status>=200&&request.status<300){if(resp.requestType!="delete"){resp.features=this.parseFeatures(request);}
resp.code=OpenLayers.Protocol.Response.SUCCESS;}else{resp.code=OpenLayers.Protocol.Response.FAILURE;}
options.callback.call(options.scope,resp);}},parseFeatures:function(request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
if(!doc||doc.length<=0){return null;}
return this.format.read(doc);},commit:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=[],nResponses=0;var types={};types[OpenLayers.State.INSERT]=[];types[OpenLayers.State.UPDATE]=[];types[OpenLayers.State.DELETE]=[];var feature,list,requestFeatures=[];for(var i=0,len=features.length;i<len;++i){feature=features[i];list=types[feature.state];if(list){list.push(feature);requestFeatures.push(feature);}}
var nRequests=(types[OpenLayers.State.INSERT].length>0?1:0)+
types[OpenLayers.State.UPDATE].length+
types[OpenLayers.State.DELETE].length;var success=true;var finalResponse=new OpenLayers.Protocol.Response({reqFeatures:requestFeatures});function insertCallback(response){var len=response.features?response.features.length:0;var fids=new Array(len);for(var i=0;i<len;++i){fids[i]=response.features[i].fid;}
finalResponse.insertIds=fids;callback.apply(this,[response]);}
function callback(response){this.callUserCallback(response,options);success=success&&response.success();nResponses++;if(nResponses>=nRequests){if(options.callback){finalResponse.code=success?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE;options.callback.apply(options.scope,[finalResponse]);}}}
var queue=types[OpenLayers.State.INSERT];if(queue.length>0){resp.push(this.create(queue,OpenLayers.Util.applyDefaults({callback:insertCallback,scope:this},options.create)));}
queue=types[OpenLayers.State.UPDATE];for(var i=queue.length-1;i>=0;--i){resp.push(this.update(queue[i],OpenLayers.Util.applyDefaults({callback:callback,scope:this},options.update)));}
queue=types[OpenLayers.State.DELETE];for(var i=queue.length-1;i>=0;--i){resp.push(this["delete"](queue[i],OpenLayers.Util.applyDefaults({callback:callback,scope:this},options["delete"])));}
return resp;},abort:function(response){if(response){response.priv.abort();}},callUserCallback:function(resp,options){var opt=options[resp.requestType];if(opt&&opt.callback){opt.callback.call(opt.scope,resp);}},CLASS_NAME:"OpenLayers.Protocol.HTTP"});OpenLayers.Style=OpenLayers.Class({name:null,title:null,description:null,layerName:null,isDefault:false,rules:null,context:null,defaultStyle:null,defaultsPerSymbolizer:false,propertyStyles:null,initialize:function(style,options){OpenLayers.Util.extend(this,options);this.rules=[];if(options&&options.rules){this.addRules(options.rules);}
this.setDefaultStyle(style||OpenLayers.Feature.Vector.style["default"]);},destroy:function(){for(var i=0,len=this.rules.length;i<len;i++){this.rules[i].destroy();this.rules[i]=null;}
this.rules=null;this.defaultStyle=null;},createSymbolizer:function(feature){var style=this.defaultsPerSymbolizer?{}:this.createLiterals(OpenLayers.Util.extend({},this.defaultStyle),feature);var rules=this.rules;var rule,context;var elseRules=[];var appliedRules=false;for(var i=0,len=rules.length;i<len;i++){rule=rules[i];var applies=rule.evaluate(feature);if(applies){if(rule instanceof OpenLayers.Rule&&rule.elseFilter){elseRules.push(rule);}else{appliedRules=true;this.applySymbolizer(rule,style,feature);}}}
if(appliedRules==false&&elseRules.length>0){appliedRules=true;for(var i=0,len=elseRules.length;i<len;i++){this.applySymbolizer(elseRules[i],style,feature);}}
if(rules.length>0&&appliedRules==false){style.display="none";}
return style;},applySymbolizer:function(rule,style,feature){var symbolizerPrefix=feature.geometry?this.getSymbolizerPrefix(feature.geometry):OpenLayers.Style.SYMBOLIZER_PREFIXES[0];var symbolizer=rule.symbolizer[symbolizerPrefix]||rule.symbolizer;if(this.defaultsPerSymbolizer===true){var defaults=this.defaultStyle;OpenLayers.Util.applyDefaults(symbolizer,{pointRadius:defaults.pointRadius});if(symbolizer.stroke===true||symbolizer.graphic===true){OpenLayers.Util.applyDefaults(symbolizer,{strokeWidth:defaults.strokeWidth,strokeColor:defaults.strokeColor,strokeOpacity:defaults.strokeOpacity,strokeDashstyle:defaults.strokeDashstyle,strokeLinecap:defaults.strokeLinecap});}
if(symbolizer.fill===true||symbolizer.graphic===true){OpenLayers.Util.applyDefaults(symbolizer,{fillColor:defaults.fillColor,fillOpacity:defaults.fillOpacity});}
if(symbolizer.graphic===true){OpenLayers.Util.applyDefaults(symbolizer,{pointRadius:this.defaultStyle.pointRadius,externalGraphic:this.defaultStyle.externalGraphic,graphicName:this.defaultStyle.graphicName,graphicOpacity:this.defaultStyle.graphicOpacity,graphicWidth:this.defaultStyle.graphicWidth,graphicHeight:this.defaultStyle.graphicHeight,graphicXOffset:this.defaultStyle.graphicXOffset,graphicYOffset:this.defaultStyle.graphicYOffset});}}
return this.createLiterals(OpenLayers.Util.extend(style,symbolizer),feature);},createLiterals:function(style,feature){var context=this.context||feature.attributes||feature.data;for(var i in this.propertyStyles){style[i]=OpenLayers.Style.createLiteral(style[i],context,feature);}
return style;},findPropertyStyles:function(){var propertyStyles={};var style=this.defaultStyle;this.addPropertyStyles(propertyStyles,style);var rules=this.rules;var symbolizer,value;for(var i=0,len=rules.length;i<len;i++){symbolizer=rules[i].symbolizer;for(var key in symbolizer){value=symbolizer[key];if(typeof value=="object"){this.addPropertyStyles(propertyStyles,value);}else{this.addPropertyStyles(propertyStyles,symbolizer);break;}}}
return propertyStyles;},addPropertyStyles:function(propertyStyles,symbolizer){var property;for(var key in symbolizer){property=symbolizer[key];if(typeof property=="string"&&property.match(/\$\{\w+\}/)){propertyStyles[key]=true;}}
return propertyStyles;},addRules:function(rules){this.rules=this.rules.concat(rules);this.propertyStyles=this.findPropertyStyles();},setDefaultStyle:function(style){this.defaultStyle=style;this.propertyStyles=this.findPropertyStyles();},getSymbolizerPrefix:function(geometry){var prefixes=OpenLayers.Style.SYMBOLIZER_PREFIXES;for(var i=0,len=prefixes.length;i<len;i++){if(geometry.CLASS_NAME.indexOf(prefixes[i])!=-1){return prefixes[i];}}},CLASS_NAME:"OpenLayers.Style"});OpenLayers.Style.createLiteral=function(value,context,feature){if(typeof value=="string"&&value.indexOf("${")!=-1){value=OpenLayers.String.format(value,context,[feature]);value=(isNaN(value)||!value)?value:parseFloat(value);}
return value;};OpenLayers.Style.SYMBOLIZER_PREFIXES=['Point','Line','Polygon','Text'];OpenLayers.Control.Navigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,dragPanOptions:null,zoomBox:null,zoomWheelEnabled:true,handleRightClicks:false,zoomBoxKeyMask:OpenLayers.Handler.MOD_SHIFT,initialize:function(options){this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){this.deactivate();if(this.dragPan){this.dragPan.destroy();}
this.dragPan=null;if(this.zoomBox){this.zoomBox.destroy();}
this.zoomBox=null;OpenLayers.Control.prototype.destroy.apply(this,arguments);},activate:function(){this.dragPan.activate();if(this.zoomWheelEnabled){this.handlers.wheel.activate();}
this.handlers.click.activate();this.zoomBox.activate();return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){this.zoomBox.deactivate();this.dragPan.deactivate();this.handlers.click.deactivate();this.handlers.wheel.deactivate();return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},draw:function(){if(this.handleRightClicks){this.map.viewPortDiv.oncontextmenu=function(){return false;};}
var clickCallbacks={'dblclick':this.defaultDblClick,'dblrightclick':this.defaultDblRightClick};var clickOptions={'double':true,'stopDouble':true};this.handlers.click=new OpenLayers.Handler.Click(this,clickCallbacks,clickOptions);this.dragPan=new OpenLayers.Control.DragPan(OpenLayers.Util.extend({map:this.map},this.dragPanOptions));this.zoomBox=new OpenLayers.Control.ZoomBox({map:this.map,keyMask:this.zoomBoxKeyMask});this.dragPan.draw();this.zoomBox.draw();this.handlers.wheel=new OpenLayers.Handler.MouseWheel(this,{"up":this.wheelUp,"down":this.wheelDown});this.activate();},defaultDblClick:function(evt){var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom+1);},defaultDblRightClick:function(evt){var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom-1);},wheelChange:function(evt,deltaZ){var newZoom=this.map.getZoom()+deltaZ;if(!this.map.isValidZoomLevel(newZoom)){return;}
var size=this.map.getSize();var deltaX=size.w/2-evt.xy.x;var deltaY=evt.xy.y-size.h/2;var newRes=this.map.baseLayer.getResolutionForZoom(newZoom);var zoomPoint=this.map.getLonLatFromPixel(evt.xy);var newCenter=new OpenLayers.LonLat(zoomPoint.lon+deltaX*newRes,zoomPoint.lat+deltaY*newRes);this.map.setCenter(newCenter,newZoom);},wheelUp:function(evt){this.wheelChange(evt,1);},wheelDown:function(evt){this.wheelChange(evt,-1);},disableZoomWheel:function(){this.zoomWheelEnabled=false;this.handlers.wheel.deactivate();},enableZoomWheel:function(){this.zoomWheelEnabled=true;if(this.active){this.handlers.wheel.activate();}},CLASS_NAME:"OpenLayers.Control.Navigation"});OpenLayers.Filter=OpenLayers.Class({initialize:function(options){OpenLayers.Util.extend(this,options);},destroy:function(){},evaluate:function(context){return true;},clone:function(){return null;},CLASS_NAME:"OpenLayers.Filter"});OpenLayers.Geometry=OpenLayers.Class({id:null,parent:null,bounds:null,initialize:function(){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){this.id=null;this.bounds=null;},clone:function(){return new OpenLayers.Geometry();},setBounds:function(bounds){if(bounds){this.bounds=bounds.clone();}},clearBounds:function(){this.bounds=null;if(this.parent){this.parent.clearBounds();}},extendBounds:function(newBounds){var bounds=this.getBounds();if(!bounds){this.setBounds(newBounds);}else{this.bounds.extend(newBounds);}},getBounds:function(){if(this.bounds==null){this.calculateBounds();}
return this.bounds;},calculateBounds:function(){},distanceTo:function(geometry,options){},getVertices:function(nodes){},atPoint:function(lonlat,toleranceLon,toleranceLat){var atPoint=false;var bounds=this.getBounds();if((bounds!=null)&&(lonlat!=null)){var dX=(toleranceLon!=null)?toleranceLon:0;var dY=(toleranceLat!=null)?toleranceLat:0;var toleranceBounds=new OpenLayers.Bounds(this.bounds.left-dX,this.bounds.bottom-dY,this.bounds.right+dX,this.bounds.top+dY);atPoint=toleranceBounds.containsLonLat(lonlat);}
return atPoint;},getLength:function(){return 0.0;},getArea:function(){return 0.0;},getCentroid:function(){return null;},toString:function(){return OpenLayers.Format.WKT.prototype.write(new OpenLayers.Feature.Vector(this));},CLASS_NAME:"OpenLayers.Geometry"});OpenLayers.Geometry.fromWKT=function(wkt){var format=arguments.callee.format;if(!format){format=new OpenLayers.Format.WKT();arguments.callee.format=format;}
var geom;var result=format.read(wkt);if(result instanceof OpenLayers.Feature.Vector){geom=result.geometry;}else if(result instanceof Array){var len=result.length;var components=new Array(len);for(var i=0;i<len;++i){components[i]=result[i].geometry;}
geom=new OpenLayers.Geometry.Collection(components);}
return geom;};OpenLayers.Geometry.segmentsIntersect=function(seg1,seg2,options){var point=options&&options.point;var tolerance=options&&options.tolerance;var intersection=false;var x11_21=seg1.x1-seg2.x1;var y11_21=seg1.y1-seg2.y1;var x12_11=seg1.x2-seg1.x1;var y12_11=seg1.y2-seg1.y1;var y22_21=seg2.y2-seg2.y1;var x22_21=seg2.x2-seg2.x1;var d=(y22_21*x12_11)-(x22_21*y12_11);var n1=(x22_21*y11_21)-(y22_21*x11_21);var n2=(x12_11*y11_21)-(y12_11*x11_21);if(d==0){if(n1==0&&n2==0){intersection=true;}}else{var along1=n1/d;var along2=n2/d;if(along1>=0&&along1<=1&&along2>=0&&along2<=1){if(!point){intersection=true;}else{var x=seg1.x1+(along1*x12_11);var y=seg1.y1+(along1*y12_11);intersection=new OpenLayers.Geometry.Point(x,y);}}}
if(tolerance){var dist;if(intersection){if(point){var segs=[seg1,seg2];var seg,x,y;outer:for(var i=0;i<2;++i){seg=segs[i];for(var j=1;j<3;++j){x=seg["x"+j];y=seg["y"+j];dist=Math.sqrt(Math.pow(x-intersection.x,2)+
Math.pow(y-intersection.y,2));if(dist<tolerance){intersection.x=x;intersection.y=y;break outer;}}}}}else{var segs=[seg1,seg2];var source,target,x,y,p,result;outer:for(var i=0;i<2;++i){source=segs[i];target=segs[(i+1)%2];for(var j=1;j<3;++j){p={x:source["x"+j],y:source["y"+j]};result=OpenLayers.Geometry.distanceToSegment(p,target);if(result.distance<tolerance){if(point){intersection=new OpenLayers.Geometry.Point(p.x,p.y);}else{intersection=true;}
break outer;}}}}}
return intersection;};OpenLayers.Geometry.distanceToSegment=function(point,segment){var x0=point.x;var y0=point.y;var x1=segment.x1;var y1=segment.y1;var x2=segment.x2;var y2=segment.y2;var dx=x2-x1;var dy=y2-y1;var along=((dx*(x0-x1))+(dy*(y0-y1)))/(Math.pow(dx,2)+Math.pow(dy,2));var x,y;if(along<=0.0){x=x1;y=y1;}else if(along>=1.0){x=x2;y=y2;}else{x=x1+along*dx;y=y1+along*dy;}
return{distance:Math.sqrt(Math.pow(x-x0,2)+Math.pow(y-y0,2)),x:x,y:y};};OpenLayers.Layer.ArcGIS93Rest=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{format:"png"},isBaseLayer:true,initialize:function(name,url,params,options){var newArguments=[];params=OpenLayers.Util.upperCaseObject(params);newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));if(this.params.TRANSPARENT&&this.params.TRANSPARENT.toString().toLowerCase()=="true"){if((options==null)||(!options.isBaseLayer)){this.isBaseLayer=false;}
if(this.params.FORMAT=="jpg"){this.params.FORMAT=OpenLayers.Util.alphaHack()?"gif":"png";}}},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.ArcGIS93Rest(this.name,this.url,this.params,this.options);}
obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var projWords=this.projection.getCode().split(":");var srid=projWords[projWords.length-1];var imageSize=this.getImageSize();var newParams={'BBOX':bounds.toBBOX(),'SIZE':imageSize.w+","+imageSize.h,'F':"image",'BBOXSR':srid,'IMAGESR':srid};if(this.layerDefs){var layerDefStrList=[];var layerID;for(layerID in this.layerDefs){if(this.layerDefs.hasOwnProperty(layerID)){if(this.layerDefs[layerID]){layerDefStrList.push(layerID);layerDefStrList.push(":");layerDefStrList.push(this.layerDefs[layerID]);layerDefStrList.push(";");}}}
if(layerDefStrList.length>0){newParams['LAYERDEFS']=layerDefStrList.join("");}}
var requestString=this.getFullRequestString(newParams);return requestString;},setLayerFilter:function(id,queryDef){if(!this.layerDefs){this.layerDefs={};}
if(queryDef){this.layerDefs[id]=queryDef;}else{delete this.layerDefs[id];}},clearLayerFilter:function(id){if(id){delete this.layerDefs[id];}else{delete this.layerDefs;}},mergeNewParams:function(newParams){var upperParams=OpenLayers.Util.upperCaseObject(newParams);var newArguments=[upperParams];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,newArguments);},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},CLASS_NAME:"OpenLayers.Layer.ArcGIS93Rest"});OpenLayers.Layer.KaMap=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,units:null,resolution:OpenLayers.DOTS_PER_INCH,DEFAULT_PARAMS:{i:'jpeg',map:''},initialize:function(name,url,params,options){var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);},getURL:function(bounds){bounds=this.adjustBounds(bounds);var mapRes=this.map.getResolution();var scale=Math.round((this.map.getScale()*10000))/10000;var pX=Math.round(bounds.left/mapRes);var pY=-Math.round(bounds.top/mapRes);return this.getFullRequestString({t:pY,l:pX,s:scale});},addTile:function(bounds,position){var url=this.getURL(bounds);return new OpenLayers.Tile.Image(this,position,bounds,url,this.tileSize);},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=tilecol*tilelon;var offsetlat=bounds.top;var tilerow=Math.ceil(offsetlat/tilelat)+this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=-(tilerowremain+1)*this.tileSize.h;var tileoffsetlat=tilerow*tilelat;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.KaMap(this.name,this.url,this.params,this.options);}
obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);if(this.tileSize!=null){obj.tileSize=this.tileSize.clone();}
obj.grid=[];return obj;},getTileBounds:function(viewPortPx){var resolution=this.getResolution();var tileMapWidth=resolution*this.tileSize.w;var tileMapHeight=resolution*this.tileSize.h;var mapPoint=this.getLonLatFromViewPortPx(viewPortPx);var tileLeft=tileMapWidth*Math.floor(mapPoint.lon/tileMapWidth);var tileBottom=tileMapHeight*Math.floor(mapPoint.lat/tileMapHeight);return new OpenLayers.Bounds(tileLeft,tileBottom,tileLeft+tileMapWidth,tileBottom+tileMapHeight);},CLASS_NAME:"OpenLayers.Layer.KaMap"});OpenLayers.Layer.MapGuide=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,useHttpTile:false,singleTile:false,useOverlay:false,useAsyncOverlay:true,TILE_PARAMS:{operation:'GETTILEIMAGE',version:'1.2.0'},SINGLE_TILE_PARAMS:{operation:'GETMAPIMAGE',format:'PNG',locale:'en',clip:'1',version:'1.0.0'},OVERLAY_PARAMS:{operation:'GETDYNAMICMAPOVERLAYIMAGE',format:'PNG',locale:'en',clip:'1',version:'2.0.0'},FOLDER_PARAMS:{tileColumnsPerFolder:30,tileRowsPerFolder:30,format:'png',querystring:null},defaultSize:new OpenLayers.Size(300,300),initialize:function(name,url,params,options){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.transparent!="true")&&(this.transparent!=true));}
if(options&&options.useOverlay!=null){this.useOverlay=options.useOverlay;}
if(this.singleTile){if(this.useOverlay){OpenLayers.Util.applyDefaults(this.params,this.OVERLAY_PARAMS);if(!this.useAsyncOverlay){this.params.version="1.0.0";}}else{OpenLayers.Util.applyDefaults(this.params,this.SINGLE_TILE_PARAMS);}}else{if(this.useHttpTile){OpenLayers.Util.applyDefaults(this.params,this.FOLDER_PARAMS);}else{OpenLayers.Util.applyDefaults(this.params,this.TILE_PARAMS);}
this.setTileSize(this.defaultSize);}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapGuide(this.name,this.url,this.params,this.options);}
obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getURL:function(bounds){var url;var center=bounds.getCenterLonLat();var mapSize=this.map.getCurrentSize();if(this.singleTile){var params={setdisplaydpi:OpenLayers.DOTS_PER_INCH,setdisplayheight:mapSize.h*this.ratio,setdisplaywidth:mapSize.w*this.ratio,setviewcenterx:center.lon,setviewcentery:center.lat,setviewscale:this.map.getScale()};if(this.useOverlay&&!this.useAsyncOverlay){var getVisParams={};getVisParams=OpenLayers.Util.extend(getVisParams,params);getVisParams.operation="GETVISIBLEMAPEXTENT";getVisParams.version="1.0.0";getVisParams.session=this.params.session;getVisParams.mapName=this.params.mapName;getVisParams.format='text/xml';url=this.getFullRequestString(getVisParams);OpenLayers.Request.GET({url:url,async:false});}
url=this.getFullRequestString(params);}else{var currentRes=this.map.getResolution();var colidx=Math.floor((bounds.left-this.maxExtent.left)/currentRes);colidx=Math.round(colidx/this.tileSize.w);var rowidx=Math.floor((this.maxExtent.top-bounds.top)/currentRes);rowidx=Math.round(rowidx/this.tileSize.h);if(this.useHttpTile){url=this.getImageFilePath({tilecol:colidx,tilerow:rowidx,scaleindex:this.resolutions.length-this.map.zoom-1});}else{url=this.getFullRequestString({tilecol:colidx,tilerow:rowidx,scaleindex:this.resolutions.length-this.map.zoom-1});}}
return url;},getFullRequestString:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;if(typeof url=="object"){url=url[Math.floor(Math.random()*url.length)];}
var requestString=url;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getArgs(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}}
var paramsString=OpenLayers.Util.getParameterString(allParams);paramsString=paramsString.replace(/,/g,"+");if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}}
return requestString;},getImageFilePath:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;if(typeof url=="object"){url=url[Math.floor(Math.random()*url.length)];}
var requestString=url;var tileRowGroup="";var tileColGroup="";if(newParams.tilerow<0){tileRowGroup='-';}
if(newParams.tilerow==0){tileRowGroup+='0';}else{tileRowGroup+=Math.floor(Math.abs(newParams.tilerow/this.params.tileRowsPerFolder))*this.params.tileRowsPerFolder;}
if(newParams.tilecol<0){tileColGroup='-';}
if(newParams.tilecol==0){tileColGroup+='0';}else{tileColGroup+=Math.floor(Math.abs(newParams.tilecol/this.params.tileColumnsPerFolder))*this.params.tileColumnsPerFolder;}
var tilePath='/S'+Math.floor(newParams.scaleindex)
+'/'+this.params.basemaplayergroupname
+'/R'+tileRowGroup
+'/C'+tileColGroup
+'/'+(newParams.tilerow%this.params.tileRowsPerFolder)
+'_'+(newParams.tilecol%this.params.tileColumnsPerFolder)
+'.'+this.params.format;if(this.params.querystring){tilePath+="?"+this.params.querystring;}
requestString+=tilePath;return requestString;},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left-extent.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=extent.left+tilecol*tilelon;var offsetlat=extent.top-bounds.top+tilelat;var tilerow=Math.floor(offsetlat/tilelat)-this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=tilerowremain*this.tileSize.h;var tileoffsetlat=extent.top-tilelat*tilerow;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},CLASS_NAME:"OpenLayers.Layer.MapGuide"});OpenLayers.Layer.MapServer=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{mode:"map",map_imagetype:"png"},initialize:function(name,url,params,options){var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.params.transparent!="true")&&(this.params.transparent!=true));}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapServer(this.name,this.url,this.params,this.options);}
obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getURL:function(bounds){bounds=this.adjustBounds(bounds);var extent=[bounds.left,bounds.bottom,bounds.right,bounds.top];var imageSize=this.getImageSize();var url=this.getFullRequestString({mapext:extent,imgext:extent,map_size:[imageSize.w,imageSize.h],imgx:imageSize.w/2,imgy:imageSize.h/2,imgxy:[imageSize.w,imageSize.h]});return url;},getFullRequestString:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var paramsString=OpenLayers.Util.getParameterString(allParams);if(url instanceof Array){url=this.selectUrl(paramsString,url);}
var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}}
paramsString=OpenLayers.Util.getParameterString(allParams);var requestString=url;paramsString=paramsString.replace(/,/g,"+");if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}}
return requestString;},CLASS_NAME:"OpenLayers.Layer.MapServer"});OpenLayers.Layer.TMS=OpenLayers.Class(OpenLayers.Layer.Grid,{serviceVersion:"1.0.0",isBaseLayer:true,tileOrigin:null,serverResolutions:null,initialize:function(name,url,options){var newArguments=[];newArguments.push(name,url,{},options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.TMS(this.name,this.url,this.options);}
obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var res=this.map.getResolution();var x=Math.round((bounds.left-this.tileOrigin.lon)/(res*this.tileSize.w));var y=Math.round((bounds.bottom-this.tileOrigin.lat)/(res*this.tileSize.h));var z=this.serverResolutions!=null?OpenLayers.Util.indexOf(this.serverResolutions,res):this.map.getZoom();var path=this.serviceVersion+"/"+this.layername+"/"+z+"/"+x+"/"+y+"."+this.type;var url=this.url;if(url instanceof Array){url=this.selectUrl(path,url);}
return url+path;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},setMap:function(map){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);if(!this.tileOrigin){this.tileOrigin=new OpenLayers.LonLat(this.map.maxExtent.left,this.map.maxExtent.bottom);}},CLASS_NAME:"OpenLayers.Layer.TMS"});OpenLayers.Layer.TileCache=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,format:'image/png',serverResolutions:null,initialize:function(name,url,layername,options){this.layername=layername;OpenLayers.Layer.Grid.prototype.initialize.apply(this,[name,url,{},options]);this.extension=this.format.split('/')[1].toLowerCase();this.extension=(this.extension=='jpg')?'jpeg':this.extension;},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.TileCache(this.name,this.url,this.layername,this.options);}
obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){var res=this.map.getResolution();var bbox=this.maxExtent;var size=this.tileSize;var tileX=Math.round((bounds.left-bbox.left)/(res*size.w));var tileY=Math.round((bounds.bottom-bbox.bottom)/(res*size.h));var tileZ=this.serverResolutions!=null?OpenLayers.Util.indexOf(this.serverResolutions,res):this.map.getZoom();function zeroPad(number,length){number=String(number);var zeros=[];for(var i=0;i<length;++i){zeros.push('0');}
return zeros.join('').substring(0,length-number.length)+number;}
var components=[this.layername,zeroPad(tileZ,2),zeroPad(parseInt(tileX/1000000),3),zeroPad((parseInt(tileX/1000)%1000),3),zeroPad((parseInt(tileX)%1000),3),zeroPad(parseInt(tileY/1000000),3),zeroPad((parseInt(tileY/1000)%1000),3),zeroPad((parseInt(tileY)%1000),3)+'.'+this.extension];var path=components.join('/');var url=this.url;if(url instanceof Array){url=this.selectUrl(path,url);}
url=(url.charAt(url.length-1)=='/')?url:url+'/';return url+path;},addTile:function(bounds,position){var url=this.getURL(bounds);return new OpenLayers.Tile.Image(this,position,bounds,url,this.tileSize);},CLASS_NAME:"OpenLayers.Layer.TileCache"});OpenLayers.Layer.WMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{service:"WMS",version:"1.1.1",request:"GetMap",styles:"",exceptions:"application/vnd.ogc.se_inimage",format:"image/jpeg"},reproject:false,isBaseLayer:true,encodeBBOX:false,noMagic:false,initialize:function(name,url,params,options){var newArguments=[];params=OpenLayers.Util.upperCaseObject(params);newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));if(!this.noMagic&&this.params.TRANSPARENT&&this.params.TRANSPARENT.toString().toLowerCase()=="true"){if((options==null)||(!options.isBaseLayer)){this.isBaseLayer=false;}
if(this.params.FORMAT=="image/jpeg"){this.params.FORMAT=OpenLayers.Util.alphaHack()?"image/gif":"image/png";}}},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.WMS(this.name,this.url,this.params,this.options);}
obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var imageSize=this.getImageSize();var newParams={'BBOX':this.encodeBBOX?bounds.toBBOX():bounds.toArray(),'WIDTH':imageSize.w,'HEIGHT':imageSize.h};var requestString=this.getFullRequestString(newParams);return requestString;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},mergeNewParams:function(newParams){var upperParams=OpenLayers.Util.upperCaseObject(newParams);var newArguments=[upperParams];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,newArguments);},getFullRequestString:function(newParams,altUrl){var projectionCode=this.map.getProjection();this.params.SRS=(projectionCode=="none")?null:projectionCode;return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments);},CLASS_NAME:"OpenLayers.Layer.WMS"});OpenLayers.Layer.WorldWind=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{},isBaseLayer:true,lzd:null,zoomLevels:null,initialize:function(name,url,lzd,zoomLevels,params,options){this.lzd=lzd;this.zoomLevels=zoomLevels;var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getZoom:function(){var zoom=this.map.getZoom();var extent=this.map.getMaxExtent();zoom=zoom-Math.log(this.maxResolution/(this.lzd/512))/Math.log(2);return zoom;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var zoom=this.getZoom();var extent=this.map.getMaxExtent();var deg=this.lzd/Math.pow(2,this.getZoom());var x=Math.floor((bounds.left-extent.left)/deg);var y=Math.floor((bounds.bottom-extent.bottom)/deg);if(this.map.getResolution()<=(this.lzd/512)&&this.getZoom()<=this.zoomLevels){return this.getFullRequestString({L:zoom,X:x,Y:y});}else{return OpenLayers.Util.getImagesLocation()+"blank.gif";}},CLASS_NAME:"OpenLayers.Layer.WorldWind"});OpenLayers.Layer.XYZ=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,sphericalMercator:false,initialize:function(name,url,options){if(options&&options.sphericalMercator||this.sphericalMercator){options=OpenLayers.Util.extend({maxExtent:new OpenLayers.Bounds(-128*156543.0339,-128*156543.0339,128*156543.0339,128*156543.0339),maxResolution:156543.0339,numZoomLevels:19,units:"m",projection:"EPSG:900913"},options);}
url=url||this.url;name=name||this.name;var newArguments=[name,url,{},options];OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.XYZ(this.name,this.url,this.options);}
obj=OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this,[obj]);if(this.tileSize!=null){obj.tileSize=this.tileSize.clone();}
obj.grid=[];return obj;},getURL:function(bounds){var res=this.map.getResolution();var x=Math.round((bounds.left-this.maxExtent.left)/(res*this.tileSize.w));var y=Math.round((this.maxExtent.top-bounds.top)/(res*this.tileSize.h));var z=this.map.getZoom();var limit=Math.pow(2,z);var url=this.url;var s=''+x+y+z;if(url instanceof Array)
{url=this.selectUrl(s,url);}
var path=OpenLayers.String.format(url,{'x':x,'y':y,'z':z});return path;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},setMap:function(map){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);if(!this.tileOrigin){this.tileOrigin=new OpenLayers.LonLat(this.maxExtent.left,this.maxExtent.bottom);}},CLASS_NAME:"OpenLayers.Layer.XYZ"});OpenLayers.Layer.OSM=OpenLayers.Class(OpenLayers.Layer.XYZ,{name:"OpenStreetMap",attribution:"Data CC-By-SA by <a href='http://openstreetmap.org/'>OpenStreetMap</a>",sphericalMercator:true,url:'http://tile.openstreetmap.org/${z}/${x}/${y}.png',CLASS_NAME:"OpenLayers.Layer.OSM"});OpenLayers.Protocol.SQL.Gears=OpenLayers.Class(OpenLayers.Protocol.SQL,{FID_PREFIX:'__gears_fid__',NULL_GEOMETRY:'__gears_null_geometry__',NULL_FEATURE_STATE:'__gears_null_feature_state__',jsonParser:null,wktParser:null,fidRegExp:null,saveFeatureState:true,typeOfFid:"string",db:null,initialize:function(options){if(!this.supported()){return;}
OpenLayers.Protocol.SQL.prototype.initialize.apply(this,[options]);this.jsonParser=new OpenLayers.Format.JSON();this.wktParser=new OpenLayers.Format.WKT();this.fidRegExp=new RegExp('^'+this.FID_PREFIX);this.initializeDatabase();},initializeDatabase:function(){this.db=google.gears.factory.create('beta.database');this.db.open(this.databaseName);this.db.execute("CREATE TABLE IF NOT EXISTS "+this.tableName+" (fid TEXT UNIQUE, geometry TEXT, properties TEXT,"+"  state TEXT)");},destroy:function(){this.db.close();this.db=null;this.jsonParser=null;this.wktParser=null;OpenLayers.Protocol.SQL.prototype.destroy.apply(this);},supported:function(){return!!(window.google&&google.gears);},read:function(options){options=OpenLayers.Util.applyDefaults(options,this.options);var feature,features=[];var rs=this.db.execute("SELECT * FROM "+this.tableName);while(rs.isValidRow()){feature=this.unfreezeFeature(rs);if(this.evaluateFilter(feature,options.filter)){if(!options.noFeatureStateReset){feature.state=null;}
features.push(feature);}
rs.next();}
rs.close();var resp=new OpenLayers.Protocol.Response({code:OpenLayers.Protocol.Response.SUCCESS,requestType:"read",features:features});if(options&&options.callback){options.callback.call(options.scope,resp);}
return resp;},unfreezeFeature:function(row){var feature;var wkt=row.fieldByName('geometry');if(wkt==this.NULL_GEOMETRY){feature=new OpenLayers.Feature.Vector();}else{feature=this.wktParser.read(wkt);}
feature.attributes=this.jsonParser.read(row.fieldByName('properties'));feature.fid=this.extractFidFromField(row.fieldByName('fid'));var state=row.fieldByName('state');if(state==this.NULL_FEATURE_STATE){state=null;}
feature.state=state;return feature;},extractFidFromField:function(field){if(!field.match(this.fidRegExp)&&this.typeOfFid=="number"){field=parseFloat(field);}
return field;},create:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=this.createOrUpdate(features);resp.requestType="create";if(options&&options.callback){options.callback.call(options.scope,resp);}
return resp;},update:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=this.createOrUpdate(features);resp.requestType="update";if(options&&options.callback){options.callback.call(options.scope,resp);}
return resp;},createOrUpdate:function(features){if(!(features instanceof Array)){features=[features];}
var i,len=features.length,feature;var insertedFeatures=new Array(len);for(i=0;i<len;i++){feature=features[i];var params=this.freezeFeature(feature);this.db.execute("REPLACE INTO "+this.tableName+" (fid, geometry, properties, state)"+" VALUES (?, ?, ?, ?)",params);var clone=feature.clone();clone.fid=this.extractFidFromField(params[0]);insertedFeatures[i]=clone;}
return new OpenLayers.Protocol.Response({code:OpenLayers.Protocol.Response.SUCCESS,features:insertedFeatures,reqFeatures:features});},freezeFeature:function(feature){feature.fid=feature.fid!=null?""+feature.fid:OpenLayers.Util.createUniqueID(this.FID_PREFIX);var geometry=feature.geometry!=null?feature.geometry.toString():this.NULL_GEOMETRY;var properties=this.jsonParser.write(feature.attributes);var state=this.getFeatureStateForFreeze(feature);return[feature.fid,geometry,properties,state];},getFeatureStateForFreeze:function(feature){var state;if(!this.saveFeatureState){state=this.NULL_FEATURE_STATE;}else if(this.createdOffline(feature)){state=OpenLayers.State.INSERT;}else{state=feature.state;}
return state;},"delete":function(features,options){if(!(features instanceof Array)){features=[features];}
options=OpenLayers.Util.applyDefaults(options,this.options);var i,len,feature;for(i=0,len=features.length;i<len;i++){feature=features[i];if(this.saveFeatureState&&!this.createdOffline(feature)){var toDelete=feature.clone();toDelete.fid=feature.fid;if(toDelete.geometry){toDelete.geometry.destroy();toDelete.geometry=null;}
toDelete.state=feature.state;this.createOrUpdate(toDelete);}else{this.db.execute("DELETE FROM "+this.tableName+" WHERE fid = ?",[feature.fid]);}}
var resp=new OpenLayers.Protocol.Response({code:OpenLayers.Protocol.Response.SUCCESS,requestType:"delete",reqFeatures:features});if(options&&options.callback){options.callback.call(options.scope,resp);}
return resp;},createdOffline:function(feature){return(typeof feature.fid=="string"&&!!(feature.fid.match(this.fidRegExp)));},commit:function(features,options){var opt,resp=[],nRequests=0,nResponses=0;function callback(resp){if(++nResponses<nRequests){resp.last=false;}
this.callUserCallback(options,resp);}
var feature,toCreate=[],toUpdate=[],toDelete=[];for(var i=features.length-1;i>=0;i--){feature=features[i];switch(feature.state){case OpenLayers.State.INSERT:toCreate.push(feature);break;case OpenLayers.State.UPDATE:toUpdate.push(feature);break;case OpenLayers.State.DELETE:toDelete.push(feature);break;}}
if(toCreate.length>0){nRequests++;opt=OpenLayers.Util.applyDefaults({"callback":callback,"scope":this},options.create);resp.push(this.create(toCreate,opt));}
if(toUpdate.length>0){nRequests++;opt=OpenLayers.Util.applyDefaults({"callback":callback,"scope":this},options.update);resp.push(this.update(toUpdate,opt));}
if(toDelete.length>0){nRequests++;opt=OpenLayers.Util.applyDefaults({"callback":callback,"scope":this},options["delete"]);resp.push(this["delete"](toDelete,opt));}
return resp;},clear:function(){this.db.execute("DELETE FROM "+this.tableName);},callUserCallback:function(options,resp){var opt=options[resp.requestType];if(opt&&opt.callback){opt.callback.call(opt.scope,resp);}
if(resp.last&&options.callback){options.callback.call(options.scope);}},CLASS_NAME:"OpenLayers.Protocol.SQL.Gears"});OpenLayers.Rule=OpenLayers.Class({id:null,name:'default',title:null,description:null,context:null,filter:null,elseFilter:false,symbolizer:null,minScaleDenominator:null,maxScaleDenominator:null,initialize:function(options){this.symbolizer={};OpenLayers.Util.extend(this,options);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){for(var i in this.symbolizer){this.symbolizer[i]=null;}
this.symbolizer=null;},evaluate:function(feature){var context=this.getContext(feature);var applies=true;if(this.minScaleDenominator||this.maxScaleDenominator){var scale=feature.layer.map.getScale();}
if(this.minScaleDenominator){applies=scale>=OpenLayers.Style.createLiteral(this.minScaleDenominator,context);}
if(applies&&this.maxScaleDenominator){applies=scale<OpenLayers.Style.createLiteral(this.maxScaleDenominator,context);}
if(applies&&this.filter){if(this.filter.CLASS_NAME=="OpenLayers.Filter.FeatureId"){applies=this.filter.evaluate(feature);}else{applies=this.filter.evaluate(context);}}
return applies;},getContext:function(feature){var context=this.context;if(!context){context=feature.attributes||feature.data;}
if(typeof this.context=="function"){context=this.context(feature);}
return context;},clone:function(){var options=OpenLayers.Util.extend({},this);options.symbolizer={};for(var key in this.symbolizer){value=this.symbolizer[key];type=typeof value;if(type==="object"){options.symbolizer[key]=OpenLayers.Util.extend({},value);}else if(type==="string"){options.symbolizer[key]=value;}}
options.filter=this.filter&&this.filter.clone();options.context=this.context&&OpenLayers.Util.extend({},this.context);return new OpenLayers.Rule(options);},CLASS_NAME:"OpenLayers.Rule"});OpenLayers.StyleMap=OpenLayers.Class({styles:null,extendDefault:true,initialize:function(style,options){this.styles={"default":new OpenLayers.Style(OpenLayers.Feature.Vector.style["default"]),"select":new OpenLayers.Style(OpenLayers.Feature.Vector.style["select"]),"temporary":new OpenLayers.Style(OpenLayers.Feature.Vector.style["temporary"]),"delete":new OpenLayers.Style(OpenLayers.Feature.Vector.style["delete"])};if(style instanceof OpenLayers.Style){this.styles["default"]=style;this.styles["select"]=style;this.styles["temporary"]=style;this.styles["delete"]=style;}else if(typeof style=="object"){for(var key in style){if(style[key]instanceof OpenLayers.Style){this.styles[key]=style[key];}else if(typeof style[key]=="object"){this.styles[key]=new OpenLayers.Style(style[key]);}else{this.styles["default"]=new OpenLayers.Style(style);this.styles["select"]=new OpenLayers.Style(style);this.styles["temporary"]=new OpenLayers.Style(style);this.styles["delete"]=new OpenLayers.Style(style);break;}}}
OpenLayers.Util.extend(this,options);},destroy:function(){for(var key in this.styles){this.styles[key].destroy();}
this.styles=null;},createSymbolizer:function(feature,intent){if(!feature){feature=new OpenLayers.Feature.Vector();}
if(!this.styles[intent]){intent="default";}
feature.renderIntent=intent;var defaultSymbolizer={};if(this.extendDefault&&intent!="default"){defaultSymbolizer=this.styles["default"].createSymbolizer(feature);}
return OpenLayers.Util.extend(defaultSymbolizer,this.styles[intent].createSymbolizer(feature));},addUniqueValueRules:function(renderIntent,property,symbolizers,context){var rules=[];for(var value in symbolizers){rules.push(new OpenLayers.Rule({symbolizer:symbolizers[value],context:context,filter:new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,property:property,value:value})}));}
this.styles[renderIntent].addRules(rules);},CLASS_NAME:"OpenLayers.StyleMap"});OpenLayers.Control.NavToolbar=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(options){OpenLayers.Control.Panel.prototype.initialize.apply(this,[options]);this.addControls([new OpenLayers.Control.Navigation(),new OpenLayers.Control.ZoomBox()]);},draw:function(){var div=OpenLayers.Control.Panel.prototype.draw.apply(this,arguments);this.activateControl(this.controls[0]);return div;},CLASS_NAME:"OpenLayers.Control.NavToolbar"});OpenLayers.Filter.Comparison=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,matchCase:true,lowerBoundary:null,upperBoundary:null,initialize:function(options){OpenLayers.Filter.prototype.initialize.apply(this,[options]);},evaluate:function(context){var result=false;switch(this.type){case OpenLayers.Filter.Comparison.EQUAL_TO:var got=context[this.property];var exp=this.value;if(!this.matchCase&&typeof got=="string"&&typeof exp=="string"){result=(got.toUpperCase()==exp.toUpperCase());}else{result=(got==exp);}
break;case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:var got=context[this.property];var exp=this.value;if(!this.matchCase&&typeof got=="string"&&typeof exp=="string"){result=(got.toUpperCase()!=exp.toUpperCase());}else{result=(got!=exp);}
break;case OpenLayers.Filter.Comparison.LESS_THAN:result=context[this.property]<this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN:result=context[this.property]>this.value;break;case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:result=context[this.property]<=this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:result=context[this.property]>=this.value;break;case OpenLayers.Filter.Comparison.BETWEEN:result=(context[this.property]>=this.lowerBoundary)&&(context[this.property]<=this.upperBoundary);break;case OpenLayers.Filter.Comparison.LIKE:var regexp=new RegExp(this.value,"gi");result=regexp.test(context[this.property]);break;}
return result;},value2regex:function(wildCard,singleChar,escapeChar){if(wildCard=="."){var msg="'.' is an unsupported wildCard character for "+"OpenLayers.Filter.Comparison";OpenLayers.Console.error(msg);return null;}
wildCard=wildCard?wildCard:"*";singleChar=singleChar?singleChar:".";escapeChar=escapeChar?escapeChar:"!";this.value=this.value.replace(new RegExp("\\"+escapeChar+"(.|$)","g"),"\\$1");this.value=this.value.replace(new RegExp("\\"+singleChar,"g"),".");this.value=this.value.replace(new RegExp("\\"+wildCard,"g"),".*");this.value=this.value.replace(new RegExp("\\\\.\\*","g"),"\\"+wildCard);this.value=this.value.replace(new RegExp("\\\\\\.","g"),"\\"+singleChar);return this.value;},regex2value:function(){var value=this.value;value=value.replace(/!/g,"!!");value=value.replace(/(\\)?\\\./g,function($0,$1){return $1?$0:"!.";});value=value.replace(/(\\)?\\\*/g,function($0,$1){return $1?$0:"!*";});value=value.replace(/\\\\/g,"\\");value=value.replace(/\.\*/g,"*");return value;},clone:function(){return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(),this);},CLASS_NAME:"OpenLayers.Filter.Comparison"});OpenLayers.Filter.Comparison.EQUAL_TO="==";OpenLayers.Filter.Comparison.NOT_EQUAL_TO="!=";OpenLayers.Filter.Comparison.LESS_THAN="<";OpenLayers.Filter.Comparison.GREATER_THAN=">";OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO="<=";OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO=">=";OpenLayers.Filter.Comparison.BETWEEN="..";OpenLayers.Filter.Comparison.LIKE="~";OpenLayers.Filter.FeatureId=OpenLayers.Class(OpenLayers.Filter,{fids:null,initialize:function(options){this.fids=[];OpenLayers.Filter.prototype.initialize.apply(this,[options]);},evaluate:function(feature){for(var i=0,len=this.fids.length;i<len;i++){var fid=feature.fid||feature.id;if(fid==this.fids[i]){return true;}}
return false;},clone:function(){var filter=new OpenLayers.Filter.FeatureId();OpenLayers.Util.extend(filter,this);filter.fids=this.fids.slice();return filter;},CLASS_NAME:"OpenLayers.Filter.FeatureId"});OpenLayers.Filter.Logical=OpenLayers.Class(OpenLayers.Filter,{filters:null,type:null,initialize:function(options){this.filters=[];OpenLayers.Filter.prototype.initialize.apply(this,[options]);},destroy:function(){this.filters=null;OpenLayers.Filter.prototype.destroy.apply(this);},evaluate:function(context){switch(this.type){case OpenLayers.Filter.Logical.AND:for(var i=0,len=this.filters.length;i<len;i++){if(this.filters[i].evaluate(context)==false){return false;}}
return true;case OpenLayers.Filter.Logical.OR:for(var i=0,len=this.filters.length;i<len;i++){if(this.filters[i].evaluate(context)==true){return true;}}
return false;case OpenLayers.Filter.Logical.NOT:return(!this.filters[0].evaluate(context));}},clone:function(){var filters=[];for(var i=0,len=this.filters.length;i<len;++i){filters.push(this.filters[i].clone());}
return new OpenLayers.Filter.Logical({type:this.type,filters:filters});},CLASS_NAME:"OpenLayers.Filter.Logical"});OpenLayers.Filter.Logical.AND="&&";OpenLayers.Filter.Logical.OR="||";OpenLayers.Filter.Logical.NOT="!";OpenLayers.Filter.Spatial=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,distance:null,distanceUnits:null,initialize:function(options){OpenLayers.Filter.prototype.initialize.apply(this,[options]);},evaluate:function(feature){var intersect=false;switch(this.type){case OpenLayers.Filter.Spatial.BBOX:case OpenLayers.Filter.Spatial.INTERSECTS:if(feature.geometry){var geom=this.value;if(this.value.CLASS_NAME=="OpenLayers.Bounds"){geom=this.value.toGeometry();}
if(feature.geometry.intersects(geom)){intersect=true;}}
break;default:OpenLayers.Console.error(OpenLayers.i18n("filterEvaluateNotImplemented"));break;}
return intersect;},clone:function(){var options=OpenLayers.Util.applyDefaults({value:this.value&&this.value.clone&&this.value.clone()},this);return new OpenLayers.Filter.Spatial(options);},CLASS_NAME:"OpenLayers.Filter.Spatial"});OpenLayers.Filter.Spatial.BBOX="BBOX";OpenLayers.Filter.Spatial.INTERSECTS="INTERSECTS";OpenLayers.Filter.Spatial.DWITHIN="DWITHIN";OpenLayers.Filter.Spatial.WITHIN="WITHIN";OpenLayers.Filter.Spatial.CONTAINS="CONTAINS";OpenLayers.Geometry.Collection=OpenLayers.Class(OpenLayers.Geometry,{components:null,componentTypes:null,initialize:function(components){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.components=[];if(components!=null){this.addComponents(components);}},destroy:function(){this.components.length=0;this.components=null;},clone:function(){var geometry=eval("new "+this.CLASS_NAME+"()");for(var i=0,len=this.components.length;i<len;i++){geometry.addComponent(this.components[i].clone());}
OpenLayers.Util.applyDefaults(geometry,this);return geometry;},getComponentsString:function(){var strings=[];for(var i=0,len=this.components.length;i<len;i++){strings.push(this.components[i].toShortString());}
return strings.join(",");},calculateBounds:function(){this.bounds=null;if(this.components&&this.components.length>0){this.setBounds(this.components[0].getBounds());for(var i=1,len=this.components.length;i<len;i++){this.extendBounds(this.components[i].getBounds());}}},addComponents:function(components){if(!(components instanceof Array)){components=[components];}
for(var i=0,len=components.length;i<len;i++){this.addComponent(components[i]);}},addComponent:function(component,index){var added=false;if(component){if(this.componentTypes==null||(OpenLayers.Util.indexOf(this.componentTypes,component.CLASS_NAME)>-1)){if(index!=null&&(index<this.components.length)){var components1=this.components.slice(0,index);var components2=this.components.slice(index,this.components.length);components1.push(component);this.components=components1.concat(components2);}else{this.components.push(component);}
component.parent=this;this.clearBounds();added=true;}}
return added;},removeComponents:function(components){if(!(components instanceof Array)){components=[components];}
for(var i=components.length-1;i>=0;--i){this.removeComponent(components[i]);}},removeComponent:function(component){OpenLayers.Util.removeItem(this.components,component);this.clearBounds();},getLength:function(){var length=0.0;for(var i=0,len=this.components.length;i<len;i++){length+=this.components[i].getLength();}
return length;},getArea:function(){var area=0.0;for(var i=0,len=this.components.length;i<len;i++){area+=this.components[i].getArea();}
return area;},getGeodesicArea:function(projection){var area=0.0;for(var i=0,len=this.components.length;i<len;i++){area+=this.components[i].getGeodesicArea(projection);}
return area;},getCentroid:function(){return this.components.length&&this.components[0].getCentroid();},getGeodesicLength:function(projection){var length=0.0;for(var i=0,len=this.components.length;i<len;i++){length+=this.components[i].getGeodesicLength(projection);}
return length;},move:function(x,y){for(var i=0,len=this.components.length;i<len;i++){this.components[i].move(x,y);}},rotate:function(angle,origin){for(var i=0,len=this.components.length;i<len;++i){this.components[i].rotate(angle,origin);}},resize:function(scale,origin,ratio){for(var i=0;i<this.components.length;++i){this.components[i].resize(scale,origin,ratio);}
return this;},distanceTo:function(geometry,options){var edge=!(options&&options.edge===false);var details=edge&&options&&options.details;var result,best;var min=Number.POSITIVE_INFINITY;for(var i=0,len=this.components.length;i<len;++i){result=this.components[i].distanceTo(geometry,options);distance=details?result.distance:result;if(distance<min){min=distance;best=result;if(min==0){break;}}}
return best;},equals:function(geometry){var equivalent=true;if(!geometry||!geometry.CLASS_NAME||(this.CLASS_NAME!=geometry.CLASS_NAME)){equivalent=false;}else if(!(geometry.components instanceof Array)||(geometry.components.length!=this.components.length)){equivalent=false;}else{for(var i=0,len=this.components.length;i<len;++i){if(!this.components[i].equals(geometry.components[i])){equivalent=false;break;}}}
return equivalent;},transform:function(source,dest){if(source&&dest){for(var i=0,len=this.components.length;i<len;i++){var component=this.components[i];component.transform(source,dest);}
this.bounds=null;}
return this;},intersects:function(geometry){var intersect=false;for(var i=0,len=this.components.length;i<len;++i){intersect=geometry.intersects(this.components[i]);if(intersect){break;}}
return intersect;},getVertices:function(nodes){var vertices=[];for(var i=0,len=this.components.length;i<len;++i){Array.prototype.push.apply(vertices,this.components[i].getVertices(nodes));}
return vertices;},CLASS_NAME:"OpenLayers.Geometry.Collection"});OpenLayers.Geometry.Point=OpenLayers.Class(OpenLayers.Geometry,{x:null,y:null,initialize:function(x,y){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.x=parseFloat(x);this.y=parseFloat(y);},clone:function(obj){if(obj==null){obj=new OpenLayers.Geometry.Point(this.x,this.y);}
OpenLayers.Util.applyDefaults(obj,this);return obj;},calculateBounds:function(){this.bounds=new OpenLayers.Bounds(this.x,this.y,this.x,this.y);},distanceTo:function(geometry,options){var edge=!(options&&options.edge===false);var details=edge&&options&&options.details;var distance,x0,y0,x1,y1,result;if(geometry instanceof OpenLayers.Geometry.Point){x0=this.x;y0=this.y;x1=geometry.x;y1=geometry.y;distance=Math.sqrt(Math.pow(x0-x1,2)+Math.pow(y0-y1,2));result=!details?distance:{x0:x0,y0:y0,x1:x1,y1:y1,distance:distance};}else{result=geometry.distanceTo(this,options);if(details){result={x0:result.x1,y0:result.y1,x1:result.x0,y1:result.y0,distance:result.distance};}}
return result;},equals:function(geom){var equals=false;if(geom!=null){equals=((this.x==geom.x&&this.y==geom.y)||(isNaN(this.x)&&isNaN(this.y)&&isNaN(geom.x)&&isNaN(geom.y)));}
return equals;},toShortString:function(){return(this.x+", "+this.y);},move:function(x,y){this.x=this.x+x;this.y=this.y+y;this.clearBounds();},rotate:function(angle,origin){angle*=Math.PI/180;var radius=this.distanceTo(origin);var theta=angle+Math.atan2(this.y-origin.y,this.x-origin.x);this.x=origin.x+(radius*Math.cos(theta));this.y=origin.y+(radius*Math.sin(theta));this.clearBounds();},getCentroid:function(){return new OpenLayers.Geometry.Point(this.x,this.y);},resize:function(scale,origin,ratio){ratio=(ratio==undefined)?1:ratio;this.x=origin.x+(scale*ratio*(this.x-origin.x));this.y=origin.y+(scale*(this.y-origin.y));this.clearBounds();return this;},intersects:function(geometry){var intersect=false;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){intersect=this.equals(geometry);}else{intersect=geometry.intersects(this);}
return intersect;},transform:function(source,dest){if((source&&dest)){OpenLayers.Projection.transform(this,source,dest);this.bounds=null;}
return this;},getVertices:function(nodes){return[this];},CLASS_NAME:"OpenLayers.Geometry.Point"});OpenLayers.Geometry.Rectangle=OpenLayers.Class(OpenLayers.Geometry,{x:null,y:null,width:null,height:null,initialize:function(x,y,width,height){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.x=x;this.y=y;this.width=width;this.height=height;},calculateBounds:function(){this.bounds=new OpenLayers.Bounds(this.x,this.y,this.x+this.width,this.y+this.height);},getLength:function(){var length=(2*this.width)+(2*this.height);return length;},getArea:function(){var area=this.width*this.height;return area;},CLASS_NAME:"OpenLayers.Geometry.Rectangle"});OpenLayers.Geometry.Surface=OpenLayers.Class(OpenLayers.Geometry,{initialize:function(){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);},CLASS_NAME:"OpenLayers.Geometry.Surface"});OpenLayers.Layer.KaMapCache=OpenLayers.Class(OpenLayers.Layer.KaMap,{IMAGE_EXTENSIONS:{'jpeg':'jpg','gif':'gif','png':'png','png8':'png','png24':'png','dithered':'png'},DEFAULT_FORMAT:'jpeg',initialize:function(name,url,params,options){OpenLayers.Layer.KaMap.prototype.initialize.apply(this,arguments);this.extension=this.IMAGE_EXTENSIONS[this.params.i.toLowerCase()||DEFAULT_FORMAT];},getURL:function(bounds){bounds=this.adjustBounds(bounds);var mapRes=this.map.getResolution();var scale=Math.round((this.map.getScale()*10000))/10000;var pX=Math.round(bounds.left/mapRes);var pY=-Math.round(bounds.top/mapRes);var metaX=Math.floor(pX/this.tileSize.w/this.params.metaTileSize.w)*this.tileSize.w*this.params.metaTileSize.w;var metaY=Math.floor(pY/this.tileSize.h/this.params.metaTileSize.h)*this.tileSize.h*this.params.metaTileSize.h;var url=this.url;if(url instanceof Array){url=this.selectUrl(paramsString,url);}
var components=[url,"/",this.params.map,"/",scale,"/",this.params.g.replace(/\s/g,'_'),"/def/t",metaY,"/l",metaX,"/t",pY,"l",pX,".",this.extension];return components.join("");},CLASS_NAME:"OpenLayers.Layer.KaMapCache"});OpenLayers.Layer.MapServer.Untiled=OpenLayers.Class(OpenLayers.Layer.MapServer,{singleTile:true,initialize:function(name,url,params,options){OpenLayers.Layer.MapServer.prototype.initialize.apply(this,arguments);var msg="The OpenLayers.Layer.MapServer.Untiled class is deprecated and "+"will be removed in 3.0. Instead, you should use the "+"normal OpenLayers.Layer.MapServer class, passing it the option "+"'singleTile' as true.";OpenLayers.Console.warn(msg);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapServer.Untiled(this.name,this.url,this.params,this.options);}
obj=OpenLayers.Layer.MapServer.prototype.clone.apply(this,[obj]);return obj;},CLASS_NAME:"OpenLayers.Layer.MapServer.Untiled"});OpenLayers.Layer.Vector=OpenLayers.Class(OpenLayers.Layer,{EVENT_TYPES:["beforefeatureadded","beforefeaturesadded","featureadded","featuresadded","beforefeatureremoved","featureremoved","featuresremoved","beforefeatureselected","featureselected","featureunselected","beforefeaturemodified","featuremodified","afterfeaturemodified","vertexmodified","sketchstarted","sketchmodified","sketchcomplete","refresh"],isBaseLayer:false,isFixed:false,isVector:true,features:null,selectedFeatures:null,unrenderedFeatures:null,reportError:true,style:null,styleMap:null,strategies:null,protocol:null,renderers:['SVG','VML','Canvas'],renderer:null,rendererOptions:null,geometryType:null,drawn:false,initialize:function(name,options){this.EVENT_TYPES=OpenLayers.Layer.Vector.prototype.EVENT_TYPES.concat(OpenLayers.Layer.prototype.EVENT_TYPES);OpenLayers.Layer.prototype.initialize.apply(this,arguments);if(!this.renderer||!this.renderer.supported()){this.assignRenderer();}
if(!this.renderer||!this.renderer.supported()){this.renderer=null;this.displayError();}
if(!this.styleMap){this.styleMap=new OpenLayers.StyleMap();}
this.features=[];this.selectedFeatures=[];this.unrenderedFeatures={};if(this.strategies){for(var i=0,len=this.strategies.length;i<len;i++){this.strategies[i].setLayer(this);}}},destroy:function(){if(this.strategies){var strategy,i,len;for(i=0,len=this.strategies.length;i<len;i++){strategy=this.strategies[i];if(strategy.autoDestroy){strategy.destroy();}}
this.strategies=null;}
if(this.protocol){if(this.protocol.autoDestroy){this.protocol.destroy();}
this.protocol=null;}
this.destroyFeatures();this.features=null;this.selectedFeatures=null;this.unrenderedFeatures=null;if(this.renderer){this.renderer.destroy();}
this.renderer=null;this.geometryType=null;this.drawn=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},refresh:function(obj){if(this.calculateInRange()&&this.visibility){this.events.triggerEvent("refresh",obj);}},assignRenderer:function(){for(var i=0,len=this.renderers.length;i<len;i++){var rendererClass=OpenLayers.Renderer[this.renderers[i]];if(rendererClass&&rendererClass.prototype.supported()){this.renderer=new rendererClass(this.div,this.rendererOptions);break;}}},displayError:function(){if(this.reportError){OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",{'renderers':this.renderers.join("\n")}));}},setMap:function(map){OpenLayers.Layer.prototype.setMap.apply(this,arguments);if(!this.renderer){this.map.removeLayer(this);}else{this.renderer.map=this.map;this.renderer.setSize(this.map.getSize());}},afterAdd:function(){if(this.strategies){var strategy,i,len;for(i=0,len=this.strategies.length;i<len;i++){strategy=this.strategies[i];if(strategy.autoActivate){strategy.activate();}}}},removeMap:function(map){if(this.strategies){var strategy,i,len;for(i=0,len=this.strategies.length;i<len;i++){strategy=this.strategies[i];if(strategy.autoActivate){strategy.deactivate();}}}},onMapResize:function(){OpenLayers.Layer.prototype.onMapResize.apply(this,arguments);this.renderer.setSize(this.map.getSize());},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var coordSysUnchanged=true;if(!dragging){this.renderer.root.style.visibility="hidden";this.div.style.left=-parseInt(this.map.layerContainerDiv.style.left)+"px";this.div.style.top=-parseInt(this.map.layerContainerDiv.style.top)+"px";var extent=this.map.getExtent();coordSysUnchanged=this.renderer.setExtent(extent,zoomChanged);this.renderer.root.style.visibility="visible";if(navigator.userAgent.toLowerCase().indexOf("gecko")!=-1){this.div.scrollLeft=this.div.scrollLeft;}
if(!zoomChanged&&coordSysUnchanged){for(var i in this.unrenderedFeatures){var feature=this.unrenderedFeatures[i];this.drawFeature(feature);}}}
if(!this.drawn||zoomChanged||!coordSysUnchanged){this.drawn=true;var feature;for(var i=0,len=this.features.length;i<len;i++){this.renderer.locked=(i!==(len-1));feature=this.features[i];this.drawFeature(feature);}}},display:function(display){OpenLayers.Layer.prototype.display.apply(this,arguments);var currentDisplay=this.div.style.display;if(currentDisplay!=this.renderer.root.style.display){this.renderer.root.style.display=currentDisplay;}},addFeatures:function(features,options){if(!(features instanceof Array)){features=[features];}
var notify=!options||!options.silent;if(notify){var event={features:features};var ret=this.events.triggerEvent("beforefeaturesadded",event);if(ret===false){return;}
features=event.features;}
for(var i=0,len=features.length;i<len;i++){if(i!=(features.length-1)){this.renderer.locked=true;}else{this.renderer.locked=false;}
var feature=features[i];if(this.geometryType&&!(feature.geometry instanceof this.geometryType)){var throwStr=OpenLayers.i18n('componentShouldBe',{'geomType':this.geometryType.prototype.CLASS_NAME});throw throwStr;}
this.features.push(feature);feature.layer=this;if(!feature.style&&this.style){feature.style=OpenLayers.Util.extend({},this.style);}
if(notify){if(this.events.triggerEvent("beforefeatureadded",{feature:feature})===false){continue;};this.preFeatureInsert(feature);}
this.drawFeature(feature);if(notify){this.events.triggerEvent("featureadded",{feature:feature});this.onFeatureInsert(feature);}}
if(notify){this.events.triggerEvent("featuresadded",{features:features});}},removeFeatures:function(features,options){if(!features||features.length===0){return;}
if(!(features instanceof Array)){features=[features];}
if(features===this.features){features=features.slice();}
var notify=!options||!options.silent;for(var i=features.length-1;i>=0;i--){if(i!=0&&features[i-1].geometry){this.renderer.locked=true;}else{this.renderer.locked=false;}
var feature=features[i];delete this.unrenderedFeatures[feature.id];if(notify){this.events.triggerEvent("beforefeatureremoved",{feature:feature});}
this.features=OpenLayers.Util.removeItem(this.features,feature);feature.layer=null;if(feature.geometry){this.renderer.eraseFeatures(feature);}
if(OpenLayers.Util.indexOf(this.selectedFeatures,feature)!=-1){OpenLayers.Util.removeItem(this.selectedFeatures,feature);}
if(notify){this.events.triggerEvent("featureremoved",{feature:feature});}}
if(notify){this.events.triggerEvent("featuresremoved",{features:features});}},destroyFeatures:function(features,options){var all=(features==undefined);if(all){features=this.features;}
if(features){this.removeFeatures(features,options);for(var i=features.length-1;i>=0;i--){features[i].destroy();}}},drawFeature:function(feature,style){if(!this.drawn){return}
if(typeof style!="object"){if(!style&&feature.state===OpenLayers.State.DELETE){style="delete";}
var renderIntent=style||feature.renderIntent;style=feature.style||this.style;if(!style){style=this.styleMap.createSymbolizer(feature,renderIntent);}}
if(!this.renderer.drawFeature(feature,style)){this.unrenderedFeatures[feature.id]=feature;}else{delete this.unrenderedFeatures[feature.id];};},eraseFeatures:function(features){this.renderer.eraseFeatures(features);},getFeatureFromEvent:function(evt){if(!this.renderer){OpenLayers.Console.error(OpenLayers.i18n("getFeatureError"));return null;}
var featureId=this.renderer.getFeatureIdFromEvent(evt);return this.getFeatureById(featureId);},getFeatureById:function(featureId){var feature=null;for(var i=0,len=this.features.length;i<len;++i){if(this.features[i].id==featureId){feature=this.features[i];break;}}
return feature;},onFeatureInsert:function(feature){},preFeatureInsert:function(feature){},getDataExtent:function(){var maxExtent=null;if(this.features&&(this.features.length>0)){maxExtent=new OpenLayers.Bounds();for(var i=0,len=this.features.length;i<len;i++){maxExtent.extend(this.features[i].geometry.getBounds());}}
return maxExtent;},CLASS_NAME:"OpenLayers.Layer.Vector"});OpenLayers.Layer.WMS.Untiled=OpenLayers.Class(OpenLayers.Layer.WMS,{singleTile:true,initialize:function(name,url,params,options){OpenLayers.Layer.WMS.prototype.initialize.apply(this,arguments);var msg="The OpenLayers.Layer.WMS.Untiled class is deprecated and "+"will be removed in 3.0. Instead, you should use the "+"normal OpenLayers.Layer.WMS class, passing it the option "+"'singleTile' as true.";OpenLayers.Console.warn(msg);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.WMS.Untiled(this.name,this.url,this.params,this.options);}
obj=OpenLayers.Layer.WMS.prototype.clone.apply(this,[obj]);return obj;},CLASS_NAME:"OpenLayers.Layer.WMS.Untiled"});OpenLayers.Control.GetFeature=OpenLayers.Class(OpenLayers.Control,{protocol:null,multipleKey:null,toggleKey:null,modifiers:null,multiple:false,click:true,clickout:true,toggle:false,clickTolerance:5,hover:false,box:false,maxFeatures:10,features:null,hoverFeature:null,handlerOptions:null,handlers:null,hoverResponse:null,EVENT_TYPES:["featureselected","featureunselected","clickout","beforefeatureselected","hoverfeature","outfeature"],initialize:function(options){this.EVENT_TYPES=OpenLayers.Control.GetFeature.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);options.handlerOptions=options.handlerOptions||{};OpenLayers.Control.prototype.initialize.apply(this,[options]);this.features={};this.handlers={};if(this.click){this.handlers.click=new OpenLayers.Handler.Click(this,{click:this.selectSingle},this.handlerOptions.click||{})};if(this.box){this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},OpenLayers.Util.extend(this.handlerOptions.box,{boxDivClassName:"olHandlerBoxSelectFeature"}));}
if(this.hover){this.handlers.hover=new OpenLayers.Handler.Hover(this,{'move':this.cancelHover,'pause':this.selectHover},OpenLayers.Util.extend(this.handlerOptions.hover,{'delay':250}));}},activate:function(){if(!this.active){for(var i in this.handlers){this.handlers[i].activate();}}
return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){if(this.active){for(var i in this.handlers){this.handlers[i].deactivate();}}
return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},unselectAll:function(options){var feature;for(var i=this.features.length-1;i>=0;--i){feature=this.features[i];if(!options||options.except!=feature){this.unselect(feature);}}},selectSingle:function(evt){OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");var bounds=this.pixelToBounds(evt.xy);this.setModifiers(evt);this.request(bounds,{single:true});},selectBox:function(position){if(position instanceof OpenLayers.Bounds){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));var bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);this.setModifiers(this.handlers.box.dragHandler.evt);this.request(bounds);}},selectHover:function(evt){var bounds=this.pixelToBounds(evt.xy);this.request(bounds,{single:true,hover:true});},cancelHover:function(){if(this.hoverResponse){this.protocol.abort(this.hoverResponse);this.hoverResponse=null;}},request:function(bounds,options){options=options||{};var filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,value:bounds});var response=this.protocol.read({maxFeatures:options.single==true?this.maxFeatures:undefined,filter:filter,callback:function(result){if(result.code==1){if(result.features.length){if(options.single==true){this.selectBestFeature(result.features,bounds.getCenterLonLat(),options);}else{this.select(result.features);}}else if(options.hover){this.hoverSelect();}else{this.events.triggerEvent("clickout");if(this.clickout){this.unselectAll();}}}
OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");},scope:this});if(options.hover==true){this.hoverResponse=response;}},selectBestFeature:function(features,clickPosition,options){options=options||{};if(features.length){var point=new OpenLayers.Geometry.Point(clickPosition.lon,clickPosition.lat);var feature,resultFeature,dist;var minDist=Number.MAX_VALUE;for(var i=0;i<features.length;++i){feature=features[i];if(feature.geometry){dist=point.distanceTo(feature.geometry,{edge:false});if(dist<minDist){minDist=dist;resultFeature=feature;if(minDist==0){break;}}}}
if(options.hover==true){this.hoverSelect(resultFeature);}else{this.select(resultFeature||features);}};},setModifiers:function(evt){this.modifiers={multiple:this.multiple||(this.multipleKey&&evt[this.multipleKey]),toggle:this.toggle||(this.toggleKey&&evt[this.toggleKey])}},select:function(features){if(!this.modifiers.multiple&&!this.modifiers.toggle){this.unselectAll();}
if(!(features instanceof Array)){features=[features];}
var feature;for(var i=0,len=features.length;i<len;++i){feature=features[i];if(this.features[feature.fid||feature.id]){if(this.modifiers.toggle){this.unselect(this.features[feature.fid||feature.id]);}}else{cont=this.events.triggerEvent("beforefeatureselected",{feature:feature});if(cont!==false){this.features[feature.fid||feature.id]=feature;this.events.triggerEvent("featureselected",{feature:feature});}}}},hoverSelect:function(feature){var fid=feature?feature.fid||feature.id:null;var hfid=this.hoverFeature?this.hoverFeature.fid||this.hoverFeature.id:null;if(hfid&&hfid!=fid){this.events.triggerEvent("outfeature",{feature:this.hoverFeature});this.hoverFeature=null;}
if(fid&&fid!=hfid){this.events.triggerEvent("hoverfeature",{feature:feature});this.hoverFeature=feature;}},unselect:function(feature){delete this.features[feature.fid||feature.id];this.events.triggerEvent("featureunselected",{feature:feature});},unselectAll:function(){for(var fid in this.features){this.unselect(this.features[fid]);}},setMap:function(map){for(var i in this.handlers){this.handlers[i].setMap(map);}
OpenLayers.Control.prototype.setMap.apply(this,arguments);},pixelToBounds:function(pixel){var llPx=pixel.add(-this.clickTolerance/2,this.clickTolerance/2);var urPx=pixel.add(this.clickTolerance/2,-this.clickTolerance/2);var ll=this.map.getLonLatFromPixel(llPx);var ur=this.map.getLonLatFromPixel(urPx);return new OpenLayers.Bounds(ll.lon,ll.lat,ur.lon,ur.lat);},CLASS_NAME:"OpenLayers.Control.GetFeature"});OpenLayers.Control.Snapping=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:["beforesnap","snap","unsnap"],DEFAULTS:{tolerance:10,node:true,edge:true,vertex:true},greedy:true,precedence:["node","vertex","edge"],resolution:null,geoToleranceCache:null,layer:null,feature:null,point:null,initialize:function(options){Array.prototype.push.apply(this.EVENT_TYPES,OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.options=options||{};if(this.options.layer){this.setLayer(this.options.layer);}
var defaults=OpenLayers.Util.extend({},this.options.defaults);this.defaults=OpenLayers.Util.applyDefaults(defaults,this.DEFAULTS);this.setTargets(this.options.targets);if(this.targets.length===0&&this.layer){this.addTargetLayer(this.layer);}
this.geoToleranceCache={};},setLayer:function(layer){if(this.active){this.deactivate();this.layer=layer;this.activate();}else{this.layer=layer;}},setTargets:function(targets){this.targets=[];if(targets&&targets.length){var target;for(var i=0,len=targets.length;i<len;++i){target=targets[i];if(target instanceof OpenLayers.Layer.Vector){this.addTargetLayer(target);}else{this.addTarget(target);}}}},addTargetLayer:function(layer){this.addTarget({layer:layer});},addTarget:function(target){target=OpenLayers.Util.applyDefaults(target,this.defaults);target.nodeTolerance=target.nodeTolerance||target.tolerance;target.vertexTolerance=target.vertexTolerance||target.tolerance;target.edgeTolerance=target.edgeTolerance||target.tolerance;this.targets.push(target);},removeTargetLayer:function(layer){var target;for(var i=this.targets.length-1;i>=0;--i){target=this.targets[i];if(target.layer===layer){this.removeTarget(target);}}},removeTarget:function(target){return OpenLayers.Util.removeItem(this.targets,target);},activate:function(){var activated=OpenLayers.Control.prototype.activate.call(this);if(activated){if(this.layer&&this.layer.events){this.layer.events.on({sketchstarted:this.onSketchModified,sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});}}
return activated;},deactivate:function(){var deactivated=OpenLayers.Control.prototype.deactivate.call(this);if(deactivated){if(this.layer&&this.layer.events){this.layer.events.un({sketchstarted:this.onSketchModified,sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});}}
this.feature=null;this.point=null;return deactivated;},onSketchModified:function(event){this.feature=event.feature;this.considerSnapping(event.vertex,event.vertex);},onVertexModified:function(event){this.feature=event.feature;var loc=this.layer.map.getLonLatFromViewPortPx(event.pixel);this.considerSnapping(event.vertex,new OpenLayers.Geometry.Point(loc.lon,loc.lat));},considerSnapping:function(point,loc){var best={rank:Number.POSITIVE_INFINITY,dist:Number.POSITIVE_INFINITY,x:null,y:null};var snapped=false;var result,target;for(var i=0,len=this.targets.length;i<len;++i){target=this.targets[i];result=this.testTarget(target,loc);if(result){if(this.greedy){best=result;best.target=target;snapped=true;break;}else{if((result.rank<best.rank)||(result.rank===best.rank&&result.dist<best.dist)){best=result;best.target=target;snapped=true;}}}}
if(snapped){var proceed=this.events.triggerEvent("beforesnap",{point:point,x:best.x,y:best.y,distance:best.dist,layer:best.target.layer,snapType:this.precedence[best.rank]});if(proceed!==false){point.x=best.x;point.y=best.y;this.point=point;this.events.triggerEvent("snap",{point:point,snapType:this.precedence[best.rank],layer:best.target.layer,distance:best.dist});}else{snapped=false;}}
if(this.point&&!snapped){point.x=loc.x;point.y=loc.y;this.point=null;this.events.triggerEvent("unsnap",{point:point});}},testTarget:function(target,loc){var tolerance={node:this.getGeoTolerance(target.nodeTolerance),vertex:this.getGeoTolerance(target.vertexTolerance),edge:this.getGeoTolerance(target.edgeTolerance)};var maxTolerance=Math.max(tolerance.node,tolerance.vertex,tolerance.edge);var result={rank:Number.POSITIVE_INFINITY,dist:Number.POSITIVE_INFINITY};var eligible=false;var features=target.layer.features;var feature,type,vertices,vertex,closest,dist,found;var numTypes=this.precedence.length;var ll=new OpenLayers.LonLat(loc.x,loc.y);for(var i=0,len=features.length;i<len;++i){feature=features[i];if(feature!==this.feature&&!feature._sketch&&feature.state!==OpenLayers.State.DELETE&&(!target.filter||target.filter.evaluate(feature.attributes))){if(feature.atPoint(ll,maxTolerance,maxTolerance)){for(var j=0,stop=Math.min(result.rank+1,numTypes);j<stop;++j){type=this.precedence[j];if(target[type]){if(type==="edge"){closest=feature.geometry.distanceTo(loc,{details:true});dist=closest.distance;if(dist<=tolerance[type]&&dist<result.dist){result={rank:j,dist:dist,x:closest.x0,y:closest.y0};eligible=true;break;}}else{vertices=feature.geometry.getVertices(type==="node");found=false;for(var k=0,klen=vertices.length;k<klen;++k){vertex=vertices[k];dist=vertex.distanceTo(loc);if(dist<=tolerance[type]&&(j<result.rank||(j===result.rank&&dist<result.dist))){result={rank:j,dist:dist,x:vertex.x,y:vertex.y};eligible=true;found=true;}}
if(found){break;}}}}}}}
return eligible?result:null;},getGeoTolerance:function(tolerance){var resolution=this.layer.map.getResolution();if(resolution!==this.resolution){this.resolution=resolution;this.geoToleranceCache={};}
var geoTolerance=this.geoToleranceCache[tolerance];if(geoTolerance===undefined){geoTolerance=tolerance*resolution;this.geoToleranceCache[tolerance]=geoTolerance;}
return geoTolerance;},destroy:function(){if(this.active){this.deactivate();}
delete this.layer;delete this.targets;OpenLayers.Control.prototype.destroy.call(this);},CLASS_NAME:"OpenLayers.Control.Snapping"});OpenLayers.Format.Filter=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.0.0",version:null,parser:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},write:function(filter,options){var version=(options&&options.version)||this.version||this.defaultVersion;if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.Filter["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a Filter parser for version "+
version;}
this.parser=new format(this.options);}
return this.parser.write(filter);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var version=this.version;if(!version){version=this.defaultVersion;}
if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.Filter["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a Filter parser for version "+
version;}
this.parser=new format(this.options);}
var filter=this.parser.read(data);return filter;},CLASS_NAME:"OpenLayers.Format.Filter"});OpenLayers.Format.SLD=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.0.0",version:null,namedLayersAsArray:false,parser:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},write:function(sld,options){var version=(options&&options.version)||this.version||this.defaultVersion;if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.SLD["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a SLD parser for version "+
version;}
this.parser=new format(this.options);}
var root=this.parser.write(sld);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},read:function(data,options){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var root=data.documentElement;var version=this.version;if(!version){version=root.getAttribute("version");if(!version){version=this.defaultVersion;}}
if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.SLD["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a SLD parser for version "+
version;}
this.parser=new format(this.options);}
var sld=this.parser.read(data,options);return sld;},CLASS_NAME:"OpenLayers.Format.SLD"});OpenLayers.Format.Text=OpenLayers.Class(OpenLayers.Format,{defaultStyle:null,extractStyles:true,initialize:function(options){options=options||{};if(options.extractStyles!==false){options.defaultStyle={'externalGraphic':OpenLayers.Util.getImagesLocation()+"marker.png",'graphicWidth':21,'graphicHeight':25,'graphicXOffset':-10.5,'graphicYOffset':-12.5};}
OpenLayers.Format.prototype.initialize.apply(this,[options]);},read:function(text){var lines=text.split('\n');var columns;var features=[];for(var lcv=0;lcv<(lines.length-1);lcv++){var currLine=lines[lcv].replace(/^\s*/,'').replace(/\s*$/,'');if(currLine.charAt(0)!='#'){if(!columns){columns=currLine.split('\t');}else{var vals=currLine.split('\t');var geometry=new OpenLayers.Geometry.Point(0,0);var attributes={};var style=this.defaultStyle?OpenLayers.Util.applyDefaults({},this.defaultStyle):null;var icon,iconSize,iconOffset,overflow;var set=false;for(var valIndex=0;valIndex<vals.length;valIndex++){if(vals[valIndex]){if(columns[valIndex]=='point'){var coords=vals[valIndex].split(',');geometry.y=parseFloat(coords[0]);geometry.x=parseFloat(coords[1]);set=true;}else if(columns[valIndex]=='lat'){geometry.y=parseFloat(vals[valIndex]);set=true;}else if(columns[valIndex]=='lon'){geometry.x=parseFloat(vals[valIndex]);set=true;}else if(columns[valIndex]=='title')
attributes['title']=vals[valIndex];else if(columns[valIndex]=='image'||columns[valIndex]=='icon'&&style){style['externalGraphic']=vals[valIndex];}else if(columns[valIndex]=='iconSize'&&style){var size=vals[valIndex].split(',');style['graphicWidth']=parseFloat(size[0]);style['graphicHeight']=parseFloat(size[1]);}else if(columns[valIndex]=='iconOffset'&&style){var offset=vals[valIndex].split(',');style['graphicXOffset']=parseFloat(offset[0]);style['graphicYOffset']=parseFloat(offset[1]);}else if(columns[valIndex]=='description'){attributes['description']=vals[valIndex];}else if(columns[valIndex]=='overflow'){attributes['overflow']=vals[valIndex];}else{attributes[columns[valIndex]]=vals[valIndex];}}}
if(set){if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}
var feature=new OpenLayers.Feature.Vector(geometry,attributes,style);features.push(feature);}}}}
return features;},CLASS_NAME:"OpenLayers.Format.Text"});OpenLayers.Geometry.MultiPoint=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Point"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},addPoint:function(point,index){this.addComponent(point,index);},removePoint:function(point){this.removeComponent(point);},CLASS_NAME:"OpenLayers.Geometry.MultiPoint"});OpenLayers.Handler.Point=OpenLayers.Class(OpenLayers.Handler,{point:null,layer:null,multi:false,drawing:false,mouseDown:false,lastDown:null,lastUp:null,persist:false,layerOptions:null,initialize:function(control,callbacks,options){if(!(options&&options.layerOptions&&options.layerOptions.styleMap)){this.style=OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'],{});}
OpenLayers.Handler.prototype.initialize.apply(this,arguments);},activate:function(){if(!OpenLayers.Handler.prototype.activate.apply(this,arguments)){return false;}
var options=OpenLayers.Util.extend({displayInLayerSwitcher:false,calculateInRange:function(){return true;}},this.layerOptions);this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,options);this.map.addLayer(this.layer);return true;},createFeature:function(pixel){var lonlat=this.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.callback("create",[this.point.geometry,this.point]);this.point.geometry.clearBounds();this.layer.addFeatures([this.point],{silent:true});},deactivate:function(){if(!OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){return false;}
if(this.drawing){this.cancel();}
this.destroyFeature();if(this.layer.map!=null){this.layer.destroy(false);}
this.layer=null;return true;},destroyFeature:function(){if(this.layer){this.layer.destroyFeatures();}
this.point=null;},finalize:function(cancel){var key=cancel?"cancel":"done";this.drawing=false;this.mouseDown=false;this.lastDown=null;this.lastUp=null;this.callback(key,[this.geometryClone()]);if(cancel||!this.persist){this.destroyFeature();}},cancel:function(){this.finalize(true);},click:function(evt){OpenLayers.Event.stop(evt);return false;},dblclick:function(evt){OpenLayers.Event.stop(evt);return false;},modifyFeature:function(pixel){var lonlat=this.map.getLonLatFromPixel(pixel);this.point.geometry.x=lonlat.lon;this.point.geometry.y=lonlat.lat;this.callback("modify",[this.point.geometry,this.point]);this.point.geometry.clearBounds();this.drawFeature();},drawFeature:function(){this.layer.drawFeature(this.point,this.style);},getGeometry:function(){var geometry=this.point&&this.point.geometry;if(geometry&&this.multi){geometry=new OpenLayers.Geometry.MultiPoint([geometry]);}
return geometry;},geometryClone:function(){var geom=this.getGeometry();return geom&&geom.clone();},mousedown:function(evt){if(!this.checkModifiers(evt)){return true;}
if(this.lastDown&&this.lastDown.equals(evt.xy)){return true;}
this.drawing=true;if(this.lastDown==null){if(this.persist){this.destroyFeature();}
this.createFeature(evt.xy);}else{this.modifyFeature(evt.xy);}
this.lastDown=evt.xy;return false;},mousemove:function(evt){if(this.drawing){this.modifyFeature(evt.xy);}
return true;},mouseup:function(evt){if(this.drawing){this.finalize();return false;}else{return true;}},CLASS_NAME:"OpenLayers.Handler.Point"});OpenLayers.Layer.GML=OpenLayers.Class(OpenLayers.Layer.Vector,{loaded:false,format:null,formatOptions:null,initialize:function(name,url,options){var newArguments=[];newArguments.push(name,options);OpenLayers.Layer.Vector.prototype.initialize.apply(this,newArguments);this.url=url;},setVisibility:function(visibility,noEvent){OpenLayers.Layer.Vector.prototype.setVisibility.apply(this,arguments);if(this.visibility&&!this.loaded){this.loadGML();}},moveTo:function(bounds,zoomChanged,minor){OpenLayers.Layer.Vector.prototype.moveTo.apply(this,arguments);if(this.visibility&&!this.loaded){this.loadGML();}},loadGML:function(){if(!this.loaded){this.events.triggerEvent("loadstart");OpenLayers.Request.GET({url:this.url,success:this.requestSuccess,failure:this.requestFailure,scope:this});this.loaded=true;}},setUrl:function(url){this.url=url;this.destroyFeatures();this.loaded=false;this.loadGML();},requestSuccess:function(request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
var options={};OpenLayers.Util.extend(options,this.formatOptions);if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
var gml=this.format?new this.format(options):new OpenLayers.Format.GML(options);this.addFeatures(gml.read(doc));this.events.triggerEvent("loadend");},requestFailure:function(request){OpenLayers.Console.userError(OpenLayers.i18n("errorLoadingGML",{'url':this.url}));this.events.triggerEvent("loadend");},CLASS_NAME:"OpenLayers.Layer.GML"});OpenLayers.Layer.PointTrack=OpenLayers.Class(OpenLayers.Layer.Vector,{dataFrom:null,initialize:function(name,options){OpenLayers.Layer.Vector.prototype.initialize.apply(this,arguments);},addNodes:function(pointFeatures){if(pointFeatures.length<2){OpenLayers.Console.error("At least two point features have to be added to create"+"a line from");return;}
var lines=new Array(pointFeatures.length-1);var pointFeature,startPoint,endPoint;for(var i=0,len=pointFeatures.length;i<len;i++){pointFeature=pointFeatures[i];endPoint=pointFeature.geometry;if(!endPoint){var lonlat=pointFeature.lonlat;endPoint=new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat);}else if(endPoint.CLASS_NAME!="OpenLayers.Geometry.Point"){OpenLayers.Console.error("Only features with point geometries are supported.");return;}
if(i>0){var attributes=(this.dataFrom!=null)?(pointFeatures[i+this.dataFrom].data||pointFeatures[i+this.dataFrom].attributes):null;var line=new OpenLayers.Geometry.LineString([startPoint,endPoint]);lines[i-1]=new OpenLayers.Feature.Vector(line,attributes);}
startPoint=endPoint;}
this.addFeatures(lines);},CLASS_NAME:"OpenLayers.Layer.PointTrack"});OpenLayers.Layer.PointTrack.dataFrom={'SOURCE_NODE':-1,'TARGET_NODE':0};OpenLayers.Layer.Vector.RootContainer=OpenLayers.Class(OpenLayers.Layer.Vector,{displayInLayerSwitcher:false,layers:null,initialize:function(name,options){OpenLayers.Layer.Vector.prototype.initialize.apply(this,arguments);},display:function(){},getFeatureFromEvent:function(evt){var layers=this.layers;var feature;for(var i=0;i<layers.length;i++){feature=layers[i].getFeatureFromEvent(evt);if(feature){return feature;}}},setMap:function(map){OpenLayers.Layer.Vector.prototype.setMap.apply(this,arguments);this.collectRoots();map.events.register("changelayer",this,this.handleChangeLayer);},removeMap:function(map){map.events.unregister("changelayer",this,this.handleChangeLayer);this.resetRoots();OpenLayers.Layer.Vector.prototype.removeMap.apply(this,arguments);},collectRoots:function(){var layer;for(var i=0;i<this.map.layers.length;++i){layer=this.map.layers[i];if(OpenLayers.Util.indexOf(this.layers,layer)!=-1){layer.renderer.moveRoot(this.renderer);}}},resetRoots:function(){var layer;for(var i=0;i<this.layers.length;++i){layer=this.layers[i];if(this.renderer&&layer.renderer.getRenderLayerId()==this.id){this.renderer.moveRoot(layer.renderer);}}},handleChangeLayer:function(evt){var layer=evt.layer;if(evt.property=="order"&&OpenLayers.Util.indexOf(this.layers,layer)!=-1){this.resetRoots();this.collectRoots();}},CLASS_NAME:"OpenLayers.Layer.Vector.RootContainer"});OpenLayers.Layer.WFS=OpenLayers.Class(OpenLayers.Layer.Vector,OpenLayers.Layer.Markers,{isBaseLayer:false,tile:null,ratio:2,DEFAULT_PARAMS:{service:"WFS",version:"1.0.0",request:"GetFeature"},featureClass:null,format:null,formatObject:null,formatOptions:null,vectorMode:true,encodeBBOX:false,extractAttributes:false,initialize:function(name,url,params,options){if(options==undefined){options={};}
if(options.featureClass||!OpenLayers.Layer.Vector||!OpenLayers.Feature.Vector){this.vectorMode=false;}
OpenLayers.Util.extend(options,{'reportError':false});var newArguments=[];newArguments.push(name,options);OpenLayers.Layer.Vector.prototype.initialize.apply(this,newArguments);if(!this.renderer||!this.vectorMode){this.vectorMode=false;if(!options.featureClass){options.featureClass=OpenLayers.Feature.WFS;}
OpenLayers.Layer.Markers.prototype.initialize.apply(this,newArguments);}
if(this.params&&this.params.typename&&!this.options.typename){this.options.typename=this.params.typename;}
if(!this.options.geometry_column){this.options.geometry_column="the_geom";}
this.params=OpenLayers.Util.applyDefaults(params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));this.url=url;},destroy:function(){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.destroy.apply(this,arguments);}else{OpenLayers.Layer.Markers.prototype.destroy.apply(this,arguments);}
if(this.tile){this.tile.destroy();}
this.tile=null;this.ratio=null;this.featureClass=null;this.format=null;if(this.formatObject&&this.formatObject.destroy){this.formatObject.destroy();}
this.formatObject=null;this.formatOptions=null;this.vectorMode=null;this.encodeBBOX=null;this.extractAttributes=null;},setMap:function(map){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.setMap.apply(this,arguments);var options={'extractAttributes':this.extractAttributes};OpenLayers.Util.extend(options,this.formatOptions);if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
this.formatObject=this.format?new this.format(options):new OpenLayers.Format.GML(options);}else{OpenLayers.Layer.Markers.prototype.setMap.apply(this,arguments);}},moveTo:function(bounds,zoomChanged,dragging){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.moveTo.apply(this,arguments);}else{OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);}
if(dragging){return false;}
if(zoomChanged){if(this.vectorMode){this.renderer.clear();}}
if(this.options.minZoomLevel){OpenLayers.Console.warn(OpenLayers.i18n('minZoomLevelError'));if(this.map.getZoom()<this.options.minZoomLevel){return null;}}
if(bounds==null){bounds=this.map.getExtent();}
var firstRendering=(this.tile==null);var outOfBounds=(!firstRendering&&!this.tile.bounds.containsBounds(bounds));if(zoomChanged||firstRendering||(!dragging&&outOfBounds)){var center=bounds.getCenterLonLat();var tileWidth=bounds.getWidth()*this.ratio;var tileHeight=bounds.getHeight()*this.ratio;var tileBounds=new OpenLayers.Bounds(center.lon-(tileWidth/2),center.lat-(tileHeight/2),center.lon+(tileWidth/2),center.lat+(tileHeight/2));var tileSize=this.map.getSize();tileSize.w=tileSize.w*this.ratio;tileSize.h=tileSize.h*this.ratio;var ul=new OpenLayers.LonLat(tileBounds.left,tileBounds.top);var pos=this.map.getLayerPxFromLonLat(ul);var url=this.getFullRequestString();var params=null;var filter=this.params.filter||this.params.FILTER;if(filter){params={FILTER:filter};}
else{params={BBOX:this.encodeBBOX?tileBounds.toBBOX():tileBounds.toArray()};}
if(this.map&&!this.projection.equals(this.map.getProjectionObject())){var projectedBounds=tileBounds.clone();projectedBounds.transform(this.map.getProjectionObject(),this.projection);if(!filter){params.BBOX=this.encodeBBOX?projectedBounds.toBBOX():projectedBounds.toArray();}}
url+="&"+OpenLayers.Util.getParameterString(params);if(!this.tile){this.tile=new OpenLayers.Tile.WFS(this,pos,tileBounds,url,tileSize);this.addTileMonitoringHooks(this.tile);this.tile.draw();}else{if(this.vectorMode){this.destroyFeatures();this.renderer.clear();}else{this.clearMarkers();}
this.removeTileMonitoringHooks(this.tile);this.tile.destroy();this.tile=null;this.tile=new OpenLayers.Tile.WFS(this,pos,tileBounds,url,tileSize);this.addTileMonitoringHooks(this.tile);this.tile.draw();}}},addTileMonitoringHooks:function(tile){tile.onLoadStart=function(){if(this==this.layer.tile){this.layer.events.triggerEvent("loadstart");}};tile.events.register("loadstart",tile,tile.onLoadStart);tile.onLoadEnd=function(){if(this==this.layer.tile){this.layer.events.triggerEvent("tileloaded");this.layer.events.triggerEvent("loadend");}};tile.events.register("loadend",tile,tile.onLoadEnd);tile.events.register("unload",tile,tile.onLoadEnd);},removeTileMonitoringHooks:function(tile){tile.unload();tile.events.un({"loadstart":tile.onLoadStart,"loadend":tile.onLoadEnd,"unload":tile.onLoadEnd,scope:tile});},onMapResize:function(){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.onMapResize.apply(this,arguments);}else{OpenLayers.Layer.Markers.prototype.onMapResize.apply(this,arguments);}},display:function(){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.display.apply(this,arguments);}else{OpenLayers.Layer.Markers.prototype.display.apply(this,arguments);}},mergeNewParams:function(newParams){var upperParams=OpenLayers.Util.upperCaseObject(newParams);var newArguments=[upperParams];return OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this,newArguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.WFS(this.name,this.url,this.params,this.options);}
if(this.vectorMode){obj=OpenLayers.Layer.Vector.prototype.clone.apply(this,[obj]);}else{obj=OpenLayers.Layer.Markers.prototype.clone.apply(this,[obj]);}
return obj;},getFullRequestString:function(newParams,altUrl){var projectionCode=this.projection.getCode()||this.map.getProjection();this.params.SRS=(projectionCode=="none")?null:projectionCode;return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments);},commit:function(){if(!this.writer){var options={};if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
this.writer=new OpenLayers.Format.WFS(options,this);}
var data=this.writer.write(this.features);OpenLayers.Request.POST({url:this.url,data:data,success:this.commitSuccess,failure:this.commitFailure,scope:this});},commitSuccess:function(request){var response=request.responseText;if(response.indexOf('SUCCESS')!=-1){this.commitReport(OpenLayers.i18n("commitSuccess",{'response':response}));for(var i=0;i<this.features.length;i++){this.features[i].state=null;}}else if(response.indexOf('FAILED')!=-1||response.indexOf('Exception')!=-1){this.commitReport(OpenLayers.i18n("commitFailed",{'response':response}));}},commitFailure:function(request){},commitReport:function(string,response){OpenLayers.Console.userError(string);},refresh:function(){if(this.tile){if(this.vectorMode){this.renderer.clear();this.features.length=0;}else{this.clearMarkers();this.markers.length=0;}
this.tile.draw();}},getDataExtent:function(){var extent;if(this.vectorMode){extent=OpenLayers.Layer.Vector.prototype.getDataExtent.apply(this);}else{extent=OpenLayers.Layer.Markers.prototype.getDataExtent.apply(this);}
return extent;},setOpacity:function(opacity){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.setOpacity.apply(this,[opacity]);}else{OpenLayers.Layer.Markers.prototype.setOpacity.apply(this,[opacity]);}},CLASS_NAME:"OpenLayers.Layer.WFS"});OpenLayers.Strategy.BBOX=OpenLayers.Class(OpenLayers.Strategy,{bounds:null,resolution:null,ratio:2,resFactor:null,response:null,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.call(this);if(activated){this.layer.events.on({"moveend":this.update,scope:this});this.layer.events.on({"refresh":this.update,scope:this});}
return activated;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){this.layer.events.un({"moveend":this.update,scope:this});this.layer.events.un({"refresh":this.update,scope:this});}
return deactivated;},update:function(options){var mapBounds=this.getMapBounds();if((options&&options.force)||this.invalidBounds(mapBounds)){this.calculateBounds(mapBounds);this.resolution=this.layer.map.getResolution();this.triggerRead();}},getMapBounds:function(){var bounds=this.layer.map.getExtent();if(!this.layer.projection.equals(this.layer.map.getProjectionObject())){bounds=bounds.clone().transform(this.layer.map.getProjectionObject(),this.layer.projection);}
return bounds;},invalidBounds:function(mapBounds){if(!mapBounds){mapBounds=this.getMapBounds();}
var invalid=!this.bounds||!this.bounds.containsBounds(mapBounds);if(!invalid&&this.resFactor){var ratio=this.resolution/this.layer.map.getResolution();invalid=(ratio>=this.resFactor||ratio<=(1/this.resFactor));}
return invalid;},calculateBounds:function(mapBounds){if(!mapBounds){mapBounds=this.getMapBounds();}
var center=mapBounds.getCenterLonLat();var dataWidth=mapBounds.getWidth()*this.ratio;var dataHeight=mapBounds.getHeight()*this.ratio;this.bounds=new OpenLayers.Bounds(center.lon-(dataWidth/2),center.lat-(dataHeight/2),center.lon+(dataWidth/2),center.lat+(dataHeight/2));},triggerRead:function(){this.layer.protocol.abort(this.response);this.layer.events.triggerEvent("loadstart");this.response=this.layer.protocol.read({filter:this.createFilter(),callback:this.merge,scope:this});},createFilter:function(){var filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,value:this.bounds,projection:this.layer.projection});if(this.layer.filter){filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND,filters:[this.layer.filter,filter]});}
return filter;},merge:function(resp){this.layer.destroyFeatures();var features=resp.features;if(features&&features.length>0){var remote=this.layer.projection;var local=this.layer.map.getProjectionObject();if(!local.equals(remote)){var geom;for(var i=0,len=features.length;i<len;++i){geom=features[i].geometry;if(geom){geom.transform(remote,local);}}}
this.layer.addFeatures(features);}
this.layer.events.triggerEvent("loadend");},CLASS_NAME:"OpenLayers.Strategy.BBOX"});OpenLayers.Control.SelectFeature=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:["beforefeaturehighlighted","featurehighlighted","featureunhighlighted"],multipleKey:null,toggleKey:null,multiple:false,clickout:true,toggle:false,hover:false,highlightOnly:false,box:false,onBeforeSelect:function(){},onSelect:function(){},onUnselect:function(){},scope:null,geometryTypes:null,layer:null,layers:null,callbacks:null,selectStyle:null,renderIntent:"select",handlers:null,initialize:function(layers,options){this.EVENT_TYPES=OpenLayers.Control.SelectFeature.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);if(this.scope===null){this.scope=this;}
if(layers instanceof Array){this.layers=layers;this.layer=new OpenLayers.Layer.Vector.RootContainer(this.id+"_container",{layers:layers});}else{this.layer=layers;}
var callbacks={click:this.clickFeature,clickout:this.clickoutFeature};if(this.hover){callbacks.over=this.overFeature;callbacks.out=this.outFeature;}
this.callbacks=OpenLayers.Util.extend(callbacks,this.callbacks);this.handlers={feature:new OpenLayers.Handler.Feature(this,this.layer,this.callbacks,{geometryTypes:this.geometryTypes})};if(this.box){this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},{boxDivClassName:"olHandlerBoxSelectFeature"});}},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this,arguments);if(this.layers){this.layer.destroy();}},activate:function(){if(!this.active){if(this.layers){this.map.addLayer(this.layer);}
this.handlers.feature.activate();if(this.box&&this.handlers.box){this.handlers.box.activate();}}
return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){if(this.active){this.handlers.feature.deactivate();if(this.handlers.box){this.handlers.box.deactivate();}
if(this.layers){this.map.removeLayer(this.layer);}}
return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},unselectAll:function(options){var layers=this.layers||[this.layer];var layer,feature;for(var l=0;l<layers.length;++l){layer=layers[l];for(var i=layer.selectedFeatures.length-1;i>=0;--i){feature=layer.selectedFeatures[i];if(!options||options.except!=feature){this.unselect(feature);}}}},clickFeature:function(feature){if(!this.hover){var selected=(OpenLayers.Util.indexOf(feature.layer.selectedFeatures,feature)>-1);if(selected){if(this.toggleSelect()){this.unselect(feature);}else if(!this.multipleSelect()){this.unselectAll({except:feature});}}else{if(!this.multipleSelect()){this.unselectAll({except:feature});}
this.select(feature);}}},multipleSelect:function(){return this.multiple||(this.handlers.feature.evt&&this.handlers.feature.evt[this.multipleKey]);},toggleSelect:function(){return this.toggle||(this.handlers.feature.evt&&this.handlers.feature.evt[this.toggleKey]);},clickoutFeature:function(feature){if(!this.hover&&this.clickout){this.unselectAll();}},overFeature:function(feature){var layer=feature.layer;if(this.hover){if(this.highlightOnly){this.highlight(feature);}else if(OpenLayers.Util.indexOf(layer.selectedFeatures,feature)==-1){this.select(feature);}}},outFeature:function(feature){if(this.hover){if(this.highlightOnly){if(feature._lastHighlighter==this.id){if(feature._prevHighlighter&&feature._prevHighlighter!=this.id){delete feature._lastHighlighter;var control=this.map.getControl(feature._prevHighlighter);if(control){control.highlight(feature);}}else{this.unhighlight(feature);}}}else{this.unselect(feature);}}},highlight:function(feature){var layer=feature.layer;var cont=this.events.triggerEvent("beforefeaturehighlighted",{feature:feature});if(cont!==false){feature._prevHighlighter=feature._lastHighlighter;feature._lastHighlighter=this.id;var style=this.selectStyle||this.renderIntent;layer.drawFeature(feature,style);this.events.triggerEvent("featurehighlighted",{feature:feature});}},unhighlight:function(feature){var layer=feature.layer;feature._lastHighlighter=feature._prevHighlighter;delete feature._prevHighlighter;layer.drawFeature(feature,feature.style||feature.layer.style||"default");this.events.triggerEvent("featureunhighlighted",{feature:feature});},select:function(feature){var cont=this.onBeforeSelect.call(this.scope,feature);var layer=feature.layer;if(cont!==false){cont=layer.events.triggerEvent("beforefeatureselected",{feature:feature});if(cont!==false){layer.selectedFeatures.push(feature);this.highlight(feature);layer.events.triggerEvent("featureselected",{feature:feature});this.onSelect.call(this.scope,feature);}}},unselect:function(feature){var layer=feature.layer;this.unhighlight(feature);OpenLayers.Util.removeItem(layer.selectedFeatures,feature);layer.events.triggerEvent("featureunselected",{feature:feature});this.onUnselect.call(this.scope,feature);},selectBox:function(position){if(position instanceof OpenLayers.Bounds){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));var bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);if(!this.multipleSelect()){this.unselectAll();}
var prevMultiple=this.multiple;this.multiple=true;var layers=this.layers||[this.layer];var layer;for(var l=0;l<layers.length;++l){layer=layers[l];for(var i=0,len=layer.features.length;i<len;++i){var feature=layer.features[i];if(this.geometryTypes==null||OpenLayers.Util.indexOf(this.geometryTypes,feature.geometry.CLASS_NAME)>-1){if(bounds.toGeometry().intersects(feature.geometry)){if(OpenLayers.Util.indexOf(layer.selectedFeatures,feature)==-1){this.select(feature);}}}}}
this.multiple=prevMultiple;}},setMap:function(map){this.handlers.feature.setMap(map);if(this.box){this.handlers.box.setMap(map);}
OpenLayers.Control.prototype.setMap.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.SelectFeature"});OpenLayers.Format.Filter.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ogc:"http://www.opengis.net/ogc",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"ogc",schemaLocation:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){var obj={};this.readers.ogc["Filter"].apply(this,[data,obj]);return obj.filter;},readers:{"ogc":{"Filter":function(node,parent){var obj={fids:[],filters:[]};this.readChildNodes(node,obj);if(obj.fids.length>0){parent.filter=new OpenLayers.Filter.FeatureId({fids:obj.fids});}else if(obj.filters.length>0){parent.filter=obj.filters[0];}},"FeatureId":function(node,obj){var fid=node.getAttribute("fid");if(fid){obj.fids.push(fid);}},"And":function(node,obj){var filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND});this.readChildNodes(node,filter);obj.filters.push(filter);},"Or":function(node,obj){var filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.OR});this.readChildNodes(node,filter);obj.filters.push(filter);},"Not":function(node,obj){var filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.NOT});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLessThan":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsGreaterThan":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLessThanOrEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsGreaterThanOrEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsBetween":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.BETWEEN});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLike":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LIKE});this.readChildNodes(node,filter);var wildCard=node.getAttribute("wildCard");var singleChar=node.getAttribute("singleChar");var esc=node.getAttribute("escape");filter.value2regex(wildCard,singleChar,esc);obj.filters.push(filter);},"Literal":function(node,obj){obj.value=OpenLayers.String.numericIf(this.getChildValue(node));},"PropertyName":function(node,filter){filter.property=this.getChildValue(node);},"LowerBoundary":function(node,filter){filter.lowerBoundary=OpenLayers.String.numericIf(this.readOgcExpression(node));},"UpperBoundary":function(node,filter){filter.upperBoundary=OpenLayers.String.numericIf(this.readOgcExpression(node));},"Intersects":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.INTERSECTS);},"Within":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.WITHIN);},"Contains":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.CONTAINS);},"DWithin":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.DWITHIN);},"Distance":function(node,obj){obj.distance=parseInt(this.getChildValue(node));obj.distanceUnits=node.getAttribute("units");}}},readSpatial:function(node,obj,type){var filter=new OpenLayers.Filter.Spatial({type:type});this.readChildNodes(node,filter);filter.value=filter.components[0];delete filter.components;obj.filters.push(filter);},readOgcExpression:function(node){var obj={};this.readChildNodes(node,obj);var value=obj.value;if(!value){value=this.getChildValue(node);}
return value;},write:function(filter){return this.writers.ogc["Filter"].apply(this,[filter]);},writers:{"ogc":{"Filter":function(filter){var node=this.createElementNSPlus("ogc:Filter");var sub=filter.CLASS_NAME.split(".").pop();if(sub=="FeatureId"){for(var i=0;i<filter.fids.length;++i){this.writeNode("FeatureId",filter.fids[i],node);}}else{this.writeNode(this.getFilterType(filter),filter,node);}
return node;},"FeatureId":function(fid){return this.createElementNSPlus("ogc:FeatureId",{attributes:{fid:fid}});},"And":function(filter){var node=this.createElementNSPlus("ogc:And");var childFilter;for(var i=0;i<filter.filters.length;++i){childFilter=filter.filters[i];this.writeNode(this.getFilterType(childFilter),childFilter,node);}
return node;},"Or":function(filter){var node=this.createElementNSPlus("ogc:Or");var childFilter;for(var i=0;i<filter.filters.length;++i){childFilter=filter.filters[i];this.writeNode(this.getFilterType(childFilter),childFilter,node);}
return node;},"Not":function(filter){var node=this.createElementNSPlus("ogc:Not");var childFilter=filter.filters[0];this.writeNode(this.getFilterType(childFilter),childFilter,node);return node;},"PropertyIsLessThan":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsLessThan");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsGreaterThan":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsGreaterThan");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsLessThanOrEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsGreaterThanOrEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsBetween":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsBetween");this.writeNode("PropertyName",filter,node);this.writeNode("LowerBoundary",filter,node);this.writeNode("UpperBoundary",filter,node);return node;},"PropertyIsLike":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsLike",{attributes:{wildCard:"*",singleChar:".",escape:"!"}});this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.regex2value(),node);return node;},"PropertyName":function(filter){return this.createElementNSPlus("ogc:PropertyName",{value:filter.property});},"Literal":function(value){return this.createElementNSPlus("ogc:Literal",{value:value});},"LowerBoundary":function(filter){var node=this.createElementNSPlus("ogc:LowerBoundary");this.writeNode("Literal",filter.lowerBoundary,node);return node;},"UpperBoundary":function(filter){var node=this.createElementNSPlus("ogc:UpperBoundary");this.writeNode("Literal",filter.upperBoundary,node);return node;},"INTERSECTS":function(filter){return this.writeSpatial(filter,"Intersects");},"WITHIN":function(filter){return this.writeSpatial(filter,"Within");},"CONTAINS":function(filter){return this.writeSpatial(filter,"Contains");},"DWITHIN":function(filter){var node=this.writeSpatial(filter,"DWithin");this.writeNode("Distance",filter,node);return node;},"Distance":function(filter){return this.createElementNSPlus("ogc:Distance",{attributes:{units:filter.distanceUnits},value:filter.distance});}}},getFilterType:function(filter){var filterType=this.filterMap[filter.type];if(!filterType){throw"Filter writing not supported for rule type: "+filter.type;}
return filterType;},filterMap:{"&&":"And","||":"Or","!":"Not","==":"PropertyIsEqualTo","!=":"PropertyIsNotEqualTo","<":"PropertyIsLessThan",">":"PropertyIsGreaterThan","<=":"PropertyIsLessThanOrEqualTo",">=":"PropertyIsGreaterThanOrEqualTo","..":"PropertyIsBetween","~":"PropertyIsLike","BBOX":"BBOX","DWITHIN":"DWITHIN","WITHIN":"WITHIN","CONTAINS":"CONTAINS","INTERSECTS":"INTERSECTS"},CLASS_NAME:"OpenLayers.Format.Filter.v1"});OpenLayers.Geometry.Curve=OpenLayers.Class(OpenLayers.Geometry.MultiPoint,{componentTypes:["OpenLayers.Geometry.Point"],initialize:function(points){OpenLayers.Geometry.MultiPoint.prototype.initialize.apply(this,arguments);},getLength:function(){var length=0.0;if(this.components&&(this.components.length>1)){for(var i=1,len=this.components.length;i<len;i++){length+=this.components[i-1].distanceTo(this.components[i]);}}
return length;},getGeodesicLength:function(projection){var geom=this;if(projection){var gg=new OpenLayers.Projection("EPSG:4326");if(!gg.equals(projection)){geom=this.clone().transform(projection,gg);}}
var length=0.0;if(geom.components&&(geom.components.length>1)){var p1,p2;for(var i=1,len=geom.components.length;i<len;i++){p1=geom.components[i-1];p2=geom.components[i];length+=OpenLayers.Util.distVincenty({lon:p1.x,lat:p1.y},{lon:p2.x,lat:p2.y});}}
return length*1000;},CLASS_NAME:"OpenLayers.Geometry.Curve"});OpenLayers.Control.ModifyFeature=OpenLayers.Class(OpenLayers.Control,{geometryTypes:null,clickout:true,toggle:true,layer:null,feature:null,vertices:null,virtualVertices:null,selectControl:null,dragControl:null,handlers:null,deleteCodes:null,virtualStyle:null,mode:null,modified:false,radiusHandle:null,dragHandle:null,onModificationStart:function(){},onModification:function(){},onModificationEnd:function(){},initialize:function(layer,options){this.layer=layer;this.vertices=[];this.virtualVertices=[];this.virtualStyle=OpenLayers.Util.extend({},this.layer.style||this.layer.styleMap.createSymbolizer());this.virtualStyle.fillOpacity=0.3;this.virtualStyle.strokeOpacity=0.3;this.deleteCodes=[46,68];this.mode=OpenLayers.Control.ModifyFeature.RESHAPE;OpenLayers.Control.prototype.initialize.apply(this,[options]);if(!(this.deleteCodes instanceof Array)){this.deleteCodes=[this.deleteCodes];}
var control=this;var selectOptions={geometryTypes:this.geometryTypes,clickout:this.clickout,toggle:this.toggle,onBeforeSelect:this.beforeSelectFeature,onSelect:this.selectFeature,onUnselect:this.unselectFeature,scope:this};this.selectControl=new OpenLayers.Control.SelectFeature(layer,selectOptions);var dragOptions={geometryTypes:["OpenLayers.Geometry.Point"],snappingOptions:this.snappingOptions,onStart:function(feature,pixel){control.dragStart.apply(control,[feature,pixel]);},onDrag:function(feature,pixel){control.dragVertex.apply(control,[feature,pixel]);},onComplete:function(feature){control.dragComplete.apply(control,[feature]);}};this.dragControl=new OpenLayers.Control.DragFeature(layer,dragOptions);var keyboardOptions={keydown:this.handleKeypress};this.handlers={keyboard:new OpenLayers.Handler.Keyboard(this,keyboardOptions)};},destroy:function(){this.layer=null;this.selectControl.destroy();this.dragControl.destroy();OpenLayers.Control.prototype.destroy.apply(this,[]);},activate:function(){return(this.selectControl.activate()&&this.handlers.keyboard.activate()&&OpenLayers.Control.prototype.activate.apply(this,arguments));},deactivate:function(){var deactivated=false;if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){this.layer.removeFeatures(this.vertices,{silent:true});this.layer.removeFeatures(this.virtualVertices,{silent:true});this.vertices=[];this.dragControl.deactivate();if(this.feature&&this.feature.geometry&&this.feature.layer){this.selectControl.unselect.apply(this.selectControl,[this.feature]);}
this.selectControl.deactivate();this.handlers.keyboard.deactivate();deactivated=true;}
return deactivated;},beforeSelectFeature:function(feature){return this.layer.events.triggerEvent("beforefeaturemodified",{feature:feature});},selectFeature:function(feature){this.feature=feature;this.modified=false;this.resetVertices();this.dragControl.activate();this.onModificationStart(this.feature);},unselectFeature:function(feature){this.layer.removeFeatures(this.vertices,{silent:true});this.vertices=[];this.layer.destroyFeatures(this.virtualVertices,{silent:true});this.virtualVertices=[];if(this.dragHandle){this.layer.destroyFeatures([this.dragHandle],{silent:true});delete this.dragHandle;}
if(this.radiusHandle){this.layer.destroyFeatures([this.radiusHandle],{silent:true});delete this.radiusHandle;}
this.feature=null;this.dragControl.deactivate();this.onModificationEnd(feature);this.layer.events.triggerEvent("afterfeaturemodified",{feature:feature,modified:this.modified});this.modified=false;},dragStart:function(feature,pixel){if(feature!=this.feature&&!feature.geometry.parent&&feature!=this.dragHandle&&feature!=this.radiusHandle){if(this.feature){this.selectControl.clickFeature.apply(this.selectControl,[this.feature]);}
if(this.geometryTypes==null||OpenLayers.Util.indexOf(this.geometryTypes,feature.geometry.CLASS_NAME)!=-1){this.selectControl.clickFeature.apply(this.selectControl,[feature]);this.dragControl.overFeature.apply(this.dragControl,[feature]);this.dragControl.lastPixel=pixel;this.dragControl.handlers.drag.started=true;this.dragControl.handlers.drag.start=pixel;this.dragControl.handlers.drag.last=pixel;}}},dragVertex:function(vertex,pixel){this.modified=true;if(this.feature.geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){if(this.feature!=vertex){this.feature=vertex;}
this.layer.events.triggerEvent("vertexmodified",{vertex:vertex.geometry,feature:this.feature,pixel:pixel});}else{if(vertex._index){vertex.geometry.parent.addComponent(vertex.geometry,vertex._index);delete vertex._index;OpenLayers.Util.removeItem(this.virtualVertices,vertex);this.vertices.push(vertex);}else if(vertex==this.dragHandle){this.layer.removeFeatures(this.vertices,{silent:true});this.vertices=[];if(this.radiusHandle){this.layer.destroyFeatures([this.radiusHandle],{silent:true});this.radiusHandle=null;}}else if(vertex!==this.radiusHandle){this.layer.events.triggerEvent("vertexmodified",{vertex:vertex.geometry,feature:this.feature,pixel:pixel});}
if(this.virtualVertices.length>0){this.layer.destroyFeatures(this.virtualVertices,{silent:true});this.virtualVertices=[];}
this.layer.drawFeature(this.feature,this.selectControl.renderIntent);}
this.layer.drawFeature(vertex);},dragComplete:function(vertex){this.resetVertices();this.setFeatureState();this.onModification(this.feature);this.layer.events.triggerEvent("featuremodified",{feature:this.feature});},setFeatureState:function(){if(this.feature.state!=OpenLayers.State.INSERT&&this.feature.state!=OpenLayers.State.DELETE){this.feature.state=OpenLayers.State.UPDATE;}},resetVertices:function(){if(this.dragControl.feature){this.dragControl.outFeature(this.dragControl.feature);}
if(this.vertices.length>0){this.layer.removeFeatures(this.vertices,{silent:true});this.vertices=[];}
if(this.virtualVertices.length>0){this.layer.removeFeatures(this.virtualVertices,{silent:true});this.virtualVertices=[];}
if(this.dragHandle){this.layer.destroyFeatures([this.dragHandle],{silent:true});this.dragHandle=null;}
if(this.radiusHandle){this.layer.destroyFeatures([this.radiusHandle],{silent:true});this.radiusHandle=null;}
if(this.feature&&this.feature.geometry.CLASS_NAME!="OpenLayers.Geometry.Point"){if((this.mode&OpenLayers.Control.ModifyFeature.DRAG)){this.collectDragHandle();}
if((this.mode&(OpenLayers.Control.ModifyFeature.ROTATE|OpenLayers.Control.ModifyFeature.RESIZE))){this.collectRadiusHandle();}
if(this.mode&OpenLayers.Control.ModifyFeature.RESHAPE){if(!(this.mode&OpenLayers.Control.ModifyFeature.RESIZE)){this.collectVertices();}}}},handleKeypress:function(evt){var code=evt.keyCode;if(this.feature&&OpenLayers.Util.indexOf(this.deleteCodes,code)!=-1){var vertex=this.dragControl.feature;if(vertex&&OpenLayers.Util.indexOf(this.vertices,vertex)!=-1&&!this.dragControl.handlers.drag.dragging&&vertex.geometry.parent){vertex.geometry.parent.removeComponent(vertex.geometry);this.layer.drawFeature(this.feature,this.selectControl.renderIntent);this.resetVertices();this.setFeatureState();this.onModification(this.feature);this.layer.events.triggerEvent("featuremodified",{feature:this.feature});}}},collectVertices:function(){this.vertices=[];this.virtualVertices=[];var control=this;function collectComponentVertices(geometry){var i,vertex,component,len;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){vertex=new OpenLayers.Feature.Vector(geometry);vertex._sketch=true;control.vertices.push(vertex);}else{var numVert=geometry.components.length;if(geometry.CLASS_NAME=="OpenLayers.Geometry.LinearRing"){numVert-=1;}
for(i=0;i<numVert;++i){component=geometry.components[i];if(component.CLASS_NAME=="OpenLayers.Geometry.Point"){vertex=new OpenLayers.Feature.Vector(component);vertex._sketch=true;control.vertices.push(vertex);}else{collectComponentVertices(component);}}
if(geometry.CLASS_NAME!="OpenLayers.Geometry.MultiPoint"){for(i=0,len=geometry.components.length;i<len-1;++i){var prevVertex=geometry.components[i];var nextVertex=geometry.components[i+1];if(prevVertex.CLASS_NAME=="OpenLayers.Geometry.Point"&&nextVertex.CLASS_NAME=="OpenLayers.Geometry.Point"){var x=(prevVertex.x+nextVertex.x)/2;var y=(prevVertex.y+nextVertex.y)/2;var point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(x,y),null,control.virtualStyle);point.geometry.parent=geometry;point._index=i+1;point._sketch=true;control.virtualVertices.push(point);}}}}}
collectComponentVertices.call(this,this.feature.geometry);this.layer.addFeatures(this.virtualVertices,{silent:true});this.layer.addFeatures(this.vertices,{silent:true});},collectDragHandle:function(){var geometry=this.feature.geometry;var center=geometry.getBounds().getCenterLonLat();var originGeometry=new OpenLayers.Geometry.Point(center.lon,center.lat);var origin=new OpenLayers.Feature.Vector(originGeometry);originGeometry.move=function(x,y){OpenLayers.Geometry.Point.prototype.move.call(this,x,y);geometry.move(x,y);};origin._sketch=true;this.dragHandle=origin;this.layer.addFeatures([this.dragHandle],{silent:true});},collectRadiusHandle:function(){var geometry=this.feature.geometry;var bounds=geometry.getBounds();var center=bounds.getCenterLonLat();var originGeometry=new OpenLayers.Geometry.Point(center.lon,center.lat);var radiusGeometry=new OpenLayers.Geometry.Point(bounds.right,bounds.bottom);var radius=new OpenLayers.Feature.Vector(radiusGeometry);var resize=(this.mode&OpenLayers.Control.ModifyFeature.RESIZE);var reshape=(this.mode&OpenLayers.Control.ModifyFeature.RESHAPE);var rotate=(this.mode&OpenLayers.Control.ModifyFeature.ROTATE);radiusGeometry.move=function(x,y){OpenLayers.Geometry.Point.prototype.move.call(this,x,y);var dx1=this.x-originGeometry.x;var dy1=this.y-originGeometry.y;var dx0=dx1-x;var dy0=dy1-y;if(rotate){var a0=Math.atan2(dy0,dx0);var a1=Math.atan2(dy1,dx1);var angle=a1-a0;angle*=180/Math.PI;geometry.rotate(angle,originGeometry);}
if(resize){var scale,ratio;if(reshape){scale=dy1/dy0;ratio=(dx1/dx0)/scale;}else{var l0=Math.sqrt((dx0*dx0)+(dy0*dy0));var l1=Math.sqrt((dx1*dx1)+(dy1*dy1));scale=l1/l0;}
geometry.resize(scale,originGeometry,ratio);}};radius._sketch=true;this.radiusHandle=radius;this.layer.addFeatures([this.radiusHandle],{silent:true});},setMap:function(map){this.selectControl.setMap(map);this.dragControl.setMap(map);OpenLayers.Control.prototype.setMap.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.ModifyFeature"});OpenLayers.Control.ModifyFeature.RESHAPE=1;OpenLayers.Control.ModifyFeature.RESIZE=2;OpenLayers.Control.ModifyFeature.ROTATE=4;OpenLayers.Control.ModifyFeature.DRAG=8;OpenLayers.Geometry.LineString=OpenLayers.Class(OpenLayers.Geometry.Curve,{initialize:function(points){OpenLayers.Geometry.Curve.prototype.initialize.apply(this,arguments);},removeComponent:function(point){if(this.components&&(this.components.length>2)){OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);}},intersects:function(geometry){var intersect=false;var type=geometry.CLASS_NAME;if(type=="OpenLayers.Geometry.LineString"||type=="OpenLayers.Geometry.LinearRing"||type=="OpenLayers.Geometry.Point"){var segs1=this.getSortedSegments();var segs2;if(type=="OpenLayers.Geometry.Point"){segs2=[{x1:geometry.x,y1:geometry.y,x2:geometry.x,y2:geometry.y}];}else{segs2=geometry.getSortedSegments();}
var seg1,seg1x1,seg1x2,seg1y1,seg1y2,seg2,seg2y1,seg2y2;outer:for(var i=0,len=segs1.length;i<len;++i){seg1=segs1[i];seg1x1=seg1.x1;seg1x2=seg1.x2;seg1y1=seg1.y1;seg1y2=seg1.y2;inner:for(var j=0,jlen=segs2.length;j<jlen;++j){seg2=segs2[j];if(seg2.x1>seg1x2){break;}
if(seg2.x2<seg1x1){continue;}
seg2y1=seg2.y1;seg2y2=seg2.y2;if(Math.min(seg2y1,seg2y2)>Math.max(seg1y1,seg1y2)){continue;}
if(Math.max(seg2y1,seg2y2)<Math.min(seg1y1,seg1y2)){continue;}
if(OpenLayers.Geometry.segmentsIntersect(seg1,seg2)){intersect=true;break outer;}}}}else{intersect=geometry.intersects(this);}
return intersect;},getSortedSegments:function(){var numSeg=this.components.length-1;var segments=new Array(numSeg);for(var i=0;i<numSeg;++i){point1=this.components[i];point2=this.components[i+1];if(point1.x<point2.x){segments[i]={x1:point1.x,y1:point1.y,x2:point2.x,y2:point2.y};}else{segments[i]={x1:point2.x,y1:point2.y,x2:point1.x,y2:point1.y};}}
function byX1(seg1,seg2){return seg1.x1-seg2.x1;}
return segments.sort(byX1);},splitWithSegment:function(seg,options){var edge=!(options&&options.edge===false);var tolerance=options&&options.tolerance;var lines=[];var verts=this.getVertices();var points=[];var intersections=[];var split=false;var vert1,vert2,point;var node,vertex,target;var interOptions={point:true,tolerance:tolerance};var result=null;for(var i=0,stop=verts.length-2;i<=stop;++i){vert1=verts[i];points.push(vert1.clone());vert2=verts[i+1];target={x1:vert1.x,y1:vert1.y,x2:vert2.x,y2:vert2.y};point=OpenLayers.Geometry.segmentsIntersect(seg,target,interOptions);if(point instanceof OpenLayers.Geometry.Point){if((point.x===seg.x1&&point.y===seg.y1)||(point.x===seg.x2&&point.y===seg.y2)||point.equals(vert1)||point.equals(vert2)){vertex=true;}else{vertex=false;}
if(vertex||edge){if(!point.equals(intersections[intersections.length-1])){intersections.push(point.clone());}
if(i===0){if(point.equals(vert1)){continue;}}
if(point.equals(vert2)){continue;}
split=true;if(!point.equals(vert1)){points.push(point);}
lines.push(new OpenLayers.Geometry.LineString(points));points=[point.clone()];}}}
if(split){points.push(vert2.clone());lines.push(new OpenLayers.Geometry.LineString(points));}
if(intersections.length>0){var xDir=seg.x1<seg.x2?1:-1;var yDir=seg.y1<seg.y2?1:-1;result={lines:lines,points:intersections.sort(function(p1,p2){return(xDir*p1.x-xDir*p2.x)||(yDir*p1.y-yDir*p2.y);})};}
return result;},split:function(target,options){var results=null;var mutual=options&&options.mutual;var sourceSplit,targetSplit,sourceParts,targetParts;if(target instanceof OpenLayers.Geometry.LineString){var verts=this.getVertices();var vert1,vert2,seg,splits,lines,point;var points=[];sourceParts=[];for(var i=0,stop=verts.length-2;i<=stop;++i){vert1=verts[i];vert2=verts[i+1];seg={x1:vert1.x,y1:vert1.y,x2:vert2.x,y2:vert2.y};targetParts=targetParts||[target];if(mutual){points.push(vert1.clone());}
for(var j=0;j<targetParts.length;++j){splits=targetParts[j].splitWithSegment(seg,options);if(splits){lines=splits.lines;if(lines.length>0){lines.unshift(j,1);Array.prototype.splice.apply(targetParts,lines);j+=lines.length-2;}
if(mutual){for(var k=0,len=splits.points.length;k<len;++k){point=splits.points[k];if(!point.equals(vert1)){points.push(point);sourceParts.push(new OpenLayers.Geometry.LineString(points));if(point.equals(vert2)){points=[];}else{points=[point.clone()];}}}}}}}
if(mutual&&sourceParts.length>0&&points.length>0){points.push(vert2.clone());sourceParts.push(new OpenLayers.Geometry.LineString(points));}}else{results=target.splitWith(this,options);}
if(targetParts&&targetParts.length>1){targetSplit=true;}else{targetParts=[];}
if(sourceParts&&sourceParts.length>1){sourceSplit=true;}else{sourceParts=[];}
if(targetSplit||sourceSplit){if(mutual){results=[sourceParts,targetParts];}else{results=targetParts;}}
return results;},splitWith:function(geometry,options){return geometry.split(this,options);},getVertices:function(nodes){var vertices;if(nodes===true){vertices=[this.components[0],this.components[this.components.length-1]];}else if(nodes===false){vertices=this.components.slice(1,this.components.length-1);}else{vertices=this.components.slice();}
return vertices;},distanceTo:function(geometry,options){var edge=!(options&&options.edge===false);var details=edge&&options&&options.details;var result,best={};var min=Number.POSITIVE_INFINITY;if(geometry instanceof OpenLayers.Geometry.Point){var segs=this.getSortedSegments();var x=geometry.x;var y=geometry.y;var seg;for(var i=0,len=segs.length;i<len;++i){seg=segs[i];result=OpenLayers.Geometry.distanceToSegment(geometry,seg);if(result.distance<min){min=result.distance;best=result;if(min===0){break;}}else{if(seg.x2>x&&((y>seg.y1&&y<seg.y2)||(y<seg.y1&&y>seg.y2))){break;}}}
if(details){best={distance:best.distance,x0:best.x,y0:best.y,x1:x,y1:y};}else{best=best.distance;}}else if(geometry instanceof OpenLayers.Geometry.LineString){var segs0=this.getSortedSegments();var segs1=geometry.getSortedSegments();var seg0,seg1,intersection,x0,y0;var len1=segs1.length;var interOptions={point:true};outer:for(var i=0,len=segs0.length;i<len;++i){seg0=segs0[i];x0=seg0.x1;y0=seg0.y1;for(var j=0;j<len1;++j){seg1=segs1[j];intersection=OpenLayers.Geometry.segmentsIntersect(seg0,seg1,interOptions);if(intersection){min=0;best={distance:0,x0:intersection.x,y0:intersection.y,x1:intersection.x,y1:intersection.y};break outer;}else{result=OpenLayers.Geometry.distanceToSegment({x:x0,y:y0},seg1);if(result.distance<min){min=result.distance;best={distance:min,x0:x0,y0:y0,x1:result.x,y1:result.y};}}}}
if(!details){best=best.distance;}
if(min!==0){if(seg0){result=geometry.distanceTo(new OpenLayers.Geometry.Point(seg0.x2,seg0.y2),options);var dist=details?result.distance:result;if(dist<min){if(details){best={distance:min,x0:result.x1,y0:result.y1,x1:result.x0,y1:result.y0};}else{best=dist;}}}}}else{best=geometry.distanceTo(this,options);if(details){best={distance:best.distance,x0:best.x1,y0:best.y1,x1:best.x0,y1:best.y0};}}
return best;},CLASS_NAME:"OpenLayers.Geometry.LineString"});OpenLayers.Format.GPX=OpenLayers.Class(OpenLayers.Format.XML,{extractWaypoints:true,extractTracks:true,extractRoutes:true,extractAttributes:true,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(doc){if(typeof doc=="string"){doc=OpenLayers.Format.XML.prototype.read.apply(this,[doc]);}
var features=[];if(this.extractTracks){var tracks=doc.getElementsByTagName("trk");for(var i=0,len=tracks.length;i<len;i++){var attrs={};if(this.extractAttributes){attrs=this.parseAttributes(tracks[i]);}
var segs=this.getElementsByTagNameNS(tracks[i],tracks[i].namespaceURI,"trkseg");for(var j=0,seglen=segs.length;j<seglen;j++){var track=this.extractSegment(segs[j],"trkpt");features.push(new OpenLayers.Feature.Vector(track,attrs));}}}
if(this.extractRoutes){var routes=doc.getElementsByTagName("rte");for(var k=0,klen=routes.length;k<klen;k++){var attrs={};if(this.extractAttributes){attrs=this.parseAttributes(routes[k]);}
var route=this.extractSegment(routes[k],"rtept");features.push(new OpenLayers.Feature.Vector(route,attrs));}}
if(this.extractWaypoints){var waypoints=doc.getElementsByTagName("wpt");for(var l=0,len=waypoints.length;l<len;l++){var attrs={};if(this.extractAttributes){attrs=this.parseAttributes(waypoints[l]);}
var wpt=new OpenLayers.Geometry.Point(waypoints[l].getAttribute("lon"),waypoints[l].getAttribute("lat"));features.push(new OpenLayers.Feature.Vector(wpt,attrs));}}
if(this.internalProjection&&this.externalProjection){for(var g=0,featLength=features.length;g<featLength;g++){features[g].geometry.transform(this.externalProjection,this.internalProjection);}}
return features;},extractSegment:function(segment,segmentType){var points=this.getElementsByTagNameNS(segment,segment.namespaceURI,segmentType);var point_features=[];for(var i=0,len=points.length;i<len;i++){point_features.push(new OpenLayers.Geometry.Point(points[i].getAttribute("lon"),points[i].getAttribute("lat")));}
return new OpenLayers.Geometry.LineString(point_features);},parseAttributes:function(node){var attributes={};var attrNode=node.firstChild;while(attrNode){if(attrNode.nodeType==1){var value=attrNode.firstChild;if(value.nodeType==3||value.nodeType==4){name=(attrNode.prefix)?attrNode.nodeName.split(":")[1]:attrNode.nodeName;if(name!="trkseg"&&name!="rtept"){attributes[name]=value.nodeValue;}}}
attrNode=attrNode.nextSibling;}
return attributes;},CLASS_NAME:"OpenLayers.Format.GPX"});OpenLayers.Geometry.LinearRing=OpenLayers.Class(OpenLayers.Geometry.LineString,{componentTypes:["OpenLayers.Geometry.Point"],initialize:function(points){OpenLayers.Geometry.LineString.prototype.initialize.apply(this,arguments);},addComponent:function(point,index){var added=false;var lastPoint=this.components.pop();if(index!=null||!point.equals(lastPoint)){added=OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,arguments);}
var firstPoint=this.components[0];OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);return added;},removeComponent:function(point){if(this.components.length>4){this.components.pop();OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);var firstPoint=this.components[0];OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);}},move:function(x,y){for(var i=0,len=this.components.length;i<len-1;i++){this.components[i].move(x,y);}},rotate:function(angle,origin){for(var i=0,len=this.components.length;i<len-1;++i){this.components[i].rotate(angle,origin);}},resize:function(scale,origin,ratio){for(var i=0,len=this.components.length;i<len-1;++i){this.components[i].resize(scale,origin,ratio);}
return this;},transform:function(source,dest){if(source&&dest){for(var i=0,len=this.components.length;i<len-1;i++){var component=this.components[i];component.transform(source,dest);}
this.bounds=null;}
return this;},getCentroid:function(){if(this.components&&(this.components.length>2)){var sumX=0.0;var sumY=0.0;for(var i=0;i<this.components.length-1;i++){var b=this.components[i];var c=this.components[i+1];sumX+=(b.x+c.x)*(b.x*c.y-c.x*b.y);sumY+=(b.y+c.y)*(b.x*c.y-c.x*b.y);}
var area=-1*this.getArea();var x=sumX/(6*area);var y=sumY/(6*area);}
return new OpenLayers.Geometry.Point(x,y);},getArea:function(){var area=0.0;if(this.components&&(this.components.length>2)){var sum=0.0;for(var i=0,len=this.components.length;i<len-1;i++){var b=this.components[i];var c=this.components[i+1];sum+=(b.x+c.x)*(c.y-b.y);}
area=-sum/2.0;}
return area;},getGeodesicArea:function(projection){var ring=this;if(projection){var gg=new OpenLayers.Projection("EPSG:4326");if(!gg.equals(projection)){ring=this.clone().transform(projection,gg);}}
var area=0.0;var len=ring.components&&ring.components.length;if(len>2){var p1,p2;for(var i=0;i<len-1;i++){p1=ring.components[i];p2=ring.components[i+1];area+=OpenLayers.Util.rad(p2.x-p1.x)*(2+Math.sin(OpenLayers.Util.rad(p1.y))+
Math.sin(OpenLayers.Util.rad(p2.y)));}
area=area*6378137.0*6378137.0/2.0;}
return area;},containsPoint:function(point){var approx=OpenLayers.Number.limitSigDigs;var digs=14;var px=approx(point.x,digs);var py=approx(point.y,digs);function getX(y,x1,y1,x2,y2){return(((x1-x2)*y)+((x2*y1)-(x1*y2)))/(y1-y2);}
var numSeg=this.components.length-1;var start,end,x1,y1,x2,y2,cx,cy;var crosses=0;for(var i=0;i<numSeg;++i){start=this.components[i];x1=approx(start.x,digs);y1=approx(start.y,digs);end=this.components[i+1];x2=approx(end.x,digs);y2=approx(end.y,digs);if(y1==y2){if(py==y1){if(x1<=x2&&(px>=x1&&px<=x2)||x1>=x2&&(px<=x1&&px>=x2)){crosses=-1;break;}}
continue;}
cx=approx(getX(py,x1,y1,x2,y2),digs);if(cx==px){if(y1<y2&&(py>=y1&&py<=y2)||y1>y2&&(py<=y1&&py>=y2)){crosses=-1;break;}}
if(cx<=px){continue;}
if(x1!=x2&&(cx<Math.min(x1,x2)||cx>Math.max(x1,x2))){continue;}
if(y1<y2&&(py>=y1&&py<y2)||y1>y2&&(py<y1&&py>=y2)){++crosses;}}
var contained=(crosses==-1)?1:!!(crosses&1);return contained;},intersects:function(geometry){var intersect=false;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){intersect=this.containsPoint(geometry);}else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LineString"){intersect=geometry.intersects(this);}else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LinearRing"){intersect=OpenLayers.Geometry.LineString.prototype.intersects.apply(this,[geometry]);}else{for(var i=0,len=geometry.components.length;i<len;++i){intersect=geometry.components[i].intersects(this);if(intersect){break;}}}
return intersect;},getVertices:function(nodes){return(nodes===true)?[]:this.components.slice(0,this.components.length-1);},CLASS_NAME:"OpenLayers.Geometry.LinearRing"});OpenLayers.Geometry.MultiLineString=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LineString"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},split:function(geometry,options){var results=null;var mutual=options&&options.mutual;var splits,sourceLine,sourceLines,sourceSplit,targetSplit;var sourceParts=[];var targetParts=[geometry];for(var i=0,len=this.components.length;i<len;++i){sourceLine=this.components[i];sourceSplit=false;for(var j=0;j<targetParts.length;++j){splits=sourceLine.split(targetParts[j],options);if(splits){if(mutual){sourceLines=splits[0];for(var k=0,klen=sourceLines.length;k<klen;++k){if(k===0&&sourceParts.length){sourceParts[sourceParts.length-1].addComponent(sourceLines[k]);}else{sourceParts.push(new OpenLayers.Geometry.MultiLineString([sourceLines[k]]));}}
sourceSplit=true;splits=splits[1];}
if(splits.length){splits.unshift(j,1);Array.prototype.splice.apply(targetParts,splits);break;}}}
if(!sourceSplit){if(sourceParts.length){sourceParts[sourceParts.length-1].addComponent(sourceLine.clone());}else{sourceParts=[new OpenLayers.Geometry.MultiLineString(sourceLine.clone())];}}}
if(sourceParts&&sourceParts.length>1){sourceSplit=true;}else{sourceParts=[];}
if(targetParts&&targetParts.length>1){targetSplit=true;}else{targetParts=[];}
if(sourceSplit||targetSplit){if(mutual){results=[sourceParts,targetParts];}else{results=targetParts;}}
return results;},splitWith:function(geometry,options){var results=null;var mutual=options&&options.mutual;var splits,targetLine,sourceLines,sourceSplit,targetSplit,sourceParts,targetParts;if(geometry instanceof OpenLayers.Geometry.LineString){targetParts=[];sourceParts=[geometry];for(var i=0,len=this.components.length;i<len;++i){targetSplit=false;targetLine=this.components[i];for(var j=0;j<sourceParts.length;++j){splits=sourceParts[j].split(targetLine,options);if(splits){if(mutual){sourceLines=splits[0];if(sourceLines.length){sourceLines.unshift(j,1);Array.prototype.splice.apply(sourceParts,sourceLines);j+=sourceLines.length-2;}
splits=splits[1];if(splits.length===0){splits=[targetLine.clone()];}}
for(var k=0,klen=splits.length;k<klen;++k){if(k===0&&targetParts.length){targetParts[targetParts.length-1].addComponent(splits[k]);}else{targetParts.push(new OpenLayers.Geometry.MultiLineString([splits[k]]));}}
targetSplit=true;}}
if(!targetSplit){if(targetParts.length){targetParts[targetParts.length-1].addComponent(targetLine.clone());}else{targetParts=[new OpenLayers.Geometry.MultiLineString([targetLine.clone()])];}}}}else{results=geometry.split(this);}
if(sourceParts&&sourceParts.length>1){sourceSplit=true;}else{sourceParts=[];}
if(targetParts&&targetParts.length>1){targetSplit=true;}else{targetParts=[];}
if(sourceSplit||targetSplit){if(mutual){results=[sourceParts,targetParts];}else{results=targetParts;}}
return results;},CLASS_NAME:"OpenLayers.Geometry.MultiLineString"});OpenLayers.Handler.Path=OpenLayers.Class(OpenLayers.Handler.Point,{line:null,freehand:false,freehandToggle:'shiftKey',initialize:function(control,callbacks,options){OpenLayers.Handler.Point.prototype.initialize.apply(this,arguments);},createFeature:function(pixel){var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([this.point.geometry]));this.callback("create",[this.point.geometry,this.getSketch()]);this.point.geometry.clearBounds();this.layer.addFeatures([this.line,this.point],{silent:true});},destroyFeature:function(){OpenLayers.Handler.Point.prototype.destroyFeature.apply(this);this.line=null;},removePoint:function(){if(this.point){this.layer.removeFeatures([this.point]);}},addPoint:function(pixel){this.layer.removeFeatures([this.point]);var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.line.geometry.addComponent(this.point.geometry,this.line.geometry.components.length);this.callback("point",[this.point.geometry,this.getGeometry()]);this.callback("modify",[this.point.geometry,this.getSketch()]);this.drawFeature();},freehandMode:function(evt){return(this.freehandToggle&&evt[this.freehandToggle])?!this.freehand:this.freehand;},modifyFeature:function(pixel){var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point.geometry.x=lonlat.lon;this.point.geometry.y=lonlat.lat;this.callback("modify",[this.point.geometry,this.getSketch()]);this.point.geometry.clearBounds();this.drawFeature();},drawFeature:function(){this.layer.drawFeature(this.line,this.style);this.layer.drawFeature(this.point,this.style);},getSketch:function(){return this.line;},getGeometry:function(){var geometry=this.line&&this.line.geometry;if(geometry&&this.multi){geometry=new OpenLayers.Geometry.MultiLineString([geometry]);}
return geometry;},mousedown:function(evt){if(this.lastDown&&this.lastDown.equals(evt.xy)){return false;}
if(this.lastDown==null){if(this.persist){this.destroyFeature();}
this.createFeature(evt.xy);}else if((this.lastUp==null)||!this.lastUp.equals(evt.xy)){this.addPoint(evt.xy);}
this.mouseDown=true;this.lastDown=evt.xy;this.drawing=true;return false;},mousemove:function(evt){if(this.drawing){if(this.mouseDown&&this.freehandMode(evt)){this.addPoint(evt.xy);}else{this.modifyFeature(evt.xy);}}
return true;},mouseup:function(evt){this.mouseDown=false;if(this.drawing){if(this.freehandMode(evt)){this.removePoint();this.finalize();}else{if(this.lastUp==null){this.addPoint(evt.xy);}
this.lastUp=evt.xy;}
return false;}
return true;},dblclick:function(evt){if(!this.freehandMode(evt)){var index=this.line.geometry.components.length-1;this.line.geometry.removeComponent(this.line.geometry.components[index]);this.removePoint();this.finalize();}
return false;},CLASS_NAME:"OpenLayers.Handler.Path"});OpenLayers.Control.Split=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:["beforesplit","split","aftersplit"],layer:null,source:null,sourceOptions:null,tolerance:null,edge:true,deferDelete:false,mutual:true,targetFilter:null,sourceFilter:null,handler:null,initialize:function(options){Array.prototype.push.apply(this.EVENT_TYPES,OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.options=options||{};if(this.options.source){this.setSource(this.options.source);}},setSource:function(layer){if(this.active){this.deactivate();if(this.handler){this.handler.destroy();delete this.handler;}
this.source=layer;this.activate();}else{this.source=layer;}},activate:function(){var activated=OpenLayers.Control.prototype.activate.call(this);if(activated){if(!this.source){if(!this.handler){this.handler=new OpenLayers.Handler.Path(this,{done:function(geometry){this.onSketchComplete({feature:new OpenLayers.Feature.Vector(geometry)});}},{layerOptions:this.sourceOptions});}
this.handler.activate();}else if(this.source.events){this.source.events.on({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});}}
return activated;},deactivate:function(){var deactivated=OpenLayers.Control.prototype.deactivate.call(this);if(deactivated){if(this.source&&this.source.events){this.layer.events.un({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});}}
return deactivated;},onSketchComplete:function(event){this.feature=null;return!this.considerSplit(event.feature);},afterFeatureModified:function(event){if(event.modified){var feature=event.feature;if(feature.geometry instanceof OpenLayers.Geometry.LineString||feature.geometry instanceof OpenLayers.Geometry.MultiLineString){this.feature=event.feature;this.considerSplit(event.feature);}}},removeByGeometry:function(features,geometry){for(var i=0,len=features.length;i<len;++i){if(features[i].geometry===geometry){features.splice(i,1);break;}}},isEligible:function(target){return(target.state!==OpenLayers.State.DELETE)&&(target.geometry instanceof OpenLayers.Geometry.LineString||target.geometry instanceof OpenLayers.Geometry.MultiLineString)&&(this.feature!==target)&&(!this.targetFilter||this.targetFilter.evaluate(target.attributes));},considerSplit:function(feature){sourceSplit=false;targetSplit=false;if(!this.sourceFilter||this.sourceFilter.evaluate(feature.attributes)){var features=this.layer&&this.layer.features||[];var target,results,result,proceed;var additions=[],removals=[];var mutual=(this.layer===this.source)&&this.mutual;var options={edge:this.edge,tolerance:this.tolerance,mutual:mutual};var sourceParts=[feature.geometry];var targetFeature,targetParts;var source,parts;for(var i=0,len=features.length;i<len;++i){targetFeature=features[i];if(this.isEligible(targetFeature)){targetParts=[targetFeature.geometry];for(var j=0;j<sourceParts.length;++j){source=sourceParts[j];for(var k=0;k<targetParts.length;++k){target=targetParts[k];if(source.getBounds().intersectsBounds(target.getBounds())){results=source.split(target,options);if(results){proceed=this.events.triggerEvent("beforesplit",{source:feature,target:targetFeature});if(proceed!==false){if(mutual){parts=results[0];if(parts.length>1){parts.unshift(j,1);Array.prototype.splice.apply(sourceParts,parts);j+=parts.length-3;}
results=results[1];}
if(results.length>1){results.unshift(k,1);Array.prototype.splice.apply(targetParts,results);k+=results.length-3;}}}}}}
if(targetParts&&targetParts.length>1){this.geomsToFeatures(targetFeature,targetParts);this.events.triggerEvent("split",{original:targetFeature,features:targetParts});Array.prototype.push.apply(additions,targetParts);removals.push(targetFeature);targetSplit=true;}}}
if(sourceParts&&sourceParts.length>1){this.geomsToFeatures(feature,sourceParts);this.events.triggerEvent("split",{original:feature,features:sourceParts});Array.prototype.push.apply(additions,sourceParts);removals.push(feature);sourceSplit=true;}
if(sourceSplit||targetSplit){if(this.deferDelete){var feat,destroys=[];for(var i=0,len=removals.length;i<len;++i){feat=removals[i];if(feat.state===OpenLayers.State.INSERT){destroys.push(feat);}else{feat.state=OpenLayers.State.DELETE;this.layer.drawFeature(feat);}}
this.layer.destroyFeatures(destroys,{silent:true});for(var i=0,len=additions.length;i<len;++i){additions[i].state=OpenLayers.State.INSERT;}}else{this.layer.destroyFeatures(removals,{silent:true});}
this.layer.addFeatures(additions,{silent:true});this.events.triggerEvent("aftersplit",{source:feature,features:additions});}}
return sourceSplit;},geomsToFeatures:function(feature,geoms){var clone=feature.clone();delete clone.geometry;var newFeature;for(var i=0,len=geoms.length;i<len;++i){newFeature=clone.clone();newFeature.geometry=geoms[i];newFeature.state=OpenLayers.State.INSERT;geoms[i]=newFeature;}},destroy:function(){if(this.active){this.deactivate();}
OpenLayers.Control.prototype.destroy.call(this);},CLASS_NAME:"OpenLayers.Control.Split"});OpenLayers.Geometry.Polygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LinearRing"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},getArea:function(){var area=0.0;if(this.components&&(this.components.length>0)){area+=Math.abs(this.components[0].getArea());for(var i=1,len=this.components.length;i<len;i++){area-=Math.abs(this.components[i].getArea());}}
return area;},getGeodesicArea:function(projection){var area=0.0;if(this.components&&(this.components.length>0)){area+=Math.abs(this.components[0].getGeodesicArea(projection));for(var i=1,len=this.components.length;i<len;i++){area-=Math.abs(this.components[i].getGeodesicArea(projection));}}
return area;},containsPoint:function(point){var numRings=this.components.length;var contained=false;if(numRings>0){contained=this.components[0].containsPoint(point);if(contained!==1){if(contained&&numRings>1){var hole;for(var i=1;i<numRings;++i){hole=this.components[i].containsPoint(point);if(hole){if(hole===1){contained=1;}else{contained=false;}
break;}}}}}
return contained;},intersects:function(geometry){var intersect=false;var i,len;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){intersect=this.containsPoint(geometry);}else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LineString"||geometry.CLASS_NAME=="OpenLayers.Geometry.LinearRing"){for(i=0,len=this.components.length;i<len;++i){intersect=geometry.intersects(this.components[i]);if(intersect){break;}}
if(!intersect){for(i=0,len=geometry.components.length;i<len;++i){intersect=this.containsPoint(geometry.components[i]);if(intersect){break;}}}}else{for(i=0,len=geometry.components.length;i<len;++i){intersect=this.intersects(geometry.components[i]);if(intersect){break;}}}
if(!intersect&&geometry.CLASS_NAME=="OpenLayers.Geometry.Polygon"){var ring=this.components[0];for(i=0,len=ring.components.length;i<len;++i){intersect=geometry.containsPoint(ring.components[i]);if(intersect){break;}}}
return intersect;},distanceTo:function(geometry,options){var edge=!(options&&options.edge===false);var result;if(!edge&&this.intersects(geometry)){result=0;}else{result=OpenLayers.Geometry.Collection.prototype.distanceTo.apply(this,[geometry,options]);}
return result;},CLASS_NAME:"OpenLayers.Geometry.Polygon"});OpenLayers.Geometry.Polygon.createRegularPolygon=function(origin,radius,sides,rotation){var angle=Math.PI*((1/sides)-(1/2));if(rotation){angle+=(rotation/180)*Math.PI;}
var rotatedAngle,x,y;var points=[];for(var i=0;i<sides;++i){rotatedAngle=angle+(i*2*Math.PI/sides);x=origin.x+(radius*Math.cos(rotatedAngle));y=origin.y+(radius*Math.sin(rotatedAngle));points.push(new OpenLayers.Geometry.Point(x,y));}
var ring=new OpenLayers.Geometry.LinearRing(points);return new OpenLayers.Geometry.Polygon([ring]);};OpenLayers.Format.GeoRSS=OpenLayers.Class(OpenLayers.Format.XML,{rssns:"http://backend.userland.com/rss2",featureNS:"http://mapserver.gis.umn.edu/mapserver",georssns:"http://www.georss.org/georss",geons:"http://www.w3.org/2003/01/geo/wgs84_pos#",featureTitle:"Untitled",featureDescription:"No Description",gmlParser:null,xy:false,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},createGeometryFromItem:function(item){var point=this.getElementsByTagNameNS(item,this.georssns,"point");var lat=this.getElementsByTagNameNS(item,this.geons,'lat');var lon=this.getElementsByTagNameNS(item,this.geons,'long');var line=this.getElementsByTagNameNS(item,this.georssns,"line");var polygon=this.getElementsByTagNameNS(item,this.georssns,"polygon");var where=this.getElementsByTagNameNS(item,this.georssns,"where");var box=this.getElementsByTagNameNS(item,this.georssns,"box");if(point.length>0||(lat.length>0&&lon.length>0)){var location;if(point.length>0){location=OpenLayers.String.trim(point[0].firstChild.nodeValue).split(/\s+/);if(location.length!=2){location=OpenLayers.String.trim(point[0].firstChild.nodeValue).split(/\s*,\s*/);}}else{location=[parseFloat(lat[0].firstChild.nodeValue),parseFloat(lon[0].firstChild.nodeValue)];}
var geometry=new OpenLayers.Geometry.Point(parseFloat(location[1]),parseFloat(location[0]));}else if(line.length>0){var coords=OpenLayers.String.trim(this.concatChildValues(line[0])).split(/\s+/);var components=[];var point;for(var i=0,len=coords.length;i<len;i+=2){point=new OpenLayers.Geometry.Point(parseFloat(coords[i+1]),parseFloat(coords[i]));components.push(point);}
geometry=new OpenLayers.Geometry.LineString(components);}else if(polygon.length>0){var coords=OpenLayers.String.trim(this.concatChildValues(polygon[0])).split(/\s+/);var components=[];var point;for(var i=0,len=coords.length;i<len;i+=2){point=new OpenLayers.Geometry.Point(parseFloat(coords[i+1]),parseFloat(coords[i]));components.push(point);}
geometry=new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);}else if(where.length>0){if(!this.gmlParser){this.gmlParser=new OpenLayers.Format.GML({'xy':this.xy});}
var feature=this.gmlParser.parseFeature(where[0]);geometry=feature.geometry;}else if(box.length>0){var coords=OpenLayers.String.trim(box[0].firstChild.nodeValue).split(/\s+/);var components=[];var point;if(coords.length>3){point=new OpenLayers.Geometry.Point(parseFloat(coords[1]),parseFloat(coords[0]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[1]),parseFloat(coords[2]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[3]),parseFloat(coords[2]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[3]),parseFloat(coords[0]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[1]),parseFloat(coords[0]));components.push(point);}
geometry=new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);}
if(geometry&&this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}
return geometry;},createFeatureFromItem:function(item){var geometry=this.createGeometryFromItem(item);var title=this.getChildValue(item,"*","title",this.featureTitle);var description=this.getChildValue(item,"*","description",this.getChildValue(item,"*","content",this.getChildValue(item,"*","summary",this.featureDescription)));var link=this.getChildValue(item,"*","link");if(!link){try{link=this.getElementsByTagNameNS(item,"*","link")[0].getAttribute("href");}catch(e){link=null;}}
var id=this.getChildValue(item,"*","id",null);var data={"title":title,"description":description,"link":link};var feature=new OpenLayers.Feature.Vector(geometry,data);feature.fid=id;return feature;},getChildValue:function(node,nsuri,name,def){var value;var eles=this.getElementsByTagNameNS(node,nsuri,name);if(eles&&eles[0]&&eles[0].firstChild&&eles[0].firstChild.nodeValue){value=eles[0].firstChild.nodeValue;}else{value=(def==undefined)?"":def;}
return value;},read:function(doc){if(typeof doc=="string"){doc=OpenLayers.Format.XML.prototype.read.apply(this,[doc]);}
var itemlist=null;itemlist=this.getElementsByTagNameNS(doc,'*','item');if(itemlist.length==0){itemlist=this.getElementsByTagNameNS(doc,'*','entry');}
var numItems=itemlist.length;var features=new Array(numItems);for(var i=0;i<numItems;i++){features[i]=this.createFeatureFromItem(itemlist[i]);}
return features;},write:function(features){var georss;if(features instanceof Array){georss=this.createElementNS(this.rssns,"rss");for(var i=0,len=features.length;i<len;i++){georss.appendChild(this.createFeatureXML(features[i]));}}else{georss=this.createFeatureXML(features);}
return OpenLayers.Format.XML.prototype.write.apply(this,[georss]);},createFeatureXML:function(feature){var geometryNode=this.buildGeometryNode(feature.geometry);var featureNode=this.createElementNS(this.rssns,"item");var titleNode=this.createElementNS(this.rssns,"title");titleNode.appendChild(this.createTextNode(feature.attributes.title?feature.attributes.title:""));var descNode=this.createElementNS(this.rssns,"description");descNode.appendChild(this.createTextNode(feature.attributes.description?feature.attributes.description:""));featureNode.appendChild(titleNode);featureNode.appendChild(descNode);if(feature.attributes.link){var linkNode=this.createElementNS(this.rssns,"link");linkNode.appendChild(this.createTextNode(feature.attributes.link));featureNode.appendChild(linkNode);}
for(var attr in feature.attributes){if(attr=="link"||attr=="title"||attr=="description"){continue;}
var attrText=this.createTextNode(feature.attributes[attr]);var nodename=attr;if(attr.search(":")!=-1){nodename=attr.split(":")[1];}
var attrContainer=this.createElementNS(this.featureNS,"feature:"+nodename);attrContainer.appendChild(attrText);featureNode.appendChild(attrContainer);}
featureNode.appendChild(geometryNode);return featureNode;},buildGeometryNode:function(geometry){if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
var node;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Polygon"){node=this.createElementNS(this.georssns,'georss:polygon');node.appendChild(this.buildCoordinatesNode(geometry.components[0]));}
else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LineString"){node=this.createElementNS(this.georssns,'georss:line');node.appendChild(this.buildCoordinatesNode(geometry));}
else if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){node=this.createElementNS(this.georssns,'georss:point');node.appendChild(this.buildCoordinatesNode(geometry));}else{throw"Couldn't parse "+geometry.CLASS_NAME;}
return node;},buildCoordinatesNode:function(geometry){var points=null;if(geometry.components){points=geometry.components;}
var path;if(points){var numPoints=points.length;var parts=new Array(numPoints);for(var i=0;i<numPoints;i++){parts[i]=points[i].y+" "+points[i].x;}
path=parts.join(" ");}else{path=geometry.y+" "+geometry.x;}
return this.createTextNode(path);},CLASS_NAME:"OpenLayers.Format.GeoRSS"});OpenLayers.Format.KML=OpenLayers.Class(OpenLayers.Format.XML,{kmlns:"http://earth.google.com/kml/2.0",placemarksDesc:"No description available",foldersName:"OpenLayers export",foldersDesc:"Exported on "+new Date(),extractAttributes:true,extractStyles:false,internalns:null,features:null,styles:null,styleBaseUrl:"",fetched:null,maxDepth:0,initialize:function(options){this.regExes={trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g),kmlColor:(/(\w{2})(\w{2})(\w{2})(\w{2})/),kmlIconPalette:(/root:\/\/icons\/palette-(\d+)(\.\w+)/),straightBracket:(/\$\[(.*?)\]/g)};OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){this.features=[];this.styles={};this.fetched={};var options={depth:0,styleBaseUrl:this.styleBaseUrl};return this.parseData(data,options);},parseData:function(data,options){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var types=["Link","NetworkLink","Style","StyleMap","Placemark"];for(var i=0,len=types.length;i<len;++i){var type=types[i];var nodes=this.getElementsByTagNameNS(data,"*",type);if(nodes.length==0){continue;}
switch(type.toLowerCase()){case"link":case"networklink":this.parseLinks(nodes,options);break;case"style":if(this.extractStyles){this.parseStyles(nodes,options);}
break;case"stylemap":if(this.extractStyles){this.parseStyleMaps(nodes,options);}
break;case"placemark":this.parseFeatures(nodes,options);break;}}
return this.features;},parseLinks:function(nodes,options){if(options.depth>=this.maxDepth){return false;}
var newOptions=OpenLayers.Util.extend({},options);newOptions.depth++;for(var i=0,len=nodes.length;i<len;i++){var href=this.parseProperty(nodes[i],"*","href");if(href&&!this.fetched[href]){this.fetched[href]=true;var data=this.fetchLink(href);if(data){this.parseData(data,newOptions);}}}},fetchLink:function(href){var request=OpenLayers.Request.GET({url:href,async:false});if(request){return request.responseText;}},parseStyles:function(nodes,options){for(var i=0,len=nodes.length;i<len;i++){var style=this.parseStyle(nodes[i]);if(style){styleName=(options.styleBaseUrl||"")+"#"+style.id;this.styles[styleName]=style;}}},parseStyle:function(node){var style={};var types=["LineStyle","PolyStyle","IconStyle","BalloonStyle"];var type,nodeList,geometry,parser;for(var i=0,len=types.length;i<len;++i){type=types[i];styleTypeNode=this.getElementsByTagNameNS(node,"*",type)[0];if(!styleTypeNode){continue;}
switch(type.toLowerCase()){case"linestyle":var color=this.parseProperty(styleTypeNode,"*","color");if(color){var matches=(color.toString()).match(this.regExes.kmlColor);var alpha=matches[1];style["strokeOpacity"]=parseInt(alpha,16)/255;var b=matches[2];var g=matches[3];var r=matches[4];style["strokeColor"]="#"+r+g+b;}
var width=this.parseProperty(styleTypeNode,"*","width");if(width){style["strokeWidth"]=width;}
case"polystyle":var color=this.parseProperty(styleTypeNode,"*","color");if(color){var matches=(color.toString()).match(this.regExes.kmlColor);var alpha=matches[1];style["fillOpacity"]=parseInt(alpha,16)/255;var b=matches[2];var g=matches[3];var r=matches[4];style["fillColor"]="#"+r+g+b;}
var fill=this.parseProperty(styleTypeNode,"*","fill");if(fill=="0"){style["fillColor"]="none";}
break;case"iconstyle":var scale=parseFloat(this.parseProperty(styleTypeNode,"*","scale")||1);var width=32*scale;var height=32*scale;var iconNode=this.getElementsByTagNameNS(styleTypeNode,"*","Icon")[0];if(iconNode){var href=this.parseProperty(iconNode,"*","href");if(href){var w=this.parseProperty(iconNode,"*","w");var h=this.parseProperty(iconNode,"*","h");var google="http://maps.google.com/mapfiles/kml";if(OpenLayers.String.startsWith(href,google)&&!w&&!h){w=64;h=64;scale=scale/2;}
w=w||h;h=h||w;if(w){width=parseInt(w)*scale;}
if(h){height=parseInt(h)*scale;}
var matches=href.match(this.regExes.kmlIconPalette);if(matches){var palette=matches[1];var file_extension=matches[2];var x=this.parseProperty(iconNode,"*","x");var y=this.parseProperty(iconNode,"*","y");var posX=x?x/32:0;var posY=y?(7-y/32):7;var pos=posY*8+posX;href="http://maps.google.com/mapfiles/kml/pal"
+palette+"/icon"+pos+file_extension;}
style["graphicOpacity"]=1;style["externalGraphic"]=href;}}
var hotSpotNode=this.getElementsByTagNameNS(styleTypeNode,"*","hotSpot")[0];if(hotSpotNode){var x=parseFloat(hotSpotNode.getAttribute("x"));var y=parseFloat(hotSpotNode.getAttribute("y"));var xUnits=hotSpotNode.getAttribute("xunits");if(xUnits=="pixels"){style["graphicXOffset"]=-x*scale;}
else if(xUnits=="insetPixels"){style["graphicXOffset"]=-width+(x*scale);}
else if(xUnits=="fraction"){style["graphicXOffset"]=-width*x;}
var yUnits=hotSpotNode.getAttribute("yunits");if(yUnits=="pixels"){style["graphicYOffset"]=-height+(y*scale)+1;}
else if(yUnits=="insetPixels"){style["graphicYOffset"]=-(y*scale)+1;}
else if(yUnits=="fraction"){style["graphicYOffset"]=-height*(1-y)+1;}}
style["graphicWidth"]=width;style["graphicHeight"]=height;break;case"balloonstyle":var balloonStyle=OpenLayers.Util.getXmlNodeValue(styleTypeNode);if(balloonStyle){style["balloonStyle"]=balloonStyle.replace(this.regExes.straightBracket,"${$1}");}
break;default:}}
if(!style["strokeColor"]&&style["fillColor"]){style["strokeColor"]=style["fillColor"];}
var id=node.getAttribute("id");if(id&&style){style.id=id;}
return style;},parseStyleMaps:function(nodes,options){for(var i=0,len=nodes.length;i<len;i++){var node=nodes[i];var pairs=this.getElementsByTagNameNS(node,"*","Pair");var id=node.getAttribute("id");for(var j=0,jlen=pairs.length;j<jlen;j++){var pair=pairs[j];var key=this.parseProperty(pair,"*","key");var styleUrl=this.parseProperty(pair,"*","styleUrl");if(styleUrl&&key=="normal"){this.styles[(options.styleBaseUrl||"")+"#"+id]=this.styles[(options.styleBaseUrl||"")+styleUrl];}
if(styleUrl&&key=="highlight"){}}}},parseFeatures:function(nodes,options){var features=new Array(nodes.length);for(var i=0,len=nodes.length;i<len;i++){var featureNode=nodes[i];var feature=this.parseFeature.apply(this,[featureNode]);if(feature){if(this.extractStyles&&feature.attributes&&feature.attributes.styleUrl){feature.style=this.getStyle(feature.attributes.styleUrl,options);}
if(this.extractStyles){var inlineStyleNode=this.getElementsByTagNameNS(featureNode,"*","Style")[0];if(inlineStyleNode){var inlineStyle=this.parseStyle(inlineStyleNode);if(inlineStyle){feature.style=OpenLayers.Util.extend(feature.style,inlineStyle);}}}
features[i]=feature;}else{throw"Bad Placemark: "+i;}}
this.features=this.features.concat(features);},parseFeature:function(node){var order=["MultiGeometry","Polygon","LineString","Point"];var type,nodeList,geometry,parser;for(var i=0,len=order.length;i<len;++i){type=order[i];this.internalns=node.namespaceURI?node.namespaceURI:this.kmlns;nodeList=this.getElementsByTagNameNS(node,this.internalns,type);if(nodeList.length>0){var parser=this.parseGeometry[type.toLowerCase()];if(parser){geometry=parser.apply(this,[nodeList[0]]);if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}}else{OpenLayers.Console.error(OpenLayers.i18n("unsupportedGeometryType",{'geomType':type}));}
break;}}
var attributes;if(this.extractAttributes){attributes=this.parseAttributes(node);}
var feature=new OpenLayers.Feature.Vector(geometry,attributes);var fid=node.getAttribute("id")||node.getAttribute("name");if(fid!=null){feature.fid=fid;}
return feature;},getStyle:function(styleUrl,options){var styleBaseUrl=OpenLayers.Util.removeTail(styleUrl);var newOptions=OpenLayers.Util.extend({},options);newOptions.depth++;newOptions.styleBaseUrl=styleBaseUrl;if(!this.styles[styleUrl]&&!OpenLayers.String.startsWith(styleUrl,"#")&&newOptions.depth<=this.maxDepth&&!this.fetched[styleBaseUrl]){var data=this.fetchLink(styleBaseUrl);if(data){this.parseData(data,newOptions);}}
var style=OpenLayers.Util.extend({},this.styles[styleUrl]);return style;},parseGeometry:{point:function(node){var nodeList=this.getElementsByTagNameNS(node,this.internalns,"coordinates");var coords=[];if(nodeList.length>0){var coordString=nodeList[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.removeSpace,"");coords=coordString.split(",");}
var point=null;if(coords.length>1){if(coords.length==2){coords[2]=null;}
point=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{throw"Bad coordinate string: "+coordString;}
return point;},linestring:function(node,ring){var nodeList=this.getElementsByTagNameNS(node,this.internalns,"coordinates");var line=null;if(nodeList.length>0){var coordString=this.getChildValue(nodeList[0]);coordString=coordString.replace(this.regExes.trimSpace,"");coordString=coordString.replace(this.regExes.trimComma,",");var pointList=coordString.split(this.regExes.splitSpace);var numPoints=pointList.length;var points=new Array(numPoints);var coords,numCoords;for(var i=0;i<numPoints;++i){coords=pointList[i].split(",");numCoords=coords.length;if(numCoords>1){if(coords.length==2){coords[2]=null;}
points[i]=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{throw"Bad LineString point coordinates: "+
pointList[i];}}
if(numPoints){if(ring){line=new OpenLayers.Geometry.LinearRing(points);}else{line=new OpenLayers.Geometry.LineString(points);}}else{throw"Bad LineString coordinates: "+coordString;}}
return line;},polygon:function(node){var nodeList=this.getElementsByTagNameNS(node,this.internalns,"LinearRing");var numRings=nodeList.length;var components=new Array(numRings);if(numRings>0){var ring;for(var i=0,len=nodeList.length;i<len;++i){ring=this.parseGeometry.linestring.apply(this,[nodeList[i],true]);if(ring){components[i]=ring;}else{throw"Bad LinearRing geometry: "+i;}}}
return new OpenLayers.Geometry.Polygon(components);},multigeometry:function(node){var child,parser;var parts=[];var children=node.childNodes;for(var i=0,len=children.length;i<len;++i){child=children[i];if(child.nodeType==1){var type=(child.prefix)?child.nodeName.split(":")[1]:child.nodeName;var parser=this.parseGeometry[type.toLowerCase()];if(parser){parts.push(parser.apply(this,[child]));}}}
return new OpenLayers.Geometry.Collection(parts);}},parseAttributes:function(node){var attributes={};var edNodes=node.getElementsByTagName("ExtendedData");if(edNodes.length){attributes=this.parseExtendedData(edNodes[0]);}
var child,grandchildren,grandchild;var children=node.childNodes;for(var i=0,len=children.length;i<len;++i){child=children[i];if(child.nodeType==1){grandchildren=child.childNodes;if(grandchildren.length==1||grandchildren.length==3){var grandchild;switch(grandchildren.length){case 1:grandchild=grandchildren[0];break;case 3:default:grandchild=grandchildren[1];break;}
if(grandchild.nodeType==3||grandchild.nodeType==4){var name=(child.prefix)?child.nodeName.split(":")[1]:child.nodeName;var value=OpenLayers.Util.getXmlNodeValue(grandchild);if(value){value=value.replace(this.regExes.trimSpace,"");attributes[name]=value;}}}}}
return attributes;},parseExtendedData:function(node){var attributes={};var dataNodes=node.getElementsByTagName("Data");for(var i=0,len=dataNodes.length;i<len;i++){var data=dataNodes[i];var key=data.getAttribute("name");var ed={};var valueNode=data.getElementsByTagName("value");if(valueNode.length){ed['value']=this.getChildValue(valueNode[0]);}
var nameNode=data.getElementsByTagName("displayName");if(nameNode.length){ed['displayName']=this.getChildValue(nameNode[0]);}
attributes[key]=ed;}
return attributes;},parseProperty:function(xmlNode,namespace,tagName){var value;var nodeList=this.getElementsByTagNameNS(xmlNode,namespace,tagName);try{value=OpenLayers.Util.getXmlNodeValue(nodeList[0]);}catch(e){value=null;}
return value;},write:function(features){if(!(features instanceof Array)){features=[features];}
var kml=this.createElementNS(this.kmlns,"kml");var folder=this.createFolderXML();for(var i=0,len=features.length;i<len;++i){folder.appendChild(this.createPlacemarkXML(features[i]));}
kml.appendChild(folder);return OpenLayers.Format.XML.prototype.write.apply(this,[kml]);},createFolderXML:function(){var folderName=this.createElementNS(this.kmlns,"name");var folderNameText=this.createTextNode(this.foldersName);folderName.appendChild(folderNameText);var folderDesc=this.createElementNS(this.kmlns,"description");var folderDescText=this.createTextNode(this.foldersDesc);folderDesc.appendChild(folderDescText);var folder=this.createElementNS(this.kmlns,"Folder");folder.appendChild(folderName);folder.appendChild(folderDesc);return folder;},createPlacemarkXML:function(feature){var placemarkName=this.createElementNS(this.kmlns,"name");var name=(feature.attributes.name)?feature.attributes.name:feature.id;placemarkName.appendChild(this.createTextNode(name));var placemarkDesc=this.createElementNS(this.kmlns,"description");var desc=(feature.attributes.description)?feature.attributes.description:this.placemarksDesc;placemarkDesc.appendChild(this.createTextNode(desc));var placemarkNode=this.createElementNS(this.kmlns,"Placemark");if(feature.fid!=null){placemarkNode.setAttribute("id",feature.fid);}
placemarkNode.appendChild(placemarkName);placemarkNode.appendChild(placemarkDesc);var geometryNode=this.buildGeometryNode(feature.geometry);placemarkNode.appendChild(geometryNode);return placemarkNode;},buildGeometryNode:function(geometry){if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
var className=geometry.CLASS_NAME;var type=className.substring(className.lastIndexOf(".")+1);var builder=this.buildGeometry[type.toLowerCase()];var node=null;if(builder){node=builder.apply(this,[geometry]);}
return node;},buildGeometry:{point:function(geometry){var kml=this.createElementNS(this.kmlns,"Point");kml.appendChild(this.buildCoordinatesNode(geometry));return kml;},multipoint:function(geometry){return this.buildGeometry.collection.apply(this,[geometry]);},linestring:function(geometry){var kml=this.createElementNS(this.kmlns,"LineString");kml.appendChild(this.buildCoordinatesNode(geometry));return kml;},multilinestring:function(geometry){return this.buildGeometry.collection.apply(this,[geometry]);},linearring:function(geometry){var kml=this.createElementNS(this.kmlns,"LinearRing");kml.appendChild(this.buildCoordinatesNode(geometry));return kml;},polygon:function(geometry){var kml=this.createElementNS(this.kmlns,"Polygon");var rings=geometry.components;var ringMember,ringGeom,type;for(var i=0,len=rings.length;i<len;++i){type=(i==0)?"outerBoundaryIs":"innerBoundaryIs";ringMember=this.createElementNS(this.kmlns,type);ringGeom=this.buildGeometry.linearring.apply(this,[rings[i]]);ringMember.appendChild(ringGeom);kml.appendChild(ringMember);}
return kml;},multipolygon:function(geometry){return this.buildGeometry.collection.apply(this,[geometry]);},collection:function(geometry){var kml=this.createElementNS(this.kmlns,"MultiGeometry");var child;for(var i=0,len=geometry.components.length;i<len;++i){child=this.buildGeometryNode.apply(this,[geometry.components[i]]);if(child){kml.appendChild(child);}}
return kml;}},buildCoordinatesNode:function(geometry){var coordinatesNode=this.createElementNS(this.kmlns,"coordinates");var path;var points=geometry.components;if(points){var point;var numPoints=points.length;var parts=new Array(numPoints);for(var i=0;i<numPoints;++i){point=points[i];parts[i]=point.x+","+point.y;}
path=parts.join(" ");}else{path=geometry.x+","+geometry.y;}
var txtNode=this.createTextNode(path);coordinatesNode.appendChild(txtNode);return coordinatesNode;},CLASS_NAME:"OpenLayers.Format.KML"});OpenLayers.Format.OSM=OpenLayers.Class(OpenLayers.Format.XML,{checkTags:false,interestingTagsExclude:null,areaTags:null,initialize:function(options){var layer_defaults={'interestingTagsExclude':['source','source_ref','source:ref','history','attribution','created_by'],'areaTags':['area','building','leisure','tourism','ruins','historic','landuse','military','natural','sport']};layer_defaults=OpenLayers.Util.extend(layer_defaults,options);var interesting={};for(var i=0;i<layer_defaults.interestingTagsExclude.length;i++){interesting[layer_defaults.interestingTagsExclude[i]]=true;}
layer_defaults.interestingTagsExclude=interesting;var area={};for(var i=0;i<layer_defaults.areaTags.length;i++){area[layer_defaults.areaTags[i]]=true;}
layer_defaults.areaTags=area;OpenLayers.Format.XML.prototype.initialize.apply(this,[layer_defaults]);},read:function(doc){if(typeof doc=="string"){doc=OpenLayers.Format.XML.prototype.read.apply(this,[doc]);}
var nodes=this.getNodes(doc);var ways=this.getWays(doc);var feat_list=new Array(ways.length);for(var i=0;i<ways.length;i++){var point_list=new Array(ways[i].nodes.length);var poly=this.isWayArea(ways[i])?1:0;for(var j=0;j<ways[i].nodes.length;j++){var node=nodes[ways[i].nodes[j]];var point=new OpenLayers.Geometry.Point(node.lon,node.lat);point.osm_id=parseInt(ways[i].nodes[j]);point_list[j]=point;node.used=true;}
var geometry=null;if(poly){geometry=new OpenLayers.Geometry.Polygon(new OpenLayers.Geometry.LinearRing(point_list));}else{geometry=new OpenLayers.Geometry.LineString(point_list);}
if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}
var feat=new OpenLayers.Feature.Vector(geometry,ways[i].tags);feat.osm_id=parseInt(ways[i].id);feat.fid="way."+feat.osm_id;feat_list[i]=feat;}
for(var node_id in nodes){var node=nodes[node_id];if(!node.used||this.checkTags){var tags=null;if(this.checkTags){var result=this.getTags(node.node,true);if(node.used&&!result[1]){continue;}
tags=result[0];}else{tags=this.getTags(node.node);}
var feat=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(node['lon'],node['lat']),tags);if(this.internalProjection&&this.externalProjection){feat.geometry.transform(this.externalProjection,this.internalProjection);}
feat.osm_id=parseInt(node_id);feat.fid="node."+feat.osm_id;feat_list.push(feat);}
node.node=null;}
return feat_list;},getNodes:function(doc){var node_list=doc.getElementsByTagName("node");var nodes={};for(var i=0;i<node_list.length;i++){var node=node_list[i];var id=node.getAttribute("id");nodes[id]={'lat':node.getAttribute("lat"),'lon':node.getAttribute("lon"),'node':node};}
return nodes;},getWays:function(doc){var way_list=doc.getElementsByTagName("way");var return_ways=[];for(var i=0;i<way_list.length;i++){var way=way_list[i];var way_object={id:way.getAttribute("id")};way_object.tags=this.getTags(way);var node_list=way.getElementsByTagName("nd");way_object.nodes=new Array(node_list.length);for(var j=0;j<node_list.length;j++){way_object.nodes[j]=node_list[j].getAttribute("ref");}
return_ways.push(way_object);}
return return_ways;},getTags:function(dom_node,interesting_tags){var tag_list=dom_node.getElementsByTagName("tag");var tags={};var interesting=false;for(var j=0;j<tag_list.length;j++){var key=tag_list[j].getAttribute("k");tags[key]=tag_list[j].getAttribute("v");if(interesting_tags){if(!this.interestingTagsExclude[key]){interesting=true;}}}
return interesting_tags?[tags,interesting]:tags;},isWayArea:function(way){var poly_shaped=false;var poly_tags=false;if(way.nodes[0]==way.nodes[way.nodes.length-1]){poly_shaped=true;}
if(this.checkTags){for(var key in way.tags){if(this.areaTags[key]){poly_tags=true;break;}}}
return poly_shaped&&(this.checkTags?poly_tags:true);},write:function(features){if(!(features instanceof Array)){features=[features];}
this.osm_id=1;this.created_nodes={};var root_node=this.createElementNS(null,"osm");root_node.setAttribute("version","0.5");root_node.setAttribute("generator","OpenLayers "+OpenLayers.VERSION_NUMBER);for(var i=features.length-1;i>=0;i--){var nodes=this.createFeatureNodes(features[i]);for(var j=0;j<nodes.length;j++){root_node.appendChild(nodes[j]);}}
return OpenLayers.Format.XML.prototype.write.apply(this,[root_node]);},createFeatureNodes:function(feature){var nodes=[];var className=feature.geometry.CLASS_NAME;var type=className.substring(className.lastIndexOf(".")+1);type=type.toLowerCase();var builder=this.createXML[type];if(builder){nodes=builder.apply(this,[feature]);}
return nodes;},createXML:{'point':function(point){var id=null;var geometry=point.geometry?point.geometry:point;var already_exists=false;if(point.osm_id){id=point.osm_id;if(this.created_nodes[id]){already_exists=true;}}else{id=-this.osm_id;this.osm_id++;}
if(already_exists){node=this.created_nodes[id];}else{var node=this.createElementNS(null,"node");}
this.created_nodes[id]=node;node.setAttribute("id",id);node.setAttribute("lon",geometry.x);node.setAttribute("lat",geometry.y);if(point.attributes){this.serializeTags(point,node);}
this.setState(point,node);return already_exists?[]:[node];},linestring:function(feature){var nodes=[];var geometry=feature.geometry;if(feature.osm_id){id=feature.osm_id;}else{id=-this.osm_id;this.osm_id++;}
var way=this.createElementNS(null,"way");way.setAttribute("id",id);for(var i=0;i<geometry.components.length;i++){var node=this.createXML['point'].apply(this,[geometry.components[i]]);if(node.length){node=node[0];var node_ref=node.getAttribute("id");nodes.push(node);}else{node_ref=geometry.components[i].osm_id;node=this.created_nodes[node_ref];}
this.setState(feature,node);var nd_dom=this.createElementNS(null,"nd");nd_dom.setAttribute("ref",node_ref);way.appendChild(nd_dom);}
this.serializeTags(feature,way);nodes.push(way);return nodes;},polygon:function(feature){var attrs=OpenLayers.Util.extend({'area':'yes'},feature.attributes);var feat=new OpenLayers.Feature.Vector(feature.geometry.components[0],attrs);feat.osm_id=feature.osm_id;return this.createXML['linestring'].apply(this,[feat]);}},serializeTags:function(feature,node){for(var key in feature.attributes){var tag=this.createElementNS(null,"tag");tag.setAttribute("k",key);tag.setAttribute("v",feature.attributes[key]);node.appendChild(tag);}},setState:function(feature,node){if(feature.state){var state=null;switch(feature.state){case OpenLayers.State.UPDATE:state="modify";case OpenLayers.State.DELETE:state="delete";}
if(state){node.setAttribute("action",state);}}},CLASS_NAME:"OpenLayers.Format.OSM"});OpenLayers.Geometry.MultiPolygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Polygon"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},CLASS_NAME:"OpenLayers.Geometry.MultiPolygon"});OpenLayers.Handler.Polygon=OpenLayers.Class(OpenLayers.Handler.Path,{polygon:null,initialize:function(control,callbacks,options){OpenLayers.Handler.Path.prototype.initialize.apply(this,arguments);},createFeature:function(pixel){var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LinearRing([this.point.geometry]));this.polygon=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([this.line.geometry]));this.callback("create",[this.point.geometry,this.getSketch()]);this.point.geometry.clearBounds();this.layer.addFeatures([this.polygon,this.point],{silent:true});},destroyFeature:function(){OpenLayers.Handler.Path.prototype.destroyFeature.apply(this);this.polygon=null;},drawFeature:function(){this.layer.drawFeature(this.polygon,this.style);this.layer.drawFeature(this.point,this.style);},getSketch:function(){return this.polygon;},getGeometry:function(){var geometry=this.polygon&&this.polygon.geometry;if(geometry&&this.multi){geometry=new OpenLayers.Geometry.MultiPolygon([geometry]);}
return geometry;},dblclick:function(evt){if(!this.freehandMode(evt)){var index=this.line.geometry.components.length-2;this.line.geometry.removeComponent(this.line.geometry.components[index]);this.removePoint();this.finalize();}
return false;},CLASS_NAME:"OpenLayers.Handler.Polygon"});OpenLayers.Control.EditingToolbar=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(layer,options){OpenLayers.Control.Panel.prototype.initialize.apply(this,[options]);this.addControls([new OpenLayers.Control.Navigation()]);var controls=[new OpenLayers.Control.DrawFeature(layer,OpenLayers.Handler.Point,{'displayClass':'olControlDrawFeaturePoint'}),new OpenLayers.Control.DrawFeature(layer,OpenLayers.Handler.Path,{'displayClass':'olControlDrawFeaturePath'}),new OpenLayers.Control.DrawFeature(layer,OpenLayers.Handler.Polygon,{'displayClass':'olControlDrawFeaturePolygon'})];this.addControls(controls);},draw:function(){var div=OpenLayers.Control.Panel.prototype.draw.apply(this,arguments);this.activateControl(this.controls[0]);return div;},CLASS_NAME:"OpenLayers.Control.EditingToolbar"});OpenLayers.Format.ArcXML=OpenLayers.Class(OpenLayers.Format.XML,{fontStyleKeys:['antialiasing','blockout','font','fontcolor','fontsize','fontstyle','glowing','interval','outline','printmode','shadow','transparency'],request:null,response:null,initialize:function(options){this.request=new OpenLayers.Format.ArcXML.Request();this.response=new OpenLayers.Format.ArcXML.Response();if(options){if(options.requesttype=="feature"){this.request.get_image=null;var qry=this.request.get_feature.query;this.addCoordSys(qry.featurecoordsys,options.featureCoordSys);this.addCoordSys(qry.filtercoordsys,options.filterCoordSys);if(options.polygon){qry.isspatial=true;qry.spatialfilter.polygon=options.polygon;}else if(options.envelope){qry.isspatial=true;qry.spatialfilter.envelope={minx:0,miny:0,maxx:0,maxy:0};this.parseEnvelope(qry.spatialfilter.envelope,options.envelope);}}else if(options.requesttype=="image"){this.request.get_feature=null;var props=this.request.get_image.properties;this.parseEnvelope(props.envelope,options.envelope);this.addLayers(props.layerlist,options.layers);this.addImageSize(props.imagesize,options.tileSize);this.addCoordSys(props.featurecoordsys,options.featureCoordSys);this.addCoordSys(props.filtercoordsys,options.filterCoordSys);}else{this.request=null;}}
OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},parseEnvelope:function(env,arr){if(arr&&arr.length==4){env.minx=arr[0];env.miny=arr[1];env.maxx=arr[2];env.maxy=arr[3];}},addLayers:function(ll,lyrs){for(var lind=0,len=lyrs.length;lind<len;lind++){ll.push(lyrs[lind]);}},addImageSize:function(imsize,olsize){if(olsize!==null){imsize.width=olsize.w;imsize.height=olsize.h;imsize.printwidth=olsize.w;imsize.printheight=olsize.h;}},addCoordSys:function(featOrFilt,fsys){if(typeof fsys=="string"){featOrFilt.id=parseInt(fsys);featOrFilt.string=fsys;}
else if(typeof fsys=="object"&&fsys.proj!==null){featOrFilt.id=fsys.proj.srsProjNumber;featOrFilt.string=fsys.proj.srsCode;}else{featOrFilt=fsys;}},iserror:function(data){var ret=null;if(!data){ret=(this.response.error!=='');}else{data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);var errorNodes=data.documentElement.getElementsByTagName("ERROR");ret=(errorNodes!==null&&errorNodes.length>0);}
return ret;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var arcNode=null;if(data&&data.documentElement){if(data.documentElement.nodeName=="ARCXML"){arcNode=data.documentElement;}else{arcNode=data.documentElement.getElementsByTagName("ARCXML")[0];}}
if(!arcNode){var error,source;try{error=data.firstChild.nodeValue;source=data.firstChild.childNodes[1].firstChild.nodeValue;}catch(err){}
throw{message:"Error parsing the ArcXML request",error:error,source:source};}
var response=this.parseResponse(arcNode);return response;},write:function(request){if(!request){request=this.request;}
var root=this.createElementNS("","ARCXML");root.setAttribute("version","1.1");var reqElem=this.createElementNS("","REQUEST");if(request.get_image!=null){var getElem=this.createElementNS("","GET_IMAGE");reqElem.appendChild(getElem);var propElem=this.createElementNS("","PROPERTIES");getElem.appendChild(propElem);var props=request.get_image.properties;if(props.featurecoordsys!=null){var feat=this.createElementNS("","FEATURECOORDSYS");propElem.appendChild(feat);if(props.featurecoordsys.id===0){feat.setAttribute("string",props.featurecoordsys['string']);}
else{feat.setAttribute("id",props.featurecoordsys.id);}}
if(props.filtercoordsys!=null){var filt=this.createElementNS("","FILTERCOORDSYS");propElem.appendChild(filt);if(props.filtercoordsys.id===0){filt.setAttribute("string",props.filtercoordsys.string);}
else{filt.setAttribute("id",props.filtercoordsys.id);}}
if(props.envelope!=null){var env=this.createElementNS("","ENVELOPE");propElem.appendChild(env);env.setAttribute("minx",props.envelope.minx);env.setAttribute("miny",props.envelope.miny);env.setAttribute("maxx",props.envelope.maxx);env.setAttribute("maxy",props.envelope.maxy);}
var imagesz=this.createElementNS("","IMAGESIZE");propElem.appendChild(imagesz);imagesz.setAttribute("height",props.imagesize.height);imagesz.setAttribute("width",props.imagesize.width);if(props.imagesize.height!=props.imagesize.printheight||props.imagesize.width!=props.imagesize.printwidth){imagesz.setAttribute("printheight",props.imagesize.printheight);imagesz.setArrtibute("printwidth",props.imagesize.printwidth);}
if(props.background!=null){var backgrnd=this.createElementNS("","BACKGROUND");propElem.appendChild(backgrnd);backgrnd.setAttribute("color",props.background.color.r+","+
props.background.color.g+","+
props.background.color.b);if(props.background.transcolor!==null){backgrnd.setAttribute("transcolor",props.background.transcolor.r+","+
props.background.transcolor.g+","+
props.background.transcolor.b);}}
if(props.layerlist!=null&&props.layerlist.length>0){var layerlst=this.createElementNS("","LAYERLIST");propElem.appendChild(layerlst);for(var ld=0;ld<props.layerlist.length;ld++){var ldef=this.createElementNS("","LAYERDEF");layerlst.appendChild(ldef);ldef.setAttribute("id",props.layerlist[ld].id);ldef.setAttribute("visible",props.layerlist[ld].visible);if(typeof props.layerlist[ld].query=="object"){var query=props.layerlist[ld].query;if(query.where.length<0){continue;}
var queryElem=null;if(typeof query.spatialfilter=="boolean"&&query.spatialfilter){queryElem=this.createElementNS("","SPATIALQUERY");}
else{queryElem=this.createElementNS("","QUERY");}
queryElem.setAttribute("where",query.where);if(typeof query.accuracy=="number"&&query.accuracy>0){queryElem.setAttribute("accuracy",query.accuracy);}
if(typeof query.featurelimit=="number"&&query.featurelimit<2000){queryElem.setAttribute("featurelimit",query.featurelimit);}
if(typeof query.subfields=="string"&&query.subfields!="#ALL#"){queryElem.setAttribute("subfields",query.subfields);}
if(typeof query.joinexpression=="string"&&query.joinexpression.length>0){queryElem.setAttribute("joinexpression",query.joinexpression);}
if(typeof query.jointables=="string"&&query.jointables.length>0){queryElem.setAttribute("jointables",query.jointables);}
ldef.appendChild(queryElem);}
if(typeof props.layerlist[ld].renderer=="object"){this.addRenderer(ldef,props.layerlist[ld].renderer);}}}}else if(request.get_feature!=null){var getElem=this.createElementNS("","GET_FEATURES");getElem.setAttribute("outputmode","newxml");getElem.setAttribute("checkesc","true");if(request.get_feature.geometry){getElem.setAttribute("geometry",request.get_feature.geometry);}
else{getElem.setAttribute("geometry","false");}
if(request.get_feature.compact){getElem.setAttribute("compact",request.get_feature.compact);}
if(request.get_feature.featurelimit=="number"){getElem.setAttribute("featurelimit",request.get_feature.featurelimit);}
getElem.setAttribute("globalenvelope","true");reqElem.appendChild(getElem);if(request.get_feature.layer!=null&&request.get_feature.layer.length>0){var lyrElem=this.createElementNS("","LAYER");lyrElem.setAttribute("id",request.get_feature.layer);getElem.appendChild(lyrElem);}
var fquery=request.get_feature.query;if(fquery!=null){var qElem=null;if(fquery.isspatial){qElem=this.createElementNS("","SPATIALQUERY");}else{qElem=this.createElementNS("","QUERY");}
getElem.appendChild(qElem);if(typeof fquery.accuracy=="number"){qElem.setAttribute("accuracy",fquery.accuracy);}
if(fquery.featurecoordsys!=null){var fcsElem1=this.createElementNS("","FEATURECOORDSYS");if(fquery.featurecoordsys.id==0){fcsElem1.setAttribute("string",fquery.featurecoordsys.string);}else{fcsElem1.setAttribute("id",fquery.featurecoordsys.id);}
qElem.appendChild(fcsElem1);}
if(fquery.filtercoordsys!=null){var fcsElem2=this.createElementNS("","FILTERCOORDSYS");if(fquery.filtercoordsys.id===0){fcsElem2.setAttribute("string",fquery.filtercoordsys.string);}else{fcsElem2.setAttribute("id",fquery.filtercoordsys.id);}
qElem.appendChild(fcsElem2);}
if(fquery.buffer>0){var bufElem=this.createElementNS("","BUFFER");bufElem.setAttribute("distance",fquery.buffer);qElem.appendChild(bufElem);}
if(fquery.isspatial){var spfElem=this.createElementNS("","SPATIALFILTER");spfElem.setAttribute("relation",fquery.spatialfilter.relation);qElem.appendChild(spfElem);if(fquery.spatialfilter.envelope){var envElem=this.createElementNS("","ENVELOPE");envElem.setAttribute("minx",fquery.spatialfilter.envelope.minx);envElem.setAttribute("miny",fquery.spatialfilter.envelope.miny);envElem.setAttribute("maxx",fquery.spatialfilter.envelope.maxx);envElem.setAttribute("maxy",fquery.spatialfilter.envelope.maxy);spfElem.appendChild(envElem);}else if(typeof fquery.spatialfilter.polygon=="object"){spfElem.appendChild(this.writePolygonGeometry(fquery.spatialfilter.polygon));}}
if(fquery.where!=null&&fquery.where.length>0){qElem.setAttribute("where",fquery.where);}}}
root.appendChild(reqElem);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},addGroupRenderer:function(ldef,toprenderer){var topRelem=this.createElementNS("","GROUPRENDERER");ldef.appendChild(topRelem);for(var rind=0;rind<toprenderer.length;rind++){var renderer=toprenderer[rind];this.addRenderer(topRelem,renderer);}},addRenderer:function(topRelem,renderer){if(renderer instanceof Array){this.addGroupRenderer(topRelem,renderer);}else{var renderElem=this.createElementNS("",renderer.type.toUpperCase()+"RENDERER");topRelem.appendChild(renderElem);if(renderElem.tagName=="VALUEMAPRENDERER"){this.addValueMapRenderer(renderElem,renderer);}else if(renderElem.tagName=="VALUEMAPLABELRENDERER"){this.addValueMapLabelRenderer(renderElem,renderer);}else if(renderElem.tagName=="SIMPLELABELRENDERER"){this.addSimpleLabelRenderer(renderElem,renderer);}else if(renderElem.tagName=="SCALEDEPENDENTRENDERER"){this.addScaleDependentRenderer(renderElem,renderer);}}},addScaleDependentRenderer:function(renderElem,renderer){if(typeof renderer.lower=="string"||typeof renderer.lower=="number"){renderElem.setAttribute("lower",renderer.lower);}
if(typeof renderer.upper=="string"||typeof renderer.upper=="number"){renderElem.setAttribute("upper",renderer.upper);}
this.addRenderer(renderElem,renderer.renderer);},addValueMapLabelRenderer:function(renderElem,renderer){renderElem.setAttribute("lookupfield",renderer.lookupfield);renderElem.setAttribute("labelfield",renderer.labelfield);if(typeof renderer.exacts=="object"){for(var ext=0,extlen=renderer.exacts.length;ext<extlen;ext++){var exact=renderer.exacts[ext];var eelem=this.createElementNS("","EXACT");if(typeof exact.value=="string"){eelem.setAttribute("value",exact.value);}
if(typeof exact.label=="string"){eelem.setAttribute("label",exact.label);}
if(typeof exact.method=="string"){eelem.setAttribute("method",exact.method);}
renderElem.appendChild(eelem);if(typeof exact.symbol=="object"){var selem=null;if(exact.symbol.type=="text"){selem=this.createElementNS("","TEXTSYMBOL");}
if(selem!=null){var keys=this.fontStyleKeys;for(var i=0,len=keys.length;i<len;i++){var key=keys[i];if(exact.symbol[key]){selem.setAttribute(key,exact.symbol[key]);}}
eelem.appendChild(selem);}}}}},addValueMapRenderer:function(renderElem,renderer){renderElem.setAttribute("lookupfield",renderer.lookupfield);if(typeof renderer.ranges=="object"){for(var rng=0,rnglen=renderer.ranges.length;rng<rnglen;rng++){var range=renderer.ranges[rng];var relem=this.createElementNS("","RANGE");relem.setAttribute("lower",range.lower);relem.setAttribute("upper",range.upper);renderElem.appendChild(relem);if(typeof range.symbol=="object"){var selem=null;if(range.symbol.type=="simplepolygon"){selem=this.createElementNS("","SIMPLEPOLYGONSYMBOL");}
if(selem!=null){if(typeof range.symbol.boundarycolor=="string"){selem.setAttribute("boundarycolor",range.symbol.boundarycolor);}
if(typeof range.symbol.fillcolor=="string"){selem.setAttribute("fillcolor",range.symbol.fillcolor);}
if(typeof range.symbol.filltransparency=="number"){selem.setAttribute("filltransparency",range.symbol.filltransparency);}
relem.appendChild(selem);}}}}else if(typeof renderer.exacts=="object"){for(var ext=0,extlen=renderer.exacts.length;ext<extlen;ext++){var exact=renderer.exacts[ext];var eelem=this.createElementNS("","EXACT");if(typeof exact.value=="string"){eelem.setAttribute("value",exact.value);}
if(typeof exact.label=="string"){eelem.setAttribute("label",exact.label);}
if(typeof exact.method=="string"){eelem.setAttribute("method",exact.method);}
renderElem.appendChild(eelem);if(typeof exact.symbol=="object"){var selem=null;if(exact.symbol.type=="simplemarker"){selem=this.createElementNS("","SIMPLEMARKERSYMBOL");}
if(selem!=null){if(typeof exact.symbol.antialiasing=="string"){selem.setAttribute("antialiasing",exact.symbol.antialiasing);}
if(typeof exact.symbol.color=="string"){selem.setAttribute("color",exact.symbol.color);}
if(typeof exact.symbol.outline=="string"){selem.setAttribute("outline",exact.symbol.outline);}
if(typeof exact.symbol.overlap=="string"){selem.setAttribute("overlap",exact.symbol.overlap);}
if(typeof exact.symbol.shadow=="string"){selem.setAttribute("shadow",exact.symbol.shadow);}
if(typeof exact.symbol.transparency=="number"){selem.setAttribute("transparency",exact.symbol.transparency);}
if(typeof exact.symbol.usecentroid=="string"){selem.setAttribute("usecentroid",exact.symbol.usecentroid);}
if(typeof exact.symbol.width=="number"){selem.setAttribute("width",exact.symbol.width);}
eelem.appendChild(selem);}}}}},addSimpleLabelRenderer:function(renderElem,renderer){renderElem.setAttribute("field",renderer.field);var keys=['featureweight','howmanylabels','labelbufferratio','labelpriorities','labelweight','linelabelposition','rotationalangles'];for(var i=0,len=keys.length;i<len;i++){var key=keys[i];if(renderer[key]){renderElem.setAttribute(key,renderer[key]);}}
if(renderer.symbol.type=="text"){var symbol=renderer.symbol;var selem=this.createElementNS("","TEXTSYMBOL");renderElem.appendChild(selem);var keys=this.fontStyleKeys;for(var i=0,len=keys.length;i<len;i++){var key=keys[i];if(symbol[key]){selem.setAttribute(key,renderer[key]);}}}},writePolygonGeometry:function(polygon){if(!(polygon instanceof OpenLayers.Geometry.Polygon)){throw{message:'Cannot write polygon geometry to ArcXML with an '+
polygon.CLASS_NAME+' object.',geometry:polygon};}
var polyElem=this.createElementNS("","POLYGON");for(var ln=0,lnlen=polygon.components.length;ln<lnlen;ln++){var ring=polygon.components[ln];var ringElem=this.createElementNS("","RING");for(var rn=0,rnlen=ring.components.length;rn<rnlen;rn++){var point=ring.components[rn];var pointElem=this.createElementNS("","POINT");pointElem.setAttribute("x",point.x);pointElem.setAttribute("y",point.y);ringElem.appendChild(pointElem);}
polyElem.appendChild(ringElem);}
return polyElem;},parseResponse:function(data){if(typeof data=="string"){var newData=new OpenLayers.Format.XML();data=newData.read(data);}
var response=new OpenLayers.Format.ArcXML.Response();var errorNode=data.getElementsByTagName("ERROR");if(errorNode!=null&&errorNode.length>0){response.error=this.getChildValue(errorNode,"Unknown error.");}else{var responseNode=data.getElementsByTagName("RESPONSE");if(responseNode==null||responseNode.length==0){response.error="No RESPONSE tag found in ArcXML response.";return response;}
var rtype=responseNode[0].firstChild.nodeName;if(rtype=="#text"){rtype=responseNode[0].firstChild.nextSibling.nodeName;}
if(rtype=="IMAGE"){var envelopeNode=data.getElementsByTagName("ENVELOPE");var outputNode=data.getElementsByTagName("OUTPUT");if(envelopeNode==null||envelopeNode.length==0){response.error="No ENVELOPE tag found in ArcXML response.";}else if(outputNode==null||outputNode.length==0){response.error="No OUTPUT tag found in ArcXML response.";}else{var envAttr=this.parseAttributes(envelopeNode[0]);var outputAttr=this.parseAttributes(outputNode[0]);if(typeof outputAttr.type=="string"){response.image={envelope:envAttr,output:{type:outputAttr.type,data:this.getChildValue(outputNode[0])}};}else{response.image={envelope:envAttr,output:outputAttr};}}}else if(rtype=="FEATURES"){var features=responseNode[0].getElementsByTagName("FEATURES");var featureCount=features[0].getElementsByTagName("FEATURECOUNT");response.features.featurecount=featureCount[0].getAttribute("count");if(response.features.featurecount>0){var envelope=features[0].getElementsByTagName("ENVELOPE");response.features.envelope=this.parseAttributes(envelope[0],typeof(0));var featureList=features[0].getElementsByTagName("FEATURE");for(var fn=0;fn<featureList.length;fn++){var feature=new OpenLayers.Feature.Vector();var fields=featureList[fn].getElementsByTagName("FIELD");for(var fdn=0;fdn<fields.length;fdn++){var fieldName=fields[fdn].getAttribute("name");var fieldValue=fields[fdn].getAttribute("value");feature.attributes[fieldName]=fieldValue;}
var geom=featureList[fn].getElementsByTagName("POLYGON");if(geom.length>0){var ring=geom[0].getElementsByTagName("RING");var polys=[];for(var rn=0;rn<ring.length;rn++){var linearRings=[];linearRings.push(this.parsePointGeometry(ring[rn]));var holes=ring[rn].getElementsByTagName("HOLE");for(var hn=0;hn<holes.length;hn++){linearRings.push(this.parsePointGeometry(holes[hn]));}
holes=null;polys.push(new OpenLayers.Geometry.Polygon(linearRings));linearRings=null;}
ring=null;if(polys.length==1){feature.geometry=polys[0];}else
{feature.geometry=new OpenLayers.Geometry.MultiPolygon(polys);}}
response.features.feature.push(feature);}}}else{response.error="Unidentified response type.";}}
return response;},parseAttributes:function(node,type){var attributes={};for(var attr=0;attr<node.attributes.length;attr++){if(type=="number"){attributes[node.attributes[attr].nodeName]=parseFloat(node.attributes[attr].nodeValue);}else{attributes[node.attributes[attr].nodeName]=node.attributes[attr].nodeValue;}}
return attributes;},parsePointGeometry:function(node){var ringPoints=[];var coords=node.getElementsByTagName("COORDS");if(coords.length>0){var coordArr=this.getChildValue(coords[0]);coordArr=coordArr.split(/;/);for(var cn=0;cn<coordArr.length;cn++){var coordItems=coordArr[cn].split(/ /);ringPoints.push(new OpenLayers.Geometry.Point(parseFloat(coordItems[0]),parseFloat(coordItems[1])));}
coords=null;}else{var point=node.getElementsByTagName("POINT");if(point.length>0){for(var pn=0;pn<point.length;pn++){ringPoints.push(new OpenLayers.Geometry.Point(parseFloat(point[pn].getAttribute("x")),parseFloat(point[pn].getAttribute("y"))));}}
point=null;}
return new OpenLayers.Geometry.LinearRing(ringPoints);},CLASS_NAME:"OpenLayers.Format.ArcXML"});OpenLayers.Format.ArcXML.Request=OpenLayers.Class({initialize:function(params){var defaults={get_image:{properties:{background:null,draw:true,envelope:{minx:0,miny:0,maxx:0,maxy:0},featurecoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},filtercoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},imagesize:{height:0,width:0,dpi:96,printheight:0,printwidth:0,scalesymbols:false},layerlist:[],output:{baseurl:"",legendbaseurl:"",legendname:"",legendpath:"",legendurl:"",name:"",path:"",type:"jpg",url:""}}},get_feature:{layer:"",query:{isspatial:false,featurecoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},filtercoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},buffer:0,where:"",spatialfilter:{relation:"envelope_intersection",envelope:null}}},environment:{separators:{cs:" ",ts:";"}},layer:[],workspaces:[]};return OpenLayers.Util.extend(this,defaults);},CLASS_NAME:"OpenLayers.Format.ArcXML.Request"});OpenLayers.Format.ArcXML.Response=OpenLayers.Class({initialize:function(params){var defaults={image:{envelope:null,output:''},features:{featurecount:0,envelope:null,feature:[]},error:''};return OpenLayers.Util.extend(this,defaults);},CLASS_NAME:"OpenLayers.Format.ArcXML.Response"});OpenLayers.Format.GML=OpenLayers.Class(OpenLayers.Format.XML,{featureNS:"http://mapserver.gis.umn.edu/mapserver",featurePrefix:"feature",featureName:"featureMember",layerName:"features",geometryName:"geometry",collectionName:"FeatureCollection",gmlns:"http://www.opengis.net/gml",extractAttributes:true,xy:true,initialize:function(options){this.regExes={trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)};OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
var featureNodes=this.getElementsByTagNameNS(data.documentElement,this.gmlns,this.featureName);var features=[];for(var i=0;i<featureNodes.length;i++){var feature=this.parseFeature(featureNodes[i]);if(feature){features.push(feature);}}
return features;},parseFeature:function(node){var order=["MultiPolygon","Polygon","MultiLineString","LineString","MultiPoint","Point","Envelope","Box"];var type,nodeList,geometry,parser;for(var i=0;i<order.length;++i){type=order[i];nodeList=this.getElementsByTagNameNS(node,this.gmlns,type);if(nodeList.length>0){var parser=this.parseGeometry[type.toLowerCase()];if(parser){geometry=parser.apply(this,[nodeList[0]]);if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}}else{OpenLayers.Console.error(OpenLayers.i18n("unsupportedGeometryType",{'geomType':type}));}
break;}}
var attributes;if(this.extractAttributes){attributes=this.parseAttributes(node);}
var feature=new OpenLayers.Feature.Vector(geometry,attributes);feature.gml={featureType:node.firstChild.nodeName.split(":")[1],featureNS:node.firstChild.namespaceURI,featureNSPrefix:node.firstChild.prefix};var childNode=node.firstChild;var fid;while(childNode){if(childNode.nodeType==1){fid=childNode.getAttribute("fid")||childNode.getAttribute("id");if(fid){break;}}
childNode=childNode.nextSibling;}
feature.fid=fid;return feature;},parseGeometry:{point:function(node){var nodeList,coordString;var coords=[];var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"pos");if(nodeList.length>0){coordString=nodeList[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);}
if(coords.length==0){nodeList=this.getElementsByTagNameNS(node,this.gmlns,"coordinates");if(nodeList.length>0){coordString=nodeList[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.removeSpace,"");coords=coordString.split(",");}}
if(coords.length==0){nodeList=this.getElementsByTagNameNS(node,this.gmlns,"coord");if(nodeList.length>0){var xList=this.getElementsByTagNameNS(nodeList[0],this.gmlns,"X");var yList=this.getElementsByTagNameNS(nodeList[0],this.gmlns,"Y");if(xList.length>0&&yList.length>0){coords=[xList[0].firstChild.nodeValue,yList[0].firstChild.nodeValue];}}}
if(coords.length==2){coords[2]=null;}
if(this.xy){return new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}
else{return new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}},multipoint:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"Point");var components=[];if(nodeList.length>0){var point;for(var i=0;i<nodeList.length;++i){point=this.parseGeometry.point.apply(this,[nodeList[i]]);if(point){components.push(point);}}}
return new OpenLayers.Geometry.MultiPoint(components);},linestring:function(node,ring){var nodeList,coordString;var coords=[];var points=[];nodeList=this.getElementsByTagNameNS(node,this.gmlns,"posList");if(nodeList.length>0){coordString=this.getChildValue(nodeList[0]);coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);var dim=parseInt(nodeList[0].getAttribute("dimension"));var j,x,y,z;for(var i=0;i<coords.length/dim;++i){j=i*dim;x=coords[j];y=coords[j+1];z=(dim==2)?null:coords[j+2];if(this.xy){points.push(new OpenLayers.Geometry.Point(x,y,z));}else{points.push(new OpenLayers.Geometry.Point(y,x,z));}}}
if(coords.length==0){nodeList=this.getElementsByTagNameNS(node,this.gmlns,"coordinates");if(nodeList.length>0){coordString=this.getChildValue(nodeList[0]);coordString=coordString.replace(this.regExes.trimSpace,"");coordString=coordString.replace(this.regExes.trimComma,",");var pointList=coordString.split(this.regExes.splitSpace);for(var i=0;i<pointList.length;++i){coords=pointList[i].split(",");if(coords.length==2){coords[2]=null;}
if(this.xy){points.push(new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]));}else{points.push(new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]));}}}}
var line=null;if(points.length!=0){if(ring){line=new OpenLayers.Geometry.LinearRing(points);}else{line=new OpenLayers.Geometry.LineString(points);}}
return line;},multilinestring:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"LineString");var components=[];if(nodeList.length>0){var line;for(var i=0;i<nodeList.length;++i){line=this.parseGeometry.linestring.apply(this,[nodeList[i]]);if(line){components.push(line);}}}
return new OpenLayers.Geometry.MultiLineString(components);},polygon:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"LinearRing");var components=[];if(nodeList.length>0){var ring;for(var i=0;i<nodeList.length;++i){ring=this.parseGeometry.linestring.apply(this,[nodeList[i],true]);if(ring){components.push(ring);}}}
return new OpenLayers.Geometry.Polygon(components);},multipolygon:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"Polygon");var components=[];if(nodeList.length>0){var polygon;for(var i=0;i<nodeList.length;++i){polygon=this.parseGeometry.polygon.apply(this,[nodeList[i]]);if(polygon){components.push(polygon);}}}
return new OpenLayers.Geometry.MultiPolygon(components);},envelope:function(node){var components=[];var coordString;var envelope;var lpoint=this.getElementsByTagNameNS(node,this.gmlns,"lowerCorner");if(lpoint.length>0){var coords=[];if(lpoint.length>0){coordString=lpoint[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);}
if(coords.length==2){coords[2]=null;}
if(this.xy){var lowerPoint=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{var lowerPoint=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}}
var upoint=this.getElementsByTagNameNS(node,this.gmlns,"upperCorner");if(upoint.length>0){var coords=[];if(upoint.length>0){coordString=upoint[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);}
if(coords.length==2){coords[2]=null;}
if(this.xy){var upperPoint=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{var upperPoint=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}}
if(lowerPoint&&upperPoint){components.push(new OpenLayers.Geometry.Point(lowerPoint.x,lowerPoint.y));components.push(new OpenLayers.Geometry.Point(upperPoint.x,lowerPoint.y));components.push(new OpenLayers.Geometry.Point(upperPoint.x,upperPoint.y));components.push(new OpenLayers.Geometry.Point(lowerPoint.x,upperPoint.y));components.push(new OpenLayers.Geometry.Point(lowerPoint.x,lowerPoint.y));var ring=new OpenLayers.Geometry.LinearRing(components);envelope=new OpenLayers.Geometry.Polygon([ring]);}
return envelope;}},parseAttributes:function(node){var attributes={};var childNode=node.firstChild;var children,i,child,grandchildren,grandchild,name,value;while(childNode){if(childNode.nodeType==1){children=childNode.childNodes;for(i=0;i<children.length;++i){child=children[i];if(child.nodeType==1){grandchildren=child.childNodes;if(grandchildren.length==1){grandchild=grandchildren[0];if(grandchild.nodeType==3||grandchild.nodeType==4){name=(child.prefix)?child.nodeName.split(":")[1]:child.nodeName;value=grandchild.nodeValue.replace(this.regExes.trimSpace,"");attributes[name]=value;}}else{attributes[child.nodeName.split(":").pop()]=null;}}}
break;}
childNode=childNode.nextSibling;}
return attributes;},write:function(features){if(!(features instanceof Array)){features=[features];}
var gml=this.createElementNS("http://www.opengis.net/wfs","wfs:"+this.collectionName);for(var i=0;i<features.length;i++){gml.appendChild(this.createFeatureXML(features[i]));}
return OpenLayers.Format.XML.prototype.write.apply(this,[gml]);},createFeatureXML:function(feature){var geometry=feature.geometry;var geometryNode=this.buildGeometryNode(geometry);var geomContainer=this.createElementNS(this.featureNS,this.featurePrefix+":"+
this.geometryName);geomContainer.appendChild(geometryNode);var featureNode=this.createElementNS(this.gmlns,"gml:"+this.featureName);var featureContainer=this.createElementNS(this.featureNS,this.featurePrefix+":"+
this.layerName);var fid=feature.fid||feature.id;featureContainer.setAttribute("fid",fid);featureContainer.appendChild(geomContainer);for(var attr in feature.attributes){var attrText=this.createTextNode(feature.attributes[attr]);var nodename=attr.substring(attr.lastIndexOf(":")+1);var attrContainer=this.createElementNS(this.featureNS,this.featurePrefix+":"+
nodename);attrContainer.appendChild(attrText);featureContainer.appendChild(attrContainer);}
featureNode.appendChild(featureContainer);return featureNode;},buildGeometryNode:function(geometry){if(this.externalProjection&&this.internalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
var className=geometry.CLASS_NAME;var type=className.substring(className.lastIndexOf(".")+1);var builder=this.buildGeometry[type.toLowerCase()];return builder.apply(this,[geometry]);},buildGeometry:{point:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:Point");gml.appendChild(this.buildCoordinatesNode(geometry));return gml;},multipoint:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:MultiPoint");var points=geometry.components;var pointMember,pointGeom;for(var i=0;i<points.length;i++){pointMember=this.createElementNS(this.gmlns,"gml:pointMember");pointGeom=this.buildGeometry.point.apply(this,[points[i]]);pointMember.appendChild(pointGeom);gml.appendChild(pointMember);}
return gml;},linestring:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:LineString");gml.appendChild(this.buildCoordinatesNode(geometry));return gml;},multilinestring:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:MultiLineString");var lines=geometry.components;var lineMember,lineGeom;for(var i=0;i<lines.length;++i){lineMember=this.createElementNS(this.gmlns,"gml:lineStringMember");lineGeom=this.buildGeometry.linestring.apply(this,[lines[i]]);lineMember.appendChild(lineGeom);gml.appendChild(lineMember);}
return gml;},linearring:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:LinearRing");gml.appendChild(this.buildCoordinatesNode(geometry));return gml;},polygon:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:Polygon");var rings=geometry.components;var ringMember,ringGeom,type;for(var i=0;i<rings.length;++i){type=(i==0)?"outerBoundaryIs":"innerBoundaryIs";ringMember=this.createElementNS(this.gmlns,"gml:"+type);ringGeom=this.buildGeometry.linearring.apply(this,[rings[i]]);ringMember.appendChild(ringGeom);gml.appendChild(ringMember);}
return gml;},multipolygon:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:MultiPolygon");var polys=geometry.components;var polyMember,polyGeom;for(var i=0;i<polys.length;++i){polyMember=this.createElementNS(this.gmlns,"gml:polygonMember");polyGeom=this.buildGeometry.polygon.apply(this,[polys[i]]);polyMember.appendChild(polyGeom);gml.appendChild(polyMember);}
return gml;},bounds:function(bounds){var gml=this.createElementNS(this.gmlns,"gml:Box");gml.appendChild(this.buildCoordinatesNode(bounds));return gml;}},buildCoordinatesNode:function(geometry){var coordinatesNode=this.createElementNS(this.gmlns,"gml:coordinates");coordinatesNode.setAttribute("decimal",".");coordinatesNode.setAttribute("cs",",");coordinatesNode.setAttribute("ts"," ");var parts=[];if(geometry instanceof OpenLayers.Bounds){parts.push(geometry.left+","+geometry.bottom);parts.push(geometry.right+","+geometry.top);}else{var points=(geometry.components)?geometry.components:[geometry];for(var i=0;i<points.length;i++){parts.push(points[i].x+","+points[i].y);}}
var txtNode=this.createTextNode(parts.join(" "));coordinatesNode.appendChild(txtNode);return coordinatesNode;},CLASS_NAME:"OpenLayers.Format.GML"});OpenLayers.Format.GeoJSON=OpenLayers.Class(OpenLayers.Format.JSON,{initialize:function(options){OpenLayers.Format.JSON.prototype.initialize.apply(this,[options]);},read:function(json,type,filter){type=(type)?type:"FeatureCollection";var results=null;var obj=null;if(typeof json=="string"){obj=OpenLayers.Format.JSON.prototype.read.apply(this,[json,filter]);}else{obj=json;}
if(!obj){OpenLayers.Console.error("Bad JSON: "+json);}else if(typeof(obj.type)!="string"){OpenLayers.Console.error("Bad GeoJSON - no type: "+json);}else if(this.isValidType(obj,type)){switch(type){case"Geometry":try{results=this.parseGeometry(obj);}catch(err){OpenLayers.Console.error(err);}
break;case"Feature":try{results=this.parseFeature(obj);results.type="Feature";}catch(err){OpenLayers.Console.error(err);}
break;case"FeatureCollection":results=[];switch(obj.type){case"Feature":try{results.push(this.parseFeature(obj));}catch(err){results=null;OpenLayers.Console.error(err);}
break;case"FeatureCollection":for(var i=0,len=obj.features.length;i<len;++i){try{results.push(this.parseFeature(obj.features[i]));}catch(err){results=null;OpenLayers.Console.error(err);}}
break;default:try{var geom=this.parseGeometry(obj);results.push(new OpenLayers.Feature.Vector(geom));}catch(err){results=null;OpenLayers.Console.error(err);}}
break;}}
return results;},isValidType:function(obj,type){var valid=false;switch(type){case"Geometry":if(OpenLayers.Util.indexOf(["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","Box","GeometryCollection"],obj.type)==-1){OpenLayers.Console.error("Unsupported geometry type: "+
obj.type);}else{valid=true;}
break;case"FeatureCollection":valid=true;break;default:if(obj.type==type){valid=true;}else{OpenLayers.Console.error("Cannot convert types from "+
obj.type+" to "+type);}}
return valid;},parseFeature:function(obj){var feature,geometry,attributes,bbox;attributes=(obj.properties)?obj.properties:{};bbox=(obj.geometry&&obj.geometry.bbox)||obj.bbox;try{geometry=this.parseGeometry(obj.geometry);}catch(err){throw err;}
feature=new OpenLayers.Feature.Vector(geometry,attributes);if(bbox){feature.bounds=OpenLayers.Bounds.fromArray(bbox);}
if(obj.id){feature.fid=obj.id;}
return feature;},parseGeometry:function(obj){if(obj==null){return null;}
var geometry,collection=false;if(obj.type=="GeometryCollection"){if(!(obj.geometries instanceof Array)){throw"GeometryCollection must have geometries array: "+obj;}
var numGeom=obj.geometries.length;var components=new Array(numGeom);for(var i=0;i<numGeom;++i){components[i]=this.parseGeometry.apply(this,[obj.geometries[i]]);}
geometry=new OpenLayers.Geometry.Collection(components);collection=true;}else{if(!(obj.coordinates instanceof Array)){throw"Geometry must have coordinates array: "+obj;}
if(!this.parseCoords[obj.type.toLowerCase()]){throw"Unsupported geometry type: "+obj.type;}
try{geometry=this.parseCoords[obj.type.toLowerCase()].apply(this,[obj.coordinates]);}catch(err){throw err;}}
if(this.internalProjection&&this.externalProjection&&!collection){geometry.transform(this.externalProjection,this.internalProjection);}
return geometry;},parseCoords:{"point":function(array){if(array.length!=2){throw"Only 2D points are supported: "+array;}
return new OpenLayers.Geometry.Point(array[0],array[1]);},"multipoint":function(array){var points=[];var p=null;for(var i=0,len=array.length;i<len;++i){try{p=this.parseCoords["point"].apply(this,[array[i]]);}catch(err){throw err;}
points.push(p);}
return new OpenLayers.Geometry.MultiPoint(points);},"linestring":function(array){var points=[];var p=null;for(var i=0,len=array.length;i<len;++i){try{p=this.parseCoords["point"].apply(this,[array[i]]);}catch(err){throw err;}
points.push(p);}
return new OpenLayers.Geometry.LineString(points);},"multilinestring":function(array){var lines=[];var l=null;for(var i=0,len=array.length;i<len;++i){try{l=this.parseCoords["linestring"].apply(this,[array[i]]);}catch(err){throw err;}
lines.push(l);}
return new OpenLayers.Geometry.MultiLineString(lines);},"polygon":function(array){var rings=[];var r,l;for(var i=0,len=array.length;i<len;++i){try{l=this.parseCoords["linestring"].apply(this,[array[i]]);}catch(err){throw err;}
r=new OpenLayers.Geometry.LinearRing(l.components);rings.push(r);}
return new OpenLayers.Geometry.Polygon(rings);},"multipolygon":function(array){var polys=[];var p=null;for(var i=0,len=array.length;i<len;++i){try{p=this.parseCoords["polygon"].apply(this,[array[i]]);}catch(err){throw err;}
polys.push(p);}
return new OpenLayers.Geometry.MultiPolygon(polys);},"box":function(array){if(array.length!=2){throw"GeoJSON box coordinates must have 2 elements";}
return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(array[0][0],array[0][1]),new OpenLayers.Geometry.Point(array[1][0],array[0][1]),new OpenLayers.Geometry.Point(array[1][0],array[1][1]),new OpenLayers.Geometry.Point(array[0][0],array[1][1]),new OpenLayers.Geometry.Point(array[0][0],array[0][1])])]);}},write:function(obj,pretty){var geojson={"type":null};if(obj instanceof Array){geojson.type="FeatureCollection";var numFeatures=obj.length;geojson.features=new Array(numFeatures);for(var i=0;i<numFeatures;++i){var element=obj[i];if(!element instanceof OpenLayers.Feature.Vector){var msg="FeatureCollection only supports collections "+"of features: "+element;throw msg;}
geojson.features[i]=this.extract.feature.apply(this,[element]);}}else if(obj.CLASS_NAME.indexOf("OpenLayers.Geometry")==0){geojson=this.extract.geometry.apply(this,[obj]);}else if(obj instanceof OpenLayers.Feature.Vector){geojson=this.extract.feature.apply(this,[obj]);if(obj.layer&&obj.layer.projection){geojson.crs=this.createCRSObject(obj);}}
return OpenLayers.Format.JSON.prototype.write.apply(this,[geojson,pretty]);},createCRSObject:function(object){var proj=object.layer.projection.toString();var crs={};if(proj.match(/epsg:/i)){var code=parseInt(proj.substring(proj.indexOf(":")+1));if(code==4326){crs={"type":"OGC","properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}};}else{crs={"type":"EPSG","properties":{"code":code}};}}
return crs;},extract:{'feature':function(feature){var geom=this.extract.geometry.apply(this,[feature.geometry]);return{"type":"Feature","id":feature.fid==null?feature.id:feature.fid,"properties":feature.attributes,"geometry":geom};},'geometry':function(geometry){if(geometry==null){return null;}
if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
var geometryType=geometry.CLASS_NAME.split('.')[2];var data=this.extract[geometryType.toLowerCase()].apply(this,[geometry]);var json;if(geometryType=="Collection"){json={"type":"GeometryCollection","geometries":data};}else{json={"type":geometryType,"coordinates":data};}
return json;},'point':function(point){return[point.x,point.y];},'multipoint':function(multipoint){var array=[];for(var i=0,len=multipoint.components.length;i<len;++i){array.push(this.extract.point.apply(this,[multipoint.components[i]]));}
return array;},'linestring':function(linestring){var array=[];for(var i=0,len=linestring.components.length;i<len;++i){array.push(this.extract.point.apply(this,[linestring.components[i]]));}
return array;},'multilinestring':function(multilinestring){var array=[];for(var i=0,len=multilinestring.components.length;i<len;++i){array.push(this.extract.linestring.apply(this,[multilinestring.components[i]]));}
return array;},'polygon':function(polygon){var array=[];for(var i=0,len=polygon.components.length;i<len;++i){array.push(this.extract.linestring.apply(this,[polygon.components[i]]));}
return array;},'multipolygon':function(multipolygon){var array=[];for(var i=0,len=multipolygon.components.length;i<len;++i){array.push(this.extract.polygon.apply(this,[multipolygon.components[i]]));}
return array;},'collection':function(collection){var len=collection.components.length;var array=new Array(len);for(var i=0;i<len;++i){array[i]=this.extract.geometry.apply(this,[collection.components[i]]);}
return array;}},CLASS_NAME:"OpenLayers.Format.GeoJSON"});OpenLayers.Format.ArcXML.Features=OpenLayers.Class(OpenLayers.Format.XML,{initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){var axl=new OpenLayers.Format.ArcXML();var parsed=axl.read(data);return parsed.features.feature;}});if(!OpenLayers.Format.GML){OpenLayers.Format.GML={};}
OpenLayers.Format.GML.Base=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",wfs:"http://www.opengis.net/wfs"},defaultPrefix:"gml",schemaLocation:null,featureType:null,featureNS:null,geometryName:"geometry",extractAttributes:true,srsName:null,xy:true,geometryTypes:null,singleFeatureType:null,regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.setGeometryTypes();if(options&&options.featureNS){this.setNamespace("feature",options.featureNS);}
this.singleFeatureType=!options||(typeof options.featureType==="string");},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
if(data&&data.nodeType==9){data=data.documentElement;}
var features=[];this.readNode(data,{features:features});if(features.length==0){var elements=this.getElementsByTagNameNS(data,this.namespaces.gml,"featureMember");if(elements.length){for(var i=0,len=elements.length;i<len;++i){this.readNode(elements[i],{features:features});}}else{var elements=this.getElementsByTagNameNS(data,this.namespaces.gml,"featureMembers");if(elements.length){this.readNode(elements[0],{features:features});}}}
return features;},readers:{"gml":{"featureMember":function(node,obj){this.readChildNodes(node,obj);},"featureMembers":function(node,obj){this.readChildNodes(node,obj);},"name":function(node,obj){obj.name=this.getChildValue(node);},"boundedBy":function(node,obj){var container={};this.readChildNodes(node,container);if(container.components&&container.components.length>0){obj.bounds=container.components[0];}},"Point":function(node,container){var obj={points:[]};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
container.components.push(obj.points[0]);},"coordinates":function(node,obj){var str=this.getChildValue(node).replace(this.regExes.trimSpace,"");str=str.replace(this.regExes.trimComma,",");var pointList=str.split(this.regExes.splitSpace);var coords;var numPoints=pointList.length;var points=new Array(numPoints);for(var i=0;i<numPoints;++i){coords=pointList[i].split(",");if(this.xy){points[i]=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{points[i]=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}}
obj.points=points;},"coord":function(node,obj){var coord={};this.readChildNodes(node,coord);if(!obj.points){obj.points=[];}
obj.points.push(new OpenLayers.Geometry.Point(coord.x,coord.y,coord.z));},"X":function(node,coord){coord.x=this.getChildValue(node);},"Y":function(node,coord){coord.y=this.getChildValue(node);},"Z":function(node,coord){coord.z=this.getChildValue(node);},"MultiPoint":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);container.components=[new OpenLayers.Geometry.MultiPoint(obj.components)];},"pointMember":function(node,obj){this.readChildNodes(node,obj);},"LineString":function(node,container){var obj={};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
container.components.push(new OpenLayers.Geometry.LineString(obj.points));},"MultiLineString":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);container.components=[new OpenLayers.Geometry.MultiLineString(obj.components)];},"lineStringMember":function(node,obj){this.readChildNodes(node,obj);},"Polygon":function(node,container){var obj={outer:null,inner:[]};this.readChildNodes(node,obj);obj.inner.unshift(obj.outer);if(!container.components){container.components=[];}
container.components.push(new OpenLayers.Geometry.Polygon(obj.inner));},"LinearRing":function(node,obj){var container={};this.readChildNodes(node,container);obj.components=[new OpenLayers.Geometry.LinearRing(container.points)];},"MultiPolygon":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);container.components=[new OpenLayers.Geometry.MultiPolygon(obj.components)];},"polygonMember":function(node,obj){this.readChildNodes(node,obj);},"GeometryCollection":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);container.components=[new OpenLayers.Geometry.Collection(obj.components)];},"geometryMember":function(node,obj){this.readChildNodes(node,obj);}},"feature":{"*":function(node,obj){var name;var local=node.localName||node.nodeName.split(":").pop();if(!this.singleFeatureType&&(OpenLayers.Util.indexOf(this.featureType,local)!=-1)){name="_typeName";}
else if(local==this.featureType){name="_typeName";}else{if(node.childNodes.length==0||(node.childNodes.length==1&&node.firstChild.nodeType==3)){if(this.extractAttributes){name="_attribute";}}else{name="_geometry";}}
if(name){this.readers.feature[name].apply(this,[node,obj]);}},"_typeName":function(node,obj){var container={components:[],attributes:{}};this.readChildNodes(node,container);if(container.name){container.attributes.name=container.name;}
var feature=new OpenLayers.Feature.Vector(container.components[0],container.attributes);if(!this.singleFeatureType){feature.type=node.nodeName.split(":").pop();feature.namespace=node.namespaceURI;}
var fid=node.getAttribute("fid")||this.getAttributeNS(node,this.namespaces["gml"],"id");if(fid){feature.fid=fid;}
if(this.internalProjection&&this.externalProjection&&feature.geometry){feature.geometry.transform(this.externalProjection,this.internalProjection);}
if(container.bounds){feature.geometry.bounds=container.bounds;}
obj.features.push(feature);},"_geometry":function(node,obj){this.readChildNodes(node,obj);},"_attribute":function(node,obj){var local=node.localName||node.nodeName.split(":").pop();var value=this.getChildValue(node);obj.attributes[local]=value;}},"wfs":{"FeatureCollection":function(node,obj){this.readChildNodes(node,obj);}}},write:function(features){var name;if(features instanceof Array){name="featureMembers";}else{name="featureMember";}
var root=this.writeNode("gml:"+name,features);this.setAttributeNS(root,this.namespaces["xsi"],"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},writers:{"gml":{"featureMember":function(feature){var node=this.createElementNSPlus("gml:featureMember");this.writeNode("feature:_typeName",feature,node);return node;},"MultiPoint":function(geometry){var node=this.createElementNSPlus("gml:MultiPoint");for(var i=0;i<geometry.components.length;++i){this.writeNode("pointMember",geometry.components[i],node);}
return node;},"pointMember":function(geometry){var node=this.createElementNSPlus("gml:pointMember");this.writeNode("Point",geometry,node);return node;},"MultiLineString":function(geometry){var node=this.createElementNSPlus("gml:MultiLineString");for(var i=0;i<geometry.components.length;++i){this.writeNode("lineStringMember",geometry.components[i],node);}
return node;},"lineStringMember":function(geometry){var node=this.createElementNSPlus("gml:lineStringMember");this.writeNode("LineString",geometry,node);return node;},"MultiPolygon":function(geometry){var node=this.createElementNSPlus("gml:MultiPolygon");for(var i=0;i<geometry.components.length;++i){this.writeNode("polygonMember",geometry.components[i],node);}
return node;},"polygonMember":function(geometry){var node=this.createElementNSPlus("gml:polygonMember");this.writeNode("Polygon",geometry,node);return node;},"GeometryCollection":function(geometry){var node=this.createElementNSPlus("gml:GeometryCollection");for(var i=0,len=geometry.components.length;i<len;++i){this.writeNode("geometryMember",geometry.components[i],node);}
return node;},"geometryMember":function(geometry){var node=this.createElementNSPlus("gml:geometryMember");var child=this.writeNode("feature:_geometry",geometry);node.appendChild(child.firstChild);return node;}},"feature":{"_typeName":function(feature){var node=this.createElementNSPlus("feature:"+this.featureType,{attributes:{fid:feature.fid}});if(feature.geometry){this.writeNode("feature:_geometry",feature.geometry,node);}
for(var name in feature.attributes){var value=feature.attributes[name];if(value!=null){this.writeNode("feature:_attribute",{name:name,value:value},node);}}
return node;},"_geometry":function(geometry){if(this.externalProjection&&this.internalProjection){geometry=geometry.clone().transform(this.internalProjection,this.externalProjection);}
var node=this.createElementNSPlus("feature:"+this.geometryName);var type=this.geometryTypes[geometry.CLASS_NAME];var child=this.writeNode("gml:"+type,geometry,node);if(this.srsName){child.setAttribute("srsName",this.srsName);}
return node;},"_attribute":function(obj){return this.createElementNSPlus("feature:"+obj.name,{value:obj.value});}},"wfs":{"FeatureCollection":function(features){var node=this.createElementNSPlus("wfs:FeatureCollection");for(var i=0,len=features.length;i<len;++i){this.writeNode("gml:featureMember",features[i],node);}
return node;}}},setGeometryTypes:function(){this.geometryTypes={"OpenLayers.Geometry.Point":"Point","OpenLayers.Geometry.MultiPoint":"MultiPoint","OpenLayers.Geometry.LineString":"LineString","OpenLayers.Geometry.MultiLineString":"MultiLineString","OpenLayers.Geometry.Polygon":"Polygon","OpenLayers.Geometry.MultiPolygon":"MultiPolygon","OpenLayers.Geometry.Collection":"GeometryCollection"};},CLASS_NAME:"OpenLayers.Format.GML.Base"});OpenLayers.Format.WFS=OpenLayers.Class(OpenLayers.Format.GML,{layer:null,wfsns:"http://www.opengis.net/wfs",ogcns:"http://www.opengis.net/ogc",initialize:function(options,layer){OpenLayers.Format.GML.prototype.initialize.apply(this,[options]);this.layer=layer;if(this.layer.featureNS){this.featureNS=this.layer.featureNS;}
if(this.layer.options.geometry_column){this.geometryName=this.layer.options.geometry_column;}
if(this.layer.options.typename){this.featureName=this.layer.options.typename;}},write:function(features){var transaction=this.createElementNS(this.wfsns,'wfs:Transaction');transaction.setAttribute("version","1.0.0");transaction.setAttribute("service","WFS");for(var i=0;i<features.length;i++){switch(features[i].state){case OpenLayers.State.INSERT:transaction.appendChild(this.insert(features[i]));break;case OpenLayers.State.UPDATE:transaction.appendChild(this.update(features[i]));break;case OpenLayers.State.DELETE:transaction.appendChild(this.remove(features[i]));break;}}
return OpenLayers.Format.XML.prototype.write.apply(this,[transaction]);},createFeatureXML:function(feature){var geometryNode=this.buildGeometryNode(feature.geometry);var geomContainer=this.createElementNS(this.featureNS,"feature:"+this.geometryName);geomContainer.appendChild(geometryNode);var featureContainer=this.createElementNS(this.featureNS,"feature:"+this.featureName);featureContainer.appendChild(geomContainer);for(var attr in feature.attributes){var attrText=this.createTextNode(feature.attributes[attr]);var nodename=attr;if(attr.search(":")!=-1){nodename=attr.split(":")[1];}
var attrContainer=this.createElementNS(this.featureNS,"feature:"+nodename);attrContainer.appendChild(attrText);featureContainer.appendChild(attrContainer);}
return featureContainer;},insert:function(feature){var insertNode=this.createElementNS(this.wfsns,'wfs:Insert');insertNode.appendChild(this.createFeatureXML(feature));return insertNode;},update:function(feature){if(!feature.fid){OpenLayers.Console.userError(OpenLayers.i18n("noFID"));}
var updateNode=this.createElementNS(this.wfsns,'wfs:Update');updateNode.setAttribute("typeName",this.featurePrefix+':'+this.featureName);updateNode.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);var propertyNode=this.createElementNS(this.wfsns,'wfs:Property');var nameNode=this.createElementNS(this.wfsns,'wfs:Name');var txtNode=this.createTextNode(this.geometryName);nameNode.appendChild(txtNode);propertyNode.appendChild(nameNode);var valueNode=this.createElementNS(this.wfsns,'wfs:Value');var geometryNode=this.buildGeometryNode(feature.geometry);if(feature.layer){geometryNode.setAttribute("srsName",feature.layer.projection.getCode());}
valueNode.appendChild(geometryNode);propertyNode.appendChild(valueNode);updateNode.appendChild(propertyNode);for(var propName in feature.attributes){propertyNode=this.createElementNS(this.wfsns,'wfs:Property');nameNode=this.createElementNS(this.wfsns,'wfs:Name');nameNode.appendChild(this.createTextNode(propName));propertyNode.appendChild(nameNode);valueNode=this.createElementNS(this.wfsns,'wfs:Value');valueNode.appendChild(this.createTextNode(feature.attributes[propName]));propertyNode.appendChild(valueNode);updateNode.appendChild(propertyNode);}
var filterNode=this.createElementNS(this.ogcns,'ogc:Filter');var filterIdNode=this.createElementNS(this.ogcns,'ogc:FeatureId');filterIdNode.setAttribute("fid",feature.fid);filterNode.appendChild(filterIdNode);updateNode.appendChild(filterNode);return updateNode;},remove:function(feature){if(!feature.fid){OpenLayers.Console.userError(OpenLayers.i18n("noFID"));return false;}
var deleteNode=this.createElementNS(this.wfsns,'wfs:Delete');deleteNode.setAttribute("typeName",this.featurePrefix+':'+this.featureName);deleteNode.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);var filterNode=this.createElementNS(this.ogcns,'ogc:Filter');var filterIdNode=this.createElementNS(this.ogcns,'ogc:FeatureId');filterIdNode.setAttribute("fid",feature.fid);filterNode.appendChild(filterIdNode);deleteNode.appendChild(filterNode);return deleteNode;},destroy:function(){this.layer=null;},CLASS_NAME:"OpenLayers.Format.WFS"});OpenLayers.Layer.ArcIMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{ClientVersion:"9.2",ServiceName:''},tileSize:null,featureCoordSys:"4326",filterCoordSys:"4326",layers:null,async:true,name:"ArcIMS",isBaseLayer:true,DEFAULT_OPTIONS:{tileSize:new OpenLayers.Size(512,512),featureCoordSys:"4326",filterCoordSys:"4326",layers:null,isBaseLayer:true,async:true,name:"ArcIMS"},initialize:function(name,url,options){this.tileSize=new OpenLayers.Size(512,512);this.params=OpenLayers.Util.applyDefaults({ServiceName:options.serviceName},this.DEFAULT_PARAMS);this.options=OpenLayers.Util.applyDefaults(options,this.DEFAULT_OPTIONS);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[name,url,this.params,options]);if(this.transparent){if(!this.isBaseLayer){this.isBaseLayer=false;}
if(this.format=="image/jpeg"){this.format=OpenLayers.Util.alphaHack()?"image/gif":"image/png";}}
if(this.options.layers===null){this.options.layers=[];}},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},getURL:function(bounds){var url="";bounds=this.adjustBounds(bounds);var axlReq=new OpenLayers.Format.ArcXML(OpenLayers.Util.extend(this.options,{requesttype:"image",envelope:bounds.toArray(),tileSize:this.tileSize}));var req=new OpenLayers.Request.POST({url:this.getFullRequestString(),data:axlReq.write(),async:false});if(req!=null){var doc=req.responseXML;if(!doc||!doc.documentElement){doc=req.responseText;}
var axlResp=new OpenLayers.Format.ArcXML();var arcxml=axlResp.read(doc);url=this.getUrlOrImage(arcxml.image.output);}
return url;},getURLasync:function(bounds,scope,prop,callback){bounds=this.adjustBounds(bounds);var axlReq=new OpenLayers.Format.ArcXML(OpenLayers.Util.extend(this.options,{requesttype:"image",envelope:bounds.toArray(),tileSize:this.tileSize}));OpenLayers.Request.POST({url:this.getFullRequestString(),async:true,data:axlReq.write(),callback:function(req){var doc=req.responseXML;if(!doc||!doc.documentElement){doc=req.responseText;}
var axlResp=new OpenLayers.Format.ArcXML();var arcxml=axlResp.read(doc);scope[prop]=this.getUrlOrImage(arcxml.image.output);callback.apply(scope);},scope:this});},getUrlOrImage:function(output){var ret="";if(output.url){ret=output.url;}else if(output.data){ret="data:image/"+output.type+";base64,"+output.data;}
return ret;},setLayerQuery:function(id,querydef){for(var lyr=0;lyr<this.options.layers.length;lyr++){if(id==this.options.layers[lyr].id){this.options.layers[lyr].query=querydef;return;}}
this.options.layers.push({id:id,visible:true,query:querydef});},getFeatureInfo:function(geometry,layer,options){var buffer=options.buffer||1;var callback=options.callback||function(){};var scope=options.scope||window;var requestOptions={};OpenLayers.Util.extend(requestOptions,this.options);requestOptions.requesttype="feature";if(geometry instanceof OpenLayers.LonLat){requestOptions.polygon=null;requestOptions.envelope=[geometry.lon-buffer,geometry.lat-buffer,geometry.lon+buffer,geometry.lat+buffer];}else if(geometry instanceof OpenLayers.Geometry.Polygon){requestOptions.envelope=null;requestOptions.polygon=geometry;}
var arcxml=new OpenLayers.Format.ArcXML(requestOptions);OpenLayers.Util.extend(arcxml.request.get_feature,options);arcxml.request.get_feature.layer=layer.id;if(typeof layer.query.accuracy=="number"){arcxml.request.get_feature.query.accuracy=layer.query.accuracy;}else{var mapCenter=this.map.getCenter();var viewPx=this.map.getViewPortPxFromLonLat(mapCenter);viewPx.x++;var mapOffCenter=this.map.getLonLatFromPixel(viewPx);arcxml.request.get_feature.query.accuracy=mapOffCenter.lon-mapCenter.lon;}
arcxml.request.get_feature.query.where=layer.query.where;arcxml.request.get_feature.query.spatialfilter.relation="area_intersection";OpenLayers.Request.POST({url:this.getFullRequestString({'CustomService':'Query'}),data:arcxml.write(),callback:function(request){var response=arcxml.parseResponse(request.responseText);if(!arcxml.iserror()){callback.call(scope,response.features);}else{callback.call(scope,null);}}});},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},CLASS_NAME:"OpenLayers.Layer.ArcIMS"});OpenLayers.Format.GML.v2=OpenLayers.Class(OpenLayers.Format.GML.Base,{schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",initialize:function(options){OpenLayers.Format.GML.Base.prototype.initialize.apply(this,[options]);},readers:{"gml":OpenLayers.Util.applyDefaults({"outerBoundaryIs":function(node,container){var obj={};this.readChildNodes(node,obj);container.outer=obj.components[0];},"innerBoundaryIs":function(node,container){var obj={};this.readChildNodes(node,obj);container.inner.push(obj.components[0]);},"Box":function(node,container){var obj={};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
var min=obj.points[0];var max=obj.points[1];container.components.push(new OpenLayers.Bounds(min.x,min.y,max.x,max.y));}},OpenLayers.Format.GML.Base.prototype.readers["gml"]),"feature":OpenLayers.Format.GML.Base.prototype.readers["feature"],"wfs":OpenLayers.Format.GML.Base.prototype.readers["wfs"]},write:function(features){var name;if(features instanceof Array){name="wfs:FeatureCollection";}else{name="gml:featureMember";}
var root=this.writeNode(name,features);this.setAttributeNS(root,this.namespaces["xsi"],"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},writers:{"gml":OpenLayers.Util.applyDefaults({"Point":function(geometry){var node=this.createElementNSPlus("gml:Point");this.writeNode("coordinates",[geometry],node);return node;},"coordinates":function(points){var numPoints=points.length;var parts=new Array(numPoints);var point;for(var i=0;i<numPoints;++i){point=points[i];if(this.xy){parts[i]=point.x+","+point.y;}else{parts[i]=point.y+","+point.x;}
if(point.z!=undefined){parts[i]+=","+point.z;}}
return this.createElementNSPlus("gml:coordinates",{attributes:{decimal:".",cs:",",ts:" "},value:(numPoints==1)?parts[0]:parts.join(" ")});},"LineString":function(geometry){var node=this.createElementNSPlus("gml:LineString");this.writeNode("coordinates",geometry.components,node);return node;},"Polygon":function(geometry){var node=this.createElementNSPlus("gml:Polygon");this.writeNode("outerBoundaryIs",geometry.components[0],node);for(var i=1;i<geometry.components.length;++i){this.writeNode("innerBoundaryIs",geometry.components[i],node);}
return node;},"outerBoundaryIs":function(ring){var node=this.createElementNSPlus("gml:outerBoundaryIs");this.writeNode("LinearRing",ring,node);return node;},"innerBoundaryIs":function(ring){var node=this.createElementNSPlus("gml:innerBoundaryIs");this.writeNode("LinearRing",ring,node);return node;},"LinearRing":function(ring){var node=this.createElementNSPlus("gml:LinearRing");this.writeNode("coordinates",ring.components,node);return node;},"Box":function(bounds){var node=this.createElementNSPlus("gml:Box");this.writeNode("coordinates",[{x:bounds.left,y:bounds.bottom},{x:bounds.right,y:bounds.top}],node);if(this.srsName){node.setAttribute("srsName",this.srsName);}
return node;}},OpenLayers.Format.GML.Base.prototype.writers["gml"]),"feature":OpenLayers.Format.GML.Base.prototype.writers["feature"],"wfs":OpenLayers.Format.GML.Base.prototype.writers["wfs"]},CLASS_NAME:"OpenLayers.Format.GML.v2"});OpenLayers.Format.GML.v3=OpenLayers.Class(OpenLayers.Format.GML.Base,{schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd",curve:false,multiCurve:true,surface:false,multiSurface:true,initialize:function(options){OpenLayers.Format.GML.Base.prototype.initialize.apply(this,[options]);},readers:{"gml":OpenLayers.Util.applyDefaults({"featureMembers":function(node,obj){this.readChildNodes(node,obj);},"Curve":function(node,container){var obj={points:[]};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
container.components.push(new OpenLayers.Geometry.LineString(obj.points));},"segments":function(node,obj){this.readChildNodes(node,obj);},"LineStringSegment":function(node,container){var obj={};this.readChildNodes(node,obj);if(obj.points){Array.prototype.push.apply(container.points,obj.points);}},"pos":function(node,obj){var str=this.getChildValue(node).replace(this.regExes.trimSpace,"");var coords=str.split(this.regExes.splitSpace);var point;if(this.xy){point=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{point=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}
obj.points=[point];},"posList":function(node,obj){var str=this.getChildValue(node).replace(this.regExes.trimSpace,"");var coords=str.split(this.regExes.splitSpace);var dim=parseInt(node.getAttribute("dimension"))||2;var j,x,y,z;var numPoints=coords.length/dim;var points=new Array(numPoints);for(var i=0,len=coords.length;i<len;i+=dim){x=coords[i];y=coords[i+1];z=(dim==2)?undefined:coords[i+2];if(this.xy){points[i/dim]=new OpenLayers.Geometry.Point(x,y,z);}else{points[i/dim]=new OpenLayers.Geometry.Point(y,x,z);}}
obj.points=points;},"Surface":function(node,obj){this.readChildNodes(node,obj);},"patches":function(node,obj){this.readChildNodes(node,obj);},"PolygonPatch":function(node,obj){this.readers.gml.Polygon.apply(this,[node,obj]);},"exterior":function(node,container){var obj={};this.readChildNodes(node,obj);container.outer=obj.components[0];},"interior":function(node,container){var obj={};this.readChildNodes(node,obj);container.inner.push(obj.components[0]);},"MultiCurve":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);if(obj.components.length>0){container.components=[new OpenLayers.Geometry.MultiLineString(obj.components)];}},"curveMember":function(node,obj){this.readChildNodes(node,obj);},"MultiSurface":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);if(obj.components.length>0){container.components=[new OpenLayers.Geometry.MultiPolygon(obj.components)];}},"surfaceMember":function(node,obj){this.readChildNodes(node,obj);},"surfaceMembers":function(node,obj){this.readChildNodes(node,obj);},"pointMembers":function(node,obj){this.readChildNodes(node,obj);},"lineStringMembers":function(node,obj){this.readChildNodes(node,obj);},"polygonMembers":function(node,obj){this.readChildNodes(node,obj);},"geometryMembers":function(node,obj){this.readChildNodes(node,obj);},"Envelope":function(node,container){var obj={points:new Array(2)};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
var min=obj.points[0];var max=obj.points[1];container.components.push(new OpenLayers.Bounds(min.x,min.y,max.x,max.y));},"lowerCorner":function(node,container){var obj={};this.readers.gml.pos.apply(this,[node,obj]);container.points[0]=obj.points[0];},"upperCorner":function(node,container){var obj={};this.readers.gml.pos.apply(this,[node,obj]);container.points[1]=obj.points[0];}},OpenLayers.Format.GML.Base.prototype.readers["gml"]),"feature":OpenLayers.Format.GML.Base.prototype.readers["feature"],"wfs":OpenLayers.Format.GML.Base.prototype.readers["wfs"]},write:function(features){var name;if(features instanceof Array){name="featureMembers";}else{name="featureMember";}
var root=this.writeNode("gml:"+name,features);this.setAttributeNS(root,this.namespaces["xsi"],"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},writers:{"gml":OpenLayers.Util.applyDefaults({"featureMembers":function(features){var node=this.createElementNSPlus("gml:featureMembers");for(var i=0,len=features.length;i<len;++i){this.writeNode("feature:_typeName",features[i],node);}
return node;},"Point":function(geometry){var node=this.createElementNSPlus("gml:Point");this.writeNode("pos",geometry,node);return node;},"pos":function(point){var pos=(this.xy)?(point.x+" "+point.y):(point.y+" "+point.x);return this.createElementNSPlus("gml:pos",{value:pos});},"LineString":function(geometry){var node=this.createElementNSPlus("gml:LineString");this.writeNode("posList",geometry.components,node);return node;},"Curve":function(geometry){var node=this.createElementNSPlus("gml:Curve");this.writeNode("segments",geometry,node);return node;},"segments":function(geometry){var node=this.createElementNSPlus("gml:segments");this.writeNode("LineStringSegment",geometry,node);return node;},"LineStringSegment":function(geometry){var node=this.createElementNSPlus("gml:LineStringSegment");this.writeNode("posList",geometry.components,node);return node;},"posList":function(points){var len=points.length;var parts=new Array(len);var point;for(var i=0;i<len;++i){point=points[i];if(this.xy){parts[i]=point.x+" "+point.y;}else{parts[i]=point.y+" "+point.x;}}
return this.createElementNSPlus("gml:posList",{value:parts.join(" ")});},"Surface":function(geometry){var node=this.createElementNSPlus("gml:Surface");this.writeNode("patches",geometry,node);return node;},"patches":function(geometry){var node=this.createElementNSPlus("gml:patches");this.writeNode("PolygonPatch",geometry,node);return node;},"PolygonPatch":function(geometry){var node=this.createElementNSPlus("gml:PolygonPatch",{attributes:{interpolation:"planar"}});this.writeNode("exterior",geometry.components[0],node);for(var i=1,len=geometry.components.length;i<len;++i){this.writeNode("interior",geometry.components[i],node);}
return node;},"Polygon":function(geometry){var node=this.createElementNSPlus("gml:Polygon");this.writeNode("exterior",geometry.components[0],node);for(var i=1,len=geometry.components.length;i<len;++i){this.writeNode("interior",geometry.components[i],node);}
return node;},"exterior":function(ring){var node=this.createElementNSPlus("gml:exterior");this.writeNode("LinearRing",ring,node);return node;},"interior":function(ring){var node=this.createElementNSPlus("gml:interior");this.writeNode("LinearRing",ring,node);return node;},"LinearRing":function(ring){var node=this.createElementNSPlus("gml:LinearRing");this.writeNode("posList",ring.components,node);return node;},"MultiCurve":function(geometry){var node=this.createElementNSPlus("gml:MultiCurve");for(var i=0,len=geometry.components.length;i<len;++i){this.writeNode("curveMember",geometry.components[i],node);}
return node;},"curveMember":function(geometry){var node=this.createElementNSPlus("gml:curveMember");if(this.curve){this.writeNode("Curve",geometry,node);}else{this.writeNode("LineString",geometry,node);}
return node;},"MultiSurface":function(geometry){var node=this.createElementNSPlus("gml:MultiSurface");for(var i=0,len=geometry.components.length;i<len;++i){this.writeNode("surfaceMember",geometry.components[i],node);}
return node;},"surfaceMember":function(polygon){var node=this.createElementNSPlus("gml:surfaceMember");if(this.surface){this.writeNode("Surface",polygon,node);}else{this.writeNode("Polygon",polygon,node);}
return node;},"Envelope":function(bounds){var node=this.createElementNSPlus("gml:Envelope");this.writeNode("lowerCorner",bounds,node);this.writeNode("upperCorner",bounds,node);if(this.srsName){node.setAttribute("srsName",this.srsName);}
return node;},"lowerCorner":function(bounds){var pos=(this.xy)?(bounds.left+" "+bounds.bottom):(bounds.bottom+" "+bounds.left);return this.createElementNSPlus("gml:lowerCorner",{value:pos});},"upperCorner":function(bounds){var pos=(this.xy)?(bounds.right+" "+bounds.top):(bounds.top+" "+bounds.right);return this.createElementNSPlus("gml:upperCorner",{value:pos});}},OpenLayers.Format.GML.Base.prototype.writers["gml"]),"feature":OpenLayers.Format.GML.Base.prototype.writers["feature"],"wfs":OpenLayers.Format.GML.Base.prototype.writers["wfs"]},setGeometryTypes:function(){this.geometryTypes={"OpenLayers.Geometry.Point":"Point","OpenLayers.Geometry.MultiPoint":"MultiPoint","OpenLayers.Geometry.LineString":(this.curve===true)?"Curve":"LineString","OpenLayers.Geometry.MultiLineString":(this.multiCurve===false)?"MultiLineString":"MultiCurve","OpenLayers.Geometry.Polygon":(this.surface===true)?"Surface":"Polygon","OpenLayers.Geometry.MultiPolygon":(this.multiSurface===false)?"MultiPolygon":"MultiSurface","OpenLayers.Geometry.Collection":"GeometryCollection"};},CLASS_NAME:"OpenLayers.Format.GML.v3"});OpenLayers.Format.Filter.v1_0_0=OpenLayers.Class(OpenLayers.Format.GML.v2,OpenLayers.Format.Filter.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",initialize:function(options){OpenLayers.Format.GML.v2.prototype.initialize.apply(this,[options]);},readers:{"ogc":OpenLayers.Util.applyDefaults({"PropertyIsEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsNotEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.NOT_EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);}},OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),"gml":OpenLayers.Format.GML.v2.prototype.readers["gml"],"feature":OpenLayers.Format.GML.v2.prototype.readers["feature"]},writers:{"ogc":OpenLayers.Util.applyDefaults({"PropertyIsEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsEqualTo");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsNotEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsNotEqualTo");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"BBOX":function(filter){var node=this.createElementNSPlus("ogc:BBOX");this.writeNode("PropertyName",filter,node);var box=this.writeNode("gml:Box",filter.value,node);if(filter.projection){box.setAttribute("srsName",filter.projection);}
return node;}},OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),"gml":OpenLayers.Format.GML.v2.prototype.writers["gml"],"feature":OpenLayers.Format.GML.v2.prototype.writers["feature"]},writeSpatial:function(filter,name){var node=this.createElementNSPlus("ogc:"+name);this.writeNode("PropertyName",filter,node);var child;if(filter.value instanceof OpenLayers.Geometry){child=this.writeNode("feature:_geometry",filter.value).firstChild;}else{child=this.writeNode("gml:Box",filter.value);}
if(filter.projection){child.setAttribute("srsName",filter.projection);}
node.appendChild(child);return node;},CLASS_NAME:"OpenLayers.Format.Filter.v1_0_0"});OpenLayers.Format.Filter.v1_1_0=OpenLayers.Class(OpenLayers.Format.GML.v3,OpenLayers.Format.Filter.v1,{VERSION:"1.1.0",schemaLocation:"http://www.opengis.net/ogc/filter/1.1.0/filter.xsd",initialize:function(options){OpenLayers.Format.GML.v3.prototype.initialize.apply(this,[options]);},readers:{"ogc":OpenLayers.Util.applyDefaults({"PropertyIsEqualTo":function(node,obj){var matchCase=node.getAttribute("matchCase");var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,matchCase:!(matchCase==="false"||matchCase==="0")});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsNotEqualTo":function(node,obj){var matchCase=node.getAttribute("matchCase");var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.NOT_EQUAL_TO,matchCase:!(matchCase==="false"||matchCase==="0")});this.readChildNodes(node,filter);obj.filters.push(filter);}},OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),"gml":OpenLayers.Format.GML.v3.prototype.readers["gml"],"feature":OpenLayers.Format.GML.v3.prototype.readers["feature"]},writers:{"ogc":OpenLayers.Util.applyDefaults({"PropertyIsEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsEqualTo",{attributes:{matchCase:filter.matchCase}});this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsNotEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsNotEqualTo",{attributes:{matchCase:filter.matchCase}});this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"BBOX":function(filter){var node=this.createElementNSPlus("ogc:BBOX");this.writeNode("PropertyName",filter,node);var box=this.writeNode("gml:Envelope",filter.value);if(filter.projection){box.setAttribute("srsName",filter.projection);}
node.appendChild(box);return node;}},OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),"gml":OpenLayers.Format.GML.v3.prototype.writers["gml"],"feature":OpenLayers.Format.GML.v3.prototype.writers["feature"]},writeSpatial:function(filter,name){var node=this.createElementNSPlus("ogc:"+name);this.writeNode("PropertyName",filter,node);var child;if(filter.value instanceof OpenLayers.Geometry){child=this.writeNode("feature:_geometry",filter.value).firstChild;}else{child=this.writeNode("gml:Envelope",filter.value);}
if(filter.projection){child.setAttribute("srsName",filter.projection);}
node.appendChild(child);return node;},CLASS_NAME:"OpenLayers.Format.Filter.v1_1_0"});OpenLayers.Format.SLD.v1=OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0,{namespaces:{sld:"http://www.opengis.net/sld",ogc:"http://www.opengis.net/ogc",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"sld",schemaLocation:null,defaultSymbolizer:{fillColor:"#808080",fillOpacity:1,strokeColor:"#000000",strokeOpacity:1,strokeWidth:1,strokeDashstyle:"solid",pointRadius:3,graphicName:"square"},initialize:function(options){OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this,[options]);},read:function(data,options){options=OpenLayers.Util.applyDefaults(options,this.options);var sld={namedLayers:options.namedLayersAsArray===true?[]:{}};this.readChildNodes(data,sld);return sld;},readers:OpenLayers.Util.applyDefaults({"sld":{"StyledLayerDescriptor":function(node,sld){sld.version=node.getAttribute("version");this.readChildNodes(node,sld);},"Name":function(node,obj){obj.name=this.getChildValue(node);},"Title":function(node,obj){obj.title=this.getChildValue(node);},"Abstract":function(node,obj){obj.description=this.getChildValue(node);},"NamedLayer":function(node,sld){var layer={userStyles:[],namedStyles:[]};this.readChildNodes(node,layer);for(var i=0,len=layer.userStyles.length;i<len;++i){layer.userStyles[i].layerName=layer.name;}
if(sld.namedLayers instanceof Array){sld.namedLayers.push(layer);}else{sld.namedLayers[layer.name]=layer;}},"NamedStyle":function(node,layer){layer.namedStyles.push(this.getChildName(node.firstChild));},"UserStyle":function(node,layer){var obj={defaultsPerSymbolizer:true,rules:[]};this.readChildNodes(node,obj);var style=new OpenLayers.Style(this.defaultSymbolizer,obj);layer.userStyles.push(style);},"IsDefault":function(node,style){if(this.getChildValue(node)=="1"){style.isDefault=true;}},"FeatureTypeStyle":function(node,style){var obj={rules:[]};this.readChildNodes(node,obj);style.rules=obj.rules;},"Rule":function(node,obj){var rule=new OpenLayers.Rule();this.readChildNodes(node,rule);obj.rules.push(rule);},"ElseFilter":function(node,rule){rule.elseFilter=true;},"MinScaleDenominator":function(node,rule){rule.minScaleDenominator=parseFloat(this.getChildValue(node));},"MaxScaleDenominator":function(node,rule){rule.maxScaleDenominator=parseFloat(this.getChildValue(node));},"TextSymbolizer":function(node,rule){var symbolizer=rule.symbolizer["Text"]||{};this.readChildNodes(node,symbolizer);rule.symbolizer["Text"]=symbolizer;},"Label":function(node,symbolizer){var obj={};this.readChildNodes(node,obj);if(obj.property){symbolizer.label="${"+obj.property+"}";}else{var value=this.readOgcExpression(node);if(value){symbolizer.label=value;}}},"Font":function(node,symbolizer){this.readChildNodes(node,symbolizer);},"Halo":function(node,symbolizer){var obj={};this.readChildNodes(node,obj);symbolizer.haloRadius=obj.haloRadius;symbolizer.haloColor=obj.fillColor;symbolizer.haloOpacity=obj.fillOpacity;},"Radius":function(node,symbolizer){var radius=this.readOgcExpression(node);if(radius!=null){symbolizer.haloRadius=radius;}},"LineSymbolizer":function(node,rule){var symbolizer=rule.symbolizer["Line"]||{};this.readChildNodes(node,symbolizer);rule.symbolizer["Line"]=symbolizer;},"PolygonSymbolizer":function(node,rule){var symbolizer=rule.symbolizer["Polygon"]||{};this.readChildNodes(node,symbolizer);rule.symbolizer["Polygon"]=symbolizer;},"PointSymbolizer":function(node,rule){var symbolizer=rule.symbolizer["Point"]||{};this.readChildNodes(node,symbolizer);rule.symbolizer["Point"]=symbolizer;},"Stroke":function(node,symbolizer){symbolizer.stroke=true;this.readChildNodes(node,symbolizer);},"Fill":function(node,symbolizer){symbolizer.fill=true;this.readChildNodes(node,symbolizer);},"CssParameter":function(node,symbolizer){var cssProperty=node.getAttribute("name");var symProperty=this.cssMap[cssProperty];if(symProperty){var value=this.readOgcExpression(node);if(value){symbolizer[symProperty]=value;}}},"Graphic":function(node,symbolizer){symbolizer.graphic=true;var graphic={};this.readChildNodes(node,graphic);var properties=["strokeColor","strokeWidth","strokeOpacity","strokeLinecap","fillColor","fillOpacity","graphicName","rotation","graphicFormat"];var prop,value;for(var i=0,len=properties.length;i<len;++i){prop=properties[i];value=graphic[prop];if(value!=undefined){symbolizer[prop]=value;}}
if(graphic.opacity!=undefined){symbolizer.graphicOpacity=graphic.opacity;}
if(graphic.size!=undefined){symbolizer.pointRadius=graphic.size/2;}
if(graphic.href!=undefined){symbolizer.externalGraphic=graphic.href;}
if(graphic.rotation!=undefined){symbolizer.rotation=graphic.rotation;}},"ExternalGraphic":function(node,graphic){this.readChildNodes(node,graphic);},"Mark":function(node,graphic){this.readChildNodes(node,graphic);},"WellKnownName":function(node,graphic){graphic.graphicName=this.getChildValue(node);},"Opacity":function(node,obj){var opacity=this.readOgcExpression(node);if(opacity){obj.opacity=opacity;}},"Size":function(node,obj){var size=this.readOgcExpression(node);if(size){obj.size=size;}},"Rotation":function(node,obj){var rotation=this.readOgcExpression(node);if(rotation){obj.rotation=rotation;}},"OnlineResource":function(node,obj){obj.href=this.getAttributeNS(node,this.namespaces.xlink,"href");},"Format":function(node,graphic){graphic.graphicFormat=this.getChildValue(node);}}},OpenLayers.Format.Filter.v1_0_0.prototype.readers),cssMap:{"stroke":"strokeColor","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","stroke-linecap":"strokeLinecap","stroke-dasharray":"strokeDashstyle","fill":"fillColor","fill-opacity":"fillOpacity","font-family":"fontFamily","font-size":"fontSize","font-weight":"fontWeight","font-style":"fontStyle"},getCssProperty:function(sym){var css=null;for(var prop in this.cssMap){if(this.cssMap[prop]==sym){css=prop;break;}}
return css;},getGraphicFormat:function(href){var format,regex;for(var key in this.graphicFormats){if(this.graphicFormats[key].test(href)){format=key;break;}}
return format||this.defautlGraphicFormat;},defaultGraphicFormat:"image/png",graphicFormats:{"image/jpeg":/\.jpe?g$/i,"image/gif":/\.gif$/i,"image/png":/\.png$/i},write:function(sld){return this.writers.sld.StyledLayerDescriptor.apply(this,[sld]);},writers:OpenLayers.Util.applyDefaults({"sld":{"StyledLayerDescriptor":function(sld){var root=this.createElementNSPlus("StyledLayerDescriptor",{attributes:{"version":this.VERSION,"xsi:schemaLocation":this.schemaLocation}});if(sld.name){this.writeNode("Name",sld.name,root);}
if(sld.title){this.writeNode("Title",sld.title,root);}
if(sld.description){this.writeNode("Abstract",sld.description,root);}
if(sld.namedLayers instanceof Array){for(var i=0,len=sld.namedLayers.length;i<len;++i){this.writeNode("NamedLayer",sld.namedLayers[i],root);}}else{for(var name in sld.namedLayers){this.writeNode("NamedLayer",sld.namedLayers[name],root);}}
return root;},"Name":function(name){return this.createElementNSPlus("Name",{value:name});},"Title":function(title){return this.createElementNSPlus("Title",{value:title});},"Abstract":function(description){return this.createElementNSPlus("Abstract",{value:description});},"NamedLayer":function(layer){var node=this.createElementNSPlus("NamedLayer");this.writeNode("Name",layer.name,node);if(layer.namedStyles){for(var i=0,len=layer.namedStyles.length;i<len;++i){this.writeNode("NamedStyle",layer.namedStyles[i],node);}}
if(layer.userStyles){for(var i=0,len=layer.userStyles.length;i<len;++i){this.writeNode("UserStyle",layer.userStyles[i],node);}}
return node;},"NamedStyle":function(name){var node=this.createElementNSPlus("NamedStyle");this.writeNode("Name",name,node);return node;},"UserStyle":function(style){var node=this.createElementNSPlus("UserStyle");if(style.name){this.writeNode("Name",style.name,node);}
if(style.title){this.writeNode("Title",style.title,node);}
if(style.description){this.writeNode("Abstract",style.description,node);}
if(style.isDefault){this.writeNode("IsDefault",style.isDefault,node);}
this.writeNode("FeatureTypeStyle",style,node);return node;},"IsDefault":function(bool){return this.createElementNSPlus("IsDefault",{value:(bool)?"1":"0"});},"FeatureTypeStyle":function(style){var node=this.createElementNSPlus("FeatureTypeStyle");for(var i=0,len=style.rules.length;i<len;++i){this.writeNode("Rule",style.rules[i],node);}
return node;},"Rule":function(rule){var node=this.createElementNSPlus("Rule");if(rule.name){this.writeNode("Name",rule.name,node);}
if(rule.title){this.writeNode("Title",rule.title,node);}
if(rule.description){this.writeNode("Abstract",rule.description,node);}
if(rule.elseFilter){this.writeNode("ElseFilter",null,node);}else if(rule.filter){this.writeNode("ogc:Filter",rule.filter,node);}
if(rule.minScaleDenominator!=undefined){this.writeNode("MinScaleDenominator",rule.minScaleDenominator,node);}
if(rule.maxScaleDenominator!=undefined){this.writeNode("MaxScaleDenominator",rule.maxScaleDenominator,node);}
var types=OpenLayers.Style.SYMBOLIZER_PREFIXES;var type,symbolizer;for(var i=0,len=types.length;i<len;++i){type=types[i];symbolizer=rule.symbolizer[type];if(symbolizer){this.writeNode(type+"Symbolizer",symbolizer,node);}}
return node;},"ElseFilter":function(){return this.createElementNSPlus("ElseFilter");},"MinScaleDenominator":function(scale){return this.createElementNSPlus("MinScaleDenominator",{value:scale});},"MaxScaleDenominator":function(scale){return this.createElementNSPlus("MaxScaleDenominator",{value:scale});},"LineSymbolizer":function(symbolizer){var node=this.createElementNSPlus("LineSymbolizer");this.writeNode("Stroke",symbolizer,node);return node;},"Stroke":function(symbolizer){var node=this.createElementNSPlus("Stroke");if(symbolizer.strokeColor!=undefined){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"strokeColor"},node);}
if(symbolizer.strokeOpacity!=undefined){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"strokeOpacity"},node);}
if(symbolizer.strokeWidth!=undefined){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"strokeWidth"},node);}
return node;},"CssParameter":function(obj){return this.createElementNSPlus("CssParameter",{attributes:{name:this.getCssProperty(obj.key)},value:obj.symbolizer[obj.key]});},"TextSymbolizer":function(symbolizer){var node=this.createElementNSPlus("TextSymbolizer");if(symbolizer.label!=null){this.writeNode("Label",symbolizer.label,node);}
if(symbolizer.fontFamily!=null||symbolizer.fontSize!=null||symbolizer.fontWeight!=null||symbolizer.fontStyle!=null){this.writeNode("Font",symbolizer,node);}
if(symbolizer.haloRadius!=null||symbolizer.haloColor!=null||symbolizer.haloOpacity!=null){this.writeNode("Halo",symbolizer,node);}
if(symbolizer.fillColor!=null||symbolizer.fillOpacity!=null){this.writeNode("Fill",symbolizer,node);}
return node;},"Font":function(symbolizer){var node=this.createElementNSPlus("Font");if(symbolizer.fontFamily){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fontFamily"},node);}
if(symbolizer.fontSize){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fontSize"},node);}
if(symbolizer.fontWeight){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fontWeight"},node);}
if(symbolizer.fontStyle){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fontStyle"},node);}
return node;},"Label":function(label){var node=this.createElementNSPlus("Label");var tokens=label.split("${");node.appendChild(this.createTextNode(tokens[0]));var item,last;for(var i=1,len=tokens.length;i<len;i++){item=tokens[i];last=item.indexOf("}");if(last>0){this.writeNode("ogc:PropertyName",{property:item.substring(0,last)},node);node.appendChild(this.createTextNode(item.substring(++last)));}else{node.appendChild(this.createTextNode("${"+item));}}
return node;},"Halo":function(symbolizer){var node=this.createElementNSPlus("Halo");if(symbolizer.haloRadius){this.writeNode("Radius",symbolizer.haloRadius,node);}
if(symbolizer.haloColor||symbolizer.haloOpacity){this.writeNode("Fill",{fillColor:symbolizer.haloColor,fillOpacity:symbolizer.haloOpacity},node);}
return node;},"Radius":function(value){return node=this.createElementNSPlus("Radius",{value:value});},"PolygonSymbolizer":function(symbolizer){var node=this.createElementNSPlus("PolygonSymbolizer");if(symbolizer.fillColor!=undefined||symbolizer.fillOpacity!=undefined){this.writeNode("Fill",symbolizer,node);}
if(symbolizer.strokeWidth!=undefined||symbolizer.strokeColor!=undefined||symbolizer.strokeOpacity!=undefined||symbolizer.strokeDashstyle!=undefined){this.writeNode("Stroke",symbolizer,node);}
return node;},"Fill":function(symbolizer){var node=this.createElementNSPlus("Fill");if(symbolizer.fillColor){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fillColor"},node);}
if(symbolizer.fillOpacity!=null){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fillOpacity"},node);}
return node;},"PointSymbolizer":function(symbolizer){var node=this.createElementNSPlus("PointSymbolizer");this.writeNode("Graphic",symbolizer,node);return node;},"Graphic":function(symbolizer){var node=this.createElementNSPlus("Graphic");if(symbolizer.externalGraphic!=undefined){this.writeNode("ExternalGraphic",symbolizer,node);}else{this.writeNode("Mark",symbolizer,node);}
if(symbolizer.graphicOpacity!=undefined){this.writeNode("Opacity",symbolizer.graphicOpacity,node);}
if(symbolizer.pointRadius!=undefined){this.writeNode("Size",symbolizer.pointRadius*2,node);}
if(symbolizer.rotation!=undefined){this.writeNode("Rotation",symbolizer.rotation,node);}
return node;},"ExternalGraphic":function(symbolizer){var node=this.createElementNSPlus("ExternalGraphic");this.writeNode("OnlineResource",symbolizer.externalGraphic,node);var format=symbolizer.graphicFormat||this.getGraphicFormat(symbolizer.externalGraphic);this.writeNode("Format",format,node);return node;},"Mark":function(symbolizer){var node=this.createElementNSPlus("Mark");if(symbolizer.graphicName){this.writeNode("WellKnownName",symbolizer.graphicName,node);}
this.writeNode("Fill",symbolizer,node);this.writeNode("Stroke",symbolizer,node);return node;},"WellKnownName":function(name){return this.createElementNSPlus("WellKnownName",{value:name});},"Opacity":function(value){return this.createElementNSPlus("Opacity",{value:value});},"Size":function(value){return this.createElementNSPlus("Size",{value:value});},"Rotation":function(value){return this.createElementNSPlus("Rotation",{value:value});},"OnlineResource":function(href){return this.createElementNSPlus("OnlineResource",{attributes:{"xlink:type":"simple","xlink:href":href}});},"Format":function(format){return this.createElementNSPlus("Format",{value:format});}}},OpenLayers.Format.Filter.v1_0_0.prototype.writers),CLASS_NAME:"OpenLayers.Format.SLD.v1"});OpenLayers.Format.WFST.v1_0_0=OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0,OpenLayers.Format.WFST.v1,{version:"1.0.0",schemaLocations:{"wfs":"http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"},initialize:function(options){OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this,[options]);OpenLayers.Format.WFST.v1.prototype.initialize.apply(this,[options]);},readers:{"wfs":OpenLayers.Util.applyDefaults({"WFS_TransactionResponse":function(node,obj){obj.insertIds=[];obj.success=false;this.readChildNodes(node,obj);},"InsertResult":function(node,container){var obj={fids:[]};this.readChildNodes(node,obj);container.insertIds.push(obj.fids[0]);},"TransactionResult":function(node,obj){this.readChildNodes(node,obj);},"Status":function(node,obj){this.readChildNodes(node,obj);},"SUCCESS":function(node,obj){obj.success=true;}},OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),"gml":OpenLayers.Format.GML.v2.prototype.readers["gml"],"feature":OpenLayers.Format.GML.v2.prototype.readers["feature"],"ogc":OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"]},writers:{"wfs":OpenLayers.Util.applyDefaults({"Query":function(options){options=OpenLayers.Util.extend({featureNS:this.featureNS,featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName},options);var node=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(options.featureNS?options.featurePrefix+":":"")+
options.featureType}});if(options.featureNS){node.setAttribute("xmlns:"+options.featurePrefix,options.featureNS);}
if(options.propertyNames){for(var i=0,len=options.propertyNames.length;i<len;i++){this.writeNode("ogc:PropertyName",{property:options.propertyNames[i]},node);}}
if(options.filter){this.setFilterProperty(options.filter);this.writeNode("ogc:Filter",options.filter,node);}
return node;}},OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),"gml":OpenLayers.Format.GML.v2.prototype.writers["gml"],"feature":OpenLayers.Format.GML.v2.prototype.writers["feature"],"ogc":OpenLayers.Format.Filter.v1_0_0.prototype.writers["ogc"]},CLASS_NAME:"OpenLayers.Format.WFST.v1_0_0"});OpenLayers.Format.WFST.v1_1_0=OpenLayers.Class(OpenLayers.Format.Filter.v1_1_0,OpenLayers.Format.WFST.v1,{version:"1.1.0",schemaLocations:{"wfs":"http://schemas.opengis.net/wfs/1.1.0/wfs.xsd"},initialize:function(options){OpenLayers.Format.Filter.v1_1_0.prototype.initialize.apply(this,[options]);OpenLayers.Format.WFST.v1.prototype.initialize.apply(this,[options]);},readers:{"wfs":OpenLayers.Util.applyDefaults({"TransactionResponse":function(node,obj){obj.insertIds=[];obj.success=false;this.readChildNodes(node,obj);},"TransactionSummary":function(node,obj){obj.success=true;},"InsertResults":function(node,obj){this.readChildNodes(node,obj);},"Feature":function(node,container){var obj={fids:[]};this.readChildNodes(node,obj);container.insertIds.push(obj.fids[0]);}},OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),"gml":OpenLayers.Format.GML.v3.prototype.readers["gml"],"feature":OpenLayers.Format.GML.v3.prototype.readers["feature"],"ogc":OpenLayers.Format.Filter.v1_1_0.prototype.readers["ogc"]},writers:{"wfs":OpenLayers.Util.applyDefaults({"Query":function(options){options=OpenLayers.Util.extend({featureNS:this.featureNS,featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName},options);var node=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(options.featureNS?options.featurePrefix+":":"")+
options.featureType,srsName:options.srsName}});if(options.featureNS){node.setAttribute("xmlns:"+options.featurePrefix,options.featureNS);}
if(options.propertyNames){for(var i=0,len=options.propertyNames.length;i<len;i++){this.writeNode("wfs:PropertyName",{property:options.propertyNames[i]},node);}}
if(options.filter){this.setFilterProperty(options.filter);this.writeNode("ogc:Filter",options.filter,node);}
return node;},"PropertyName":function(obj){return this.createElementNSPlus("wfs:PropertyName",{value:obj.property});}},OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),"gml":OpenLayers.Format.GML.v3.prototype.writers["gml"],"feature":OpenLayers.Format.GML.v3.prototype.writers["feature"],"ogc":OpenLayers.Format.Filter.v1_1_0.prototype.writers["ogc"]},CLASS_NAME:"OpenLayers.Format.WFST.v1_1_0"});OpenLayers.Format.SLD.v1_0_0=OpenLayers.Class(OpenLayers.Format.SLD.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd",initialize:function(options){OpenLayers.Format.SLD.v1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.SLD.v1_0_0"});OpenLayers.Protocol.WFS.v1_0_0=OpenLayers.Class(OpenLayers.Protocol.WFS.v1,{version:"1.0.0",CLASS_NAME:"OpenLayers.Protocol.WFS.v1_0_0"});OpenLayers.Protocol.WFS.v1_1_0=OpenLayers.Class(OpenLayers.Protocol.WFS.v1,{version:"1.1.0",CLASS_NAME:"OpenLayers.Protocol.WFS.v1_1_0"});/*
CryptoJS v3.0.2
code.google.com/p/crypto-js
(c) 2009-2012 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
/**
 * CryptoJS core components.
 */
var CryptoJS = CryptoJS || (function (Math, undefined) {
    /**
     * CryptoJS namespace.
     */
    var C = {};

    /**
     * Library namespace.
     */
    var C_lib = C.lib = {};

    /**
     * Base object for prototypal inheritance.
     */
    var Base = C_lib.Base = (function () {
        function F() {}

        return {
            /**
             * Creates a new object that inherits from this object.
             *
             * @param {Object} overrides Properties to copy into the new object.
             *
             * @return {Object} The new object.
             *
             * @static
             *
             * @example
             *
             *     var MyType = CryptoJS.lib.Base.extend({
             *         field: 'value',
             *
             *         method: function () {
             *         }
             *     });
             */
            extend: function (overrides) {
                // Spawn
                F.prototype = this;
                var subtype = new F();

                // Augment
                if (overrides) {
                    subtype.mixIn(overrides);
                }

                // Reference supertype
                subtype.$super = this;

                return subtype;
            },

            /**
             * Extends this object and runs the init method.
             * Arguments to create() will be passed to init().
             *
             * @return {Object} The new object.
             *
             * @static
             *
             * @example
             *
             *     var instance = MyType.create();
             */
            create: function () {
                var instance = this.extend();
                instance.init.apply(instance, arguments);

                return instance;
            },

            /**
             * Initializes a newly created object.
             * Override this method to add some logic when your objects are created.
             *
             * @example
             *
             *     var MyType = CryptoJS.lib.Base.extend({
             *         init: function () {
             *             // ...
             *         }
             *     });
             */
            init: function () {
            },

            /**
             * Copies properties into this object.
             *
             * @param {Object} properties The properties to mix in.
             *
             * @example
             *
             *     MyType.mixIn({
             *         field: 'value'
             *     });
             */
            mixIn: function (properties) {
                for (var propertyName in properties) {
                    if (properties.hasOwnProperty(propertyName)) {
                        this[propertyName] = properties[propertyName];
                    }
                }

                // IE won't copy toString using the loop above
                // Other non-enumerable properties are:
                //   hasOwnProperty, isPrototypeOf, propertyIsEnumerable,
                //   toLocaleString, valueOf
                if (properties.hasOwnProperty('toString')) {
                    this.toString = properties.toString;
                }
            },

            /**
             * Creates a copy of this object.
             *
             * @return {Object} The clone.
             *
             * @example
             *
             *     var clone = instance.clone();
             */
            clone: function () {
                return this.$super.extend(this);
            }
        };
    }());

    /**
     * An array of 32-bit words.
     *
     * @property {Array} words The array of 32-bit words.
     * @property {number} sigBytes The number of significant bytes in this word array.
     */
    var WordArray = C_lib.WordArray = Base.extend({
        /**
         * Initializes a newly created word array.
         *
         * @param {Array} words (Optional) An array of 32-bit words.
         * @param {number} sigBytes (Optional) The number of significant bytes in the words.
         *
         * @example
         *
         *     var wordArray = CryptoJS.lib.WordArray.create();
         *     var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]);
         *     var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6);
         */
        init: function (words, sigBytes) {
            words = this.words = words || [];

            if (sigBytes != undefined) {
                this.sigBytes = sigBytes;
            } else {
                this.sigBytes = words.length * 4;
            }
        },

        /**
         * Converts this word array to a string.
         *
         * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex
         *
         * @return {string} The stringified word array.
         *
         * @example
         *
         *     var string = wordArray + '';
         *     var string = wordArray.toString();
         *     var string = wordArray.toString(CryptoJS.enc.Utf8);
         */
        toString: function (encoder) {
            return (encoder || Hex).stringify(this);
        },

        /**
         * Concatenates a word array to this word array.
         *
         * @param {WordArray} wordArray The word array to append.
         *
         * @return {WordArray} This word array.
         *
         * @example
         *
         *     wordArray1.concat(wordArray2);
         */
        concat: function (wordArray) {
            // Shortcuts
            var thisWords = this.words;
            var thatWords = wordArray.words;
            var thisSigBytes = this.sigBytes;
            var thatSigBytes = wordArray.sigBytes;

            // Clamp excess bits
            this.clamp();

            // Concat
            if (thisSigBytes % 4) {
                // Copy one byte at a time
                for (var i = 0; i < thatSigBytes; i++) {
                    var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                    thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8);
                }
            } else if (thatWords.length > 0xffff) {
                // Copy one word at a time
                for (var i = 0; i < thatSigBytes; i += 4) {
                    thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2];
                }
            } else {
                // Copy all words at once
                thisWords.push.apply(thisWords, thatWords);
            }
            this.sigBytes += thatSigBytes;

            // Chainable
            return this;
        },

        /**
         * Removes insignificant bits.
         *
         * @example
         *
         *     wordArray.clamp();
         */
        clamp: function () {
            // Shortcuts
            var words = this.words;
            var sigBytes = this.sigBytes;

            // Clamp
            words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8);
            words.length = Math.ceil(sigBytes / 4);
        },

        /**
         * Creates a copy of this word array.
         *
         * @return {WordArray} The clone.
         *
         * @example
         *
         *     var clone = wordArray.clone();
         */
        clone: function () {
            var clone = Base.clone.call(this);
            clone.words = this.words.slice(0);

            return clone;
        },

        /**
         * Creates a word array filled with random bytes.
         *
         * @param {number} nBytes The number of random bytes to generate.
         *
         * @return {WordArray} The random word array.
         *
         * @static
         *
         * @example
         *
         *     var wordArray = CryptoJS.lib.WordArray.random(16);
         */
        random: function (nBytes) {
            var words = [];
            for (var i = 0; i < nBytes; i += 4) {
                words.push((Math.random() * 0x100000000) | 0);
            }

            return WordArray.create(words, nBytes);
        }
    });

    /**
     * Encoder namespace.
     */
    var C_enc = C.enc = {};

    /**
     * Hex encoding strategy.
     */
    var Hex = C_enc.Hex = {
        /**
         * Converts a word array to a hex string.
         *
         * @param {WordArray} wordArray The word array.
         *
         * @return {string} The hex string.
         *
         * @static
         *
         * @example
         *
         *     var hexString = CryptoJS.enc.Hex.stringify(wordArray);
         */
        stringify: function (wordArray) {
            // Shortcuts
            var words = wordArray.words;
            var sigBytes = wordArray.sigBytes;

            // Convert
            var hexChars = [];
            for (var i = 0; i < sigBytes; i++) {
                var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                hexChars.push((bite >>> 4).toString(16));
                hexChars.push((bite & 0x0f).toString(16));
            }

            return hexChars.join('');
        },

        /**
         * Converts a hex string to a word array.
         *
         * @param {string} hexStr The hex string.
         *
         * @return {WordArray} The word array.
         *
         * @static
         *
         * @example
         *
         *     var wordArray = CryptoJS.enc.Hex.parse(hexString);
         */
        parse: function (hexStr) {
            // Shortcut
            var hexStrLength = hexStr.length;

            // Convert
            var words = [];
            for (var i = 0; i < hexStrLength; i += 2) {
                words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
            }

            return WordArray.create(words, hexStrLength / 2);
        }
    };

    /**
     * Latin1 encoding strategy.
     */
    var Latin1 = C_enc.Latin1 = {
        /**
         * Converts a word array to a Latin1 string.
         *
         * @param {WordArray} wordArray The word array.
         *
         * @return {string} The Latin1 string.
         *
         * @static
         *
         * @example
         *
         *     var latin1String = CryptoJS.enc.Latin1.stringify(wordArray);
         */
        stringify: function (wordArray) {
            // Shortcuts
            var words = wordArray.words;
            var sigBytes = wordArray.sigBytes;

            // Convert
            var latin1Chars = [];
            for (var i = 0; i < sigBytes; i++) {
                var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                latin1Chars.push(String.fromCharCode(bite));
            }

            return latin1Chars.join('');
        },

        /**
         * Converts a Latin1 string to a word array.
         *
         * @param {string} latin1Str The Latin1 string.
         *
         * @return {WordArray} The word array.
         *
         * @static
         *
         * @example
         *
         *     var wordArray = CryptoJS.enc.Latin1.parse(latin1String);
         */
        parse: function (latin1Str) {
            // Shortcut
            var latin1StrLength = latin1Str.length;

            // Convert
            var words = [];
            for (var i = 0; i < latin1StrLength; i++) {
                words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
            }

            return WordArray.create(words, latin1StrLength);
        }
    };

    /**
     * UTF-8 encoding strategy.
     */
    var Utf8 = C_enc.Utf8 = {
        /**
         * Converts a word array to a UTF-8 string.
         *
         * @param {WordArray} wordArray The word array.
         *
         * @return {string} The UTF-8 string.
         *
         * @static
         *
         * @example
         *
         *     var utf8String = CryptoJS.enc.Utf8.stringify(wordArray);
         */
        stringify: function (wordArray) {
            try {
                return decodeURIComponent(escape(Latin1.stringify(wordArray)));
            } catch (e) {
                throw new Error('Malformed UTF-8 data');
            }
        },

        /**
         * Converts a UTF-8 string to a word array.
         *
         * @param {string} utf8Str The UTF-8 string.
         *
         * @return {WordArray} The word array.
         *
         * @static
         *
         * @example
         *
         *     var wordArray = CryptoJS.enc.Utf8.parse(utf8String);
         */
        parse: function (utf8Str) {
            return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
        }
    };

    /**
     * Abstract buffered block algorithm template.
     * The property blockSize must be implemented in a concrete subtype.
     *
     * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0
     */
    var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({
        /**
         * Resets this block algorithm's data buffer to its initial state.
         *
         * @example
         *
         *     bufferedBlockAlgorithm.reset();
         */
        reset: function () {
            // Initial values
            this._data = WordArray.create();
            this._nDataBytes = 0;
        },

        /**
         * Adds new data to this block algorithm's buffer.
         *
         * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8.
         *
         * @example
         *
         *     bufferedBlockAlgorithm._append('data');
         *     bufferedBlockAlgorithm._append(wordArray);
         */
        _append: function (data) {
            // Convert string to WordArray, else assume WordArray already
            if (typeof data == 'string') {
                data = Utf8.parse(data);
            }

            // Append
            this._data.concat(data);
            this._nDataBytes += data.sigBytes;
        },

        /**
         * Processes available data blocks.
         * This method invokes _doProcessBlock(dataWords, offset), which must be implemented by a concrete subtype.
         *
         * @param {boolean} flush Whether all blocks and partial blocks should be processed.
         *
         * @return {WordArray} The data after processing.
         *
         * @example
         *
         *     var processedData = bufferedBlockAlgorithm._process();
         *     var processedData = bufferedBlockAlgorithm._process(!!'flush');
         */
        _process: function (flush) {
            // Shortcuts
            var data = this._data;
            var dataWords = data.words;
            var dataSigBytes = data.sigBytes;
            var blockSize = this.blockSize;
            var blockSizeBytes = blockSize * 4;

            // Count blocks ready
            var nBlocksReady = dataSigBytes / blockSizeBytes;
            if (flush) {
                // Round up to include partial blocks
                nBlocksReady = Math.ceil(nBlocksReady);
            } else {
                // Round down to include only full blocks,
                // less the number of blocks that must remain in the buffer
                nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
            }

            // Count words ready
            var nWordsReady = nBlocksReady * blockSize;

            // Count bytes ready
            var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);

            // Process blocks
            if (nWordsReady) {
                for (var offset = 0; offset < nWordsReady; offset += blockSize) {
                    // Perform concrete-algorithm logic
                    this._doProcessBlock(dataWords, offset);
                }

                // Remove processed words
                var processedWords = dataWords.splice(0, nWordsReady);
                data.sigBytes -= nBytesReady;
            }

            // Return processed words
            return WordArray.create(processedWords, nBytesReady);
        },

        /**
         * Creates a copy of this object.
         *
         * @return {Object} The clone.
         *
         * @example
         *
         *     var clone = bufferedBlockAlgorithm.clone();
         */
        clone: function () {
            var clone = Base.clone.call(this);
            clone._data = this._data.clone();

            return clone;
        },

        _minBufferSize: 0
    });

    /**
     * Abstract hasher template.
     *
     * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits)
     */
    var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({
        /**
         * Configuration options.
         */
        // cfg: Base.extend(),

        /**
         * Initializes a newly created hasher.
         *
         * @param {Object} cfg (Optional) The configuration options to use for this hash computation.
         *
         * @example
         *
         *     var hasher = CryptoJS.algo.SHA256.create();
         */
        init: function (cfg) {
            // Apply config defaults
            // this.cfg = this.cfg.extend(cfg);

            // Set initial values
            this.reset();
        },

        /**
         * Resets this hasher to its initial state.
         *
         * @example
         *
         *     hasher.reset();
         */
        reset: function () {
            // Reset data buffer
            BufferedBlockAlgorithm.reset.call(this);

            // Perform concrete-hasher logic
            this._doReset();
        },

        /**
         * Updates this hasher with a message.
         *
         * @param {WordArray|string} messageUpdate The message to append.
         *
         * @return {Hasher} This hasher.
         *
         * @example
         *
         *     hasher.update('message');
         *     hasher.update(wordArray);
         */
        update: function (messageUpdate) {
            // Append
            this._append(messageUpdate);

            // Update the hash
            this._process();

            // Chainable
            return this;
        },

        /**
         * Finalizes the hash computation.
         * Note that the finalize operation is effectively a destructive, read-once operation.
         *
         * @param {WordArray|string} messageUpdate (Optional) A final message update.
         *
         * @return {WordArray} The hash.
         *
         * @example
         *
         *     var hash = hasher.finalize();
         *     var hash = hasher.finalize('message');
         *     var hash = hasher.finalize(wordArray);
         */
        finalize: function (messageUpdate) {
            // Final message update
            if (messageUpdate) {
                this._append(messageUpdate);
            }

            // Perform concrete-hasher logic
            this._doFinalize();

            return this._hash;
        },

        /**
         * Creates a copy of this object.
         *
         * @return {Object} The clone.
         *
         * @example
         *
         *     var clone = hasher.clone();
         */
        clone: function () {
            var clone = BufferedBlockAlgorithm.clone.call(this);
            clone._hash = this._hash.clone();

            return clone;
        },

        blockSize: 512/32,

        /**
         * Creates a shortcut function to a hasher's object interface.
         *
         * @param {Hasher} hasher The hasher to create a helper for.
         *
         * @return {Function} The shortcut function.
         *
         * @static
         *
         * @example
         *
         *     var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256);
         */
        _createHelper: function (hasher) {
            return function (message, cfg) {
                return hasher.create(cfg).finalize(message);
            };
        },

        /**
         * Creates a shortcut function to the HMAC's object interface.
         *
         * @param {Hasher} hasher The hasher to use in this HMAC helper.
         *
         * @return {Function} The shortcut function.
         *
         * @static
         *
         * @example
         *
         *     var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256);
         */
        _createHmacHelper: function (hasher) {
            return function (message, key) {
                return C_algo.HMAC.create(hasher, key).finalize(message);
            };
        }
    });

    /**
     * Algorithm namespace.
     */
    var C_algo = C.algo = {};

    return C;
}(Math));
/*
CryptoJS v3.0.2
code.google.com/p/crypto-js
(c) 2009-2012 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
(function () {
    // Shortcuts
    var C = CryptoJS;
    var C_lib = C.lib;
    var WordArray = C_lib.WordArray;
    var Hasher = C_lib.Hasher;
    var C_algo = C.algo;

    // Reusable object
    var W = [];

    /**
     * SHA-1 hash algorithm.
     */
    var SHA1 = C_algo.SHA1 = Hasher.extend({
        _doReset: function () {
            this._hash = WordArray.create([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]);
        },

        _doProcessBlock: function (M, offset) {
            // Shortcut
            var H = this._hash.words;

            // Working variables
            var a = H[0];
            var b = H[1];
            var c = H[2];
            var d = H[3];
            var e = H[4];

            // Computation
            for (var i = 0; i < 80; i++) {
                if (i < 16) {
                    W[i] = M[offset + i] | 0;
                } else {
                    var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
                    W[i] = (n << 1) | (n >>> 31);
                }

                var t = ((a << 5) | (a >>> 27)) + e + W[i];
                if (i < 20) {
                    t += ((b & c) | (~b & d)) + 0x5a827999;
                } else if (i < 40) {
                    t += (b ^ c ^ d) + 0x6ed9eba1;
                } else if (i < 60) {
                    t += ((b & c) | (b & d) | (c & d)) - 0x70e44324;
                } else /* if (i < 80) */ {
                    t += (b ^ c ^ d) - 0x359d3e2a;
                }

                e = d;
                d = c;
                c = (b << 30) | (b >>> 2);
                b = a;
                a = t;
            }

            // Intermediate hash value
            H[0] = (H[0] + a) | 0;
            H[1] = (H[1] + b) | 0;
            H[2] = (H[2] + c) | 0;
            H[3] = (H[3] + d) | 0;
            H[4] = (H[4] + e) | 0;
        },

        _doFinalize: function () {
            // Shortcuts
            var data = this._data;
            var dataWords = data.words;

            var nBitsTotal = this._nDataBytes * 8;
            var nBitsLeft = data.sigBytes * 8;

            // Add padding
            dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);
            dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal;
            data.sigBytes = dataWords.length * 4;

            // Hash final blocks
            this._process();
        }
    });

    /**
     * Shortcut function to the hasher's object interface.
     *
     * @param {WordArray|string} message The message to hash.
     *
     * @return {WordArray} The hash.
     *
     * @static
     *
     * @example
     *
     *     var hash = CryptoJS.SHA1('message');
     *     var hash = CryptoJS.SHA1(wordArray);
     */
    C.SHA1 = Hasher._createHelper(SHA1);

    /**
     * Shortcut function to the HMAC's object interface.
     *
     * @param {WordArray|string} message The message to hash.
     * @param {WordArray|string} key The secret key.
     *
     * @return {WordArray} The HMAC.
     *
     * @static
     *
     * @example
     *
     *     var hmac = CryptoJS.HmacSHA1(message, key);
     */
    C.HmacSHA1 = Hasher._createHmacHelper(SHA1);
}());
/*

Copyright (c) 2008-2009, The Open Source Geospatial Foundation
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Open Source Geospatial Foundation nor the names
      of its contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

Ext.namespace("GeoExt.tree");GeoExt.tree.LayerContainer=Ext.extend(Ext.tree.AsyncTreeNode,{constructor:function(config){config=Ext.applyIf(config||{},{text:"Layers"});this.loader=config.loader instanceof GeoExt.tree.LayerLoader?config.loader:new GeoExt.tree.LayerLoader(Ext.applyIf(config.loader||{},{store:config.layerStore}));GeoExt.tree.LayerContainer.superclass.constructor.call(this,config);},recordIndexToNodeIndex:function(index){var store=this.loader.store;var count=store.getCount();var nodeCount=this.childNodes.length;var nodeIndex=-1;for(var i=count-1;i>=0;--i){if(this.loader.filter(store.getAt(i))===true){++nodeIndex;if(index===i||nodeIndex>nodeCount-1){break;}}}
return nodeIndex;},destroy:function(){delete this.layerStore;GeoExt.tree.LayerContainer.superclass.destroy.apply(this,arguments);}});Ext.tree.TreePanel.nodeTypes.gx_layercontainer=GeoExt.tree.LayerContainer;Ext.namespace("GeoExt.tree");GeoExt.tree.BaseLayerContainer=Ext.extend(GeoExt.tree.LayerContainer,{constructor:function(config){config=Ext.applyIf(config||{},{text:"Base Layer",loader:{}});config.loader=Ext.applyIf(config.loader,{baseAttrs:Ext.applyIf(config.loader.baseAttrs||{},{iconCls:'gx-tree-baselayer-icon',checkedGroup:'baselayer'}),filter:function(record){var layer=record.get("layer");return layer.displayInLayerSwitcher===true&&layer.isBaseLayer===true;}});GeoExt.tree.BaseLayerContainer.superclass.constructor.call(this,config);}});Ext.tree.TreePanel.nodeTypes.gx_baselayercontainer=GeoExt.tree.BaseLayerContainer;Ext.namespace("GeoExt.tree");GeoExt.tree.LayerParamLoader=function(config){Ext.apply(this,config);this.addEvents("beforeload","load");GeoExt.tree.LayerLoader.superclass.constructor.call(this);};Ext.extend(GeoExt.tree.LayerParamLoader,Ext.util.Observable,{param:null,delimiter:",",load:function(node,callback){if(this.fireEvent("beforeload",this,node)){while(node.firstChild){node.removeChild(node.firstChild);}
var paramValue=(node.layer instanceof OpenLayers.Layer.HTTPRequest)&&node.layer.params[this.param];if(paramValue){var items=(paramValue instanceof Array)?paramValue:paramValue.split(this.delimiter);Ext.each(items,function(item){this.addParamNode(item,node);},this);}
if(typeof callback=="function"){callback();}
this.fireEvent("load",this,node);}},addParamNode:function(paramItem,node){var child=this.createNode({layer:node.layer,param:this.param,item:paramItem,delimiter:this.delimiter});var sibling=node.item(0);if(sibling){node.insertBefore(child,sibling);}else{node.appendChild(child);}},createNode:function(attr){if(this.baseAttrs){Ext.apply(attr,this.baseAttrs);}
if(typeof attr.uiProvider=='string'){attr.uiProvider=this.uiProviders[attr.uiProvider]||eval(attr.uiProvider);}
attr.nodeType=attr.nodeType||"gx_layerparam";return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);}});Ext.namespace("GeoExt");GeoExt.SliderTip=Ext.extend(Ext.Tip,{hover:true,minWidth:10,minWidth:10,offsets:[0,-10],dragging:false,init:function(slider){slider.on({dragstart:this.onSlide,drag:this.onSlide,dragend:this.hide,destroy:this.destroy,scope:this});if(this.hover){slider.on("render",this.registerThumbListeners,this);}
this.slider=slider;},registerThumbListeners:function(){this.slider.thumb.on({"mouseover":function(){this.onSlide(this.slider);this.dragging=false;},"mouseout":function(){if(!this.dragging){this.hide.apply(this,arguments);}},scope:this});},onSlide:function(slider){this.dragging=true;this.show();this.body.update(this.getText(slider));this.doAutoWidth();this.el.alignTo(slider.thumb,'b-t?',this.offsets);},getText:function(slider){return slider.getValue();}});Ext.namespace("GeoExt");GeoExt.ZoomSlider=Ext.extend(Ext.Slider,{map:null,baseCls:"gx-zoomslider",aggressive:false,updating:false,initComponent:function(){GeoExt.ZoomSlider.superclass.initComponent.call(this);if(this.map){if(this.map instanceof GeoExt.MapPanel){this.map=this.map.map;}
this.bind(this.map);}
if(this.aggressive===true){this.on('change',this.changeHandler,this);}else{this.on('changecomplete',this.changeHandler,this);}
this.on("beforedestroy",this.unbind,this);},onRender:function(){GeoExt.ZoomSlider.superclass.onRender.apply(this,arguments);this.el.addClass(this.baseCls);},afterRender:function(){Ext.Slider.superclass.afterRender.apply(this,arguments);this.update();},addToMapPanel:function(panel){if(!this.events.afterrender){this.on({render:function(){window.setTimeout(this.bind.createDelegate(this,[panel.map]),0);},scope:this});}
this.on({render:function(){var el=this.getEl();el.setStyle({position:"absolute",zIndex:panel.map.Z_INDEX_BASE.Control});el.on({mousedown:this.stopMouseEvents,click:this.stopMouseEvents});},afterrender:function(){this.bind(panel.map);},scope:this});},stopMouseEvents:function(e){e.stopEvent();},removeFromMapPanel:function(panel){var el=this.getEl();el.un("mousedown",this.stopMouseEvents,this);el.un("click",this.stopMouseEvents,this);this.unbind();},bind:function(map){this.map=map;this.map.events.on({zoomend:this.update,changebaselayer:this.initZoomValues,scope:this});if(this.map.baseLayer){this.initZoomValues();this.update();}},unbind:function(){if(this.map){this.map.events.un({zoomend:this.update,changebaselayer:this.initZoomValues,scope:this});}},initZoomValues:function(){var layer=this.map.baseLayer;if(this.initialConfig.minValue===undefined){this.minValue=layer.minZoomLevel||0;}
if(this.initialConfig.maxValue===undefined){this.maxValue=layer.maxZoomLevel||layer.numZoomLevels-1;}},getZoom:function(){return this.getValue();},getScale:function(){return OpenLayers.Util.getScaleFromResolution(this.map.getResolutionForZoom(this.getValue()),this.map.getUnits());},getResolution:function(){return this.map.getResolutionForZoom(this.getValue());},changeHandler:function(){if(this.map&&!this.updating){this.map.zoomTo(this.getValue());}},update:function(){if(this.rendered&&this.map){this.updating=true;this.setValue(this.map.getZoom());this.updating=false;}}});Ext.reg('gx_zoomslider',GeoExt.ZoomSlider);Ext.namespace("GeoExt.data");GeoExt.data.WMSCapabilitiesReader=function(meta,recordType){meta=meta||{};if(!meta.format){meta.format=new OpenLayers.Format.WMSCapabilities();}
if(typeof recordType!=="function"){recordType=GeoExt.data.LayerRecord.create(recordType||meta.fields||[{name:"name",type:"string"},{name:"title",type:"string"},{name:"abstract",type:"string"},{name:"queryable",type:"boolean"},{name:"opaque",type:"boolean"},{name:"noSubsets",type:"boolean"},{name:"cascaded",type:"int"},{name:"fixedWidth",type:"int"},{name:"fixedHeight",type:"int"},{name:"minScale",type:"float"},{name:"maxScale",type:"float"},{name:"prefix",type:"string"},{name:"formats"},{name:"styles"},{name:"srs"},{name:"dimensions"},{name:"bbox"},{name:"llbbox"},{name:"attribution"},{name:"keywords"},{name:"identifiers"},{name:"authorityURLs"},{name:"metadataURLs"}]);}
GeoExt.data.WMSCapabilitiesReader.superclass.constructor.call(this,meta,recordType);};Ext.extend(GeoExt.data.WMSCapabilitiesReader,Ext.data.DataReader,{attributionCls:"gx-attribution",read:function(request){var data=request.responseXML;if(!data||!data.documentElement){data=request.responseText;}
return this.readRecords(data);},serviceExceptionFormat:function(formats){if(OpenLayers.Util.indexOf(formats,"application/vnd.ogc.se_inimage")>-1){return"application/vnd.ogc.se_inimage";}
if(OpenLayers.Util.indexOf(formats,"application/vnd.ogc.se_xml")>-1){return"application/vnd.ogc.se_xml";}
return formats[0];},imageFormat:function(layer){var formats=layer.formats;if(layer.opaque&&OpenLayers.Util.indexOf(formats,"image/jpeg")>-1){return"image/jpeg";}
if(OpenLayers.Util.indexOf(formats,"image/png")>-1){return"image/png";}
if(OpenLayers.Util.indexOf(formats,"image/png; mode=24bit")>-1){return"image/png; mode=24bit";}
if(OpenLayers.Util.indexOf(formats,"image/gif")>-1){return"image/gif";}
return formats[0];},imageTransparent:function(layer){return layer.opaque==undefined||!layer.opaque;},readRecords:function(data){if(typeof data==="string"||data.nodeType){data=this.meta.format.read(data);}
var version=data.version;var capability=data.capability||{};var url=capability.request&&capability.request.getmap&&capability.request.getmap.href;var layers=capability.layers;var formats=capability.exception?capability.exception.formats:[];var exceptions=this.serviceExceptionFormat(formats);var records=[];if(url&&layers){var fields=this.recordType.prototype.fields;var layer,values,options,field,v;for(var i=0,lenI=layers.length;i<lenI;i++){layer=layers[i];if(layer.name){values={};for(var j=0,lenJ=fields.length;j<lenJ;j++){field=fields.items[j];v=layer[field.mapping||field.name]||field.defaultValue;v=field.convert(v);values[field.name]=v;}
options={attribution:layer.attribution?this.attributionMarkup(layer.attribution):undefined,minScale:layer.minScale,maxScale:layer.maxScale};if(this.meta.layerOptions){Ext.apply(options,this.meta.layerOptions);}
values.layer=new OpenLayers.Layer.WMS(layer.title||layer.name,url,{layers:layer.name,exceptions:exceptions,format:this.imageFormat(layer),transparent:this.imageTransparent(layer),version:version},options);records.push(new this.recordType(values,values.layer.id));}}}
return{totalRecords:records.length,success:true,records:records};},attributionMarkup:function(attribution){var markup=[];if(attribution.logo){markup.push("<img class='"+this.attributionCls+"-image' "
+"src='"+attribution.logo.href+"' />");}
if(attribution.title){markup.push("<span class='"+this.attributionCls+"-title'>"
+attribution.title
+"</span>");}
if(attribution.href){for(var i=0;i<markup.length;i++){markup[i]="<a class='"
+this.attributionCls+"-link' "
+"href="
+attribution.href
+">"
+markup[i]
+"</a>";}}
return markup.join(" ");}});Ext.namespace("GeoExt.data");GeoExt.data.LayerStoreMixin=function(){return{map:null,reader:null,constructor:function(config){config=config||{};config.reader=config.reader||new GeoExt.data.LayerReader({},config.fields);delete config.fields;var map=config.map instanceof GeoExt.MapPanel?config.map.map:config.map;delete config.map;if(config.layers){config.data=config.layers;}
delete config.layers;var options={initDir:config.initDir};delete config.initDir;arguments.callee.superclass.constructor.call(this,config);if(map){this.bind(map,options);}},bind:function(map,options){if(this.map){return;}
this.map=map;options=options||{};var initDir=options.initDir;if(options.initDir==undefined){initDir=GeoExt.data.LayerStore.MAP_TO_STORE|GeoExt.data.LayerStore.STORE_TO_MAP;}
var layers=map.layers.slice(0);if(initDir&GeoExt.data.LayerStore.STORE_TO_MAP){this.each(function(record){this.map.addLayer(record.get("layer"));},this);}
if(initDir&GeoExt.data.LayerStore.MAP_TO_STORE){this.loadData(layers,true);}
map.events.on({"changelayer":this.onChangeLayer,"addlayer":this.onAddLayer,"removelayer":this.onRemoveLayer,scope:this});this.on({"load":this.onLoad,"clear":this.onClear,"add":this.onAdd,"remove":this.onRemove,"update":this.onUpdate,scope:this});this.data.on({"replace":this.onReplace,scope:this});},unbind:function(){if(this.map){this.map.events.un({"changelayer":this.onChangeLayer,"addlayer":this.onAddLayer,"removelayer":this.onRemoveLayer,scope:this});this.un("load",this.onLoad,this);this.un("clear",this.onClear,this);this.un("add",this.onAdd,this);this.un("remove",this.onRemove,this);this.data.un("replace",this.onReplace,this);this.map=null;}},onChangeLayer:function(evt){var layer=evt.layer;var recordIndex=this.findBy(function(rec,id){return rec.get("layer")===layer;});if(recordIndex>-1){var record=this.getAt(recordIndex);if(evt.property==="order"){if(!this._adding&&!this._removing){var layerIndex=this.map.getLayerIndex(layer);if(layerIndex!==recordIndex){this._removing=true;this.remove(record);delete this._removing;this._adding=true;this.insert(layerIndex,[record]);delete this._adding;}}}else if(evt.property==="name"){record.set("title",layer.name);}else{this.fireEvent("update",this,record,Ext.data.Record.EDIT);}}},onAddLayer:function(evt){if(!this._adding){var layer=evt.layer;this._adding=true;this.loadData([layer],true);delete this._adding;}},onRemoveLayer:function(evt){if(this.map.unloadDestroy){if(!this._removing){var layer=evt.layer;this._removing=true;this.remove(this.getById(layer.id));delete this._removing;}}else{this.unbind();}},onLoad:function(store,records,options){if(!Ext.isArray(records)){records=[records];}
if(options&&!options.add){this._removing=true;for(var i=this.map.layers.length-1;i>=0;i--){this.map.removeLayer(this.map.layers[i]);}
delete this._removing;var len=records.length;if(len>0){var layers=new Array(len);for(var j=0;j<len;j++){layers[j]=records[j].get("layer");}
this._adding=true;this.map.addLayers(layers);delete this._adding;}}},onClear:function(store){this._removing=true;for(var i=this.map.layers.length-1;i>=0;i--){this.map.removeLayer(this.map.layers[i]);}
delete this._removing;},onAdd:function(store,records,index){if(!this._adding){this._adding=true;var layer;for(var i=records.length-1;i>=0;--i){layer=records[i].get("layer");this.map.addLayer(layer);if(index!==this.map.layers.length-1){this.map.setLayerIndex(layer,index);}}
delete this._adding;}},onRemove:function(store,record,index){if(!this._removing){var layer=record.get("layer");if(this.map.getLayer(layer.id)!=null){this._removing=true;this.removeMapLayer(record);delete this._removing;}}},onUpdate:function(store,record,operation){if(operation===Ext.data.Record.EDIT){var layer=record.get("layer");var title=record.get("title");if(title!==layer.name){layer.setName(title);}}},removeMapLayer:function(record){this.map.removeLayer(record.get("layer"));},onReplace:function(key,oldRecord,newRecord){this.removeMapLayer(oldRecord);},destroy:function(){this.unbind();GeoExt.data.LayerStore.superclass.destroy.call(this);}};};GeoExt.data.LayerStore=Ext.extend(Ext.data.Store,new GeoExt.data.LayerStoreMixin);GeoExt.data.LayerStore.MAP_TO_STORE=1;GeoExt.data.LayerStore.STORE_TO_MAP=2;Ext.namespace("GeoExt.tree");GeoExt.tree.LayerLoader=function(config){Ext.apply(this,config);this.addEvents("beforeload","load");GeoExt.tree.LayerLoader.superclass.constructor.call(this);};Ext.extend(GeoExt.tree.LayerLoader,Ext.util.Observable,{store:null,filter:function(record){return record.get("layer").displayInLayerSwitcher==true;},uiProviders:null,load:function(node,callback){if(this.fireEvent("beforeload",this,node)){this.removeStoreHandlers();while(node.firstChild){node.removeChild(node.firstChild);}
if(!this.uiProviders){this.uiProviders=node.getOwnerTree().getLoader().uiProviders;}
if(!this.store){this.store=GeoExt.MapPanel.guess().layers;}
this.store.each(function(record){this.addLayerNode(node,record);},this);this.addStoreHandlers(node);if(typeof callback=="function"){callback();}
this.fireEvent("load",this,node);}},onStoreAdd:function(store,records,index,node){if(!this._reordering){var nodeIndex=node.recordIndexToNodeIndex(index+records.length-1);for(var i=0;i<records.length;++i){this.addLayerNode(node,records[i],nodeIndex);}}},onStoreRemove:function(store,record,index,node){if(!this._reordering){this.removeLayerNode(node,record);}},addLayerNode:function(node,layerRecord,index){index=index||0;if(this.filter(layerRecord)===true){var child=this.createNode({nodeType:'gx_layer',layer:layerRecord.get("layer"),layerStore:this.store});var sibling=node.item(index);if(sibling){node.insertBefore(child,sibling);}else{node.appendChild(child);}
child.on("move",this.onChildMove,this);}},removeLayerNode:function(node,layerRecord){if(this.filter(layerRecord)===true){var child=node.findChildBy(function(node){return node.layer==layerRecord.get("layer");});if(child){child.un("move",this.onChildMove,this);child.remove();node.reload();}}},onChildMove:function(tree,node,oldParent,newParent,index){this._reordering=true;var oldRecordIndex=this.store.findBy(function(record){return record.get("layer")===node.layer;});var record=this.store.getAt(oldRecordIndex);if(newParent instanceof GeoExt.tree.LayerContainer&&this.store===newParent.loader.store){newParent.loader._reordering=true;this.store.remove(record);var newRecordIndex;if(newParent.childNodes.length>1){var searchIndex=(index===0)?index+1:index-1;newRecordIndex=this.store.findBy(function(r){return newParent.childNodes[searchIndex].layer===r.get("layer");});index===0&&newRecordIndex++;}else if(oldParent.parentNode===newParent.parentNode){var prev=newParent;do{prev=prev.previousSibling;}while(prev&&!(prev instanceof GeoExt.tree.LayerContainer&&prev.lastChild));if(prev){newRecordIndex=this.store.findBy(function(r){return prev.lastChild.layer===r.get("layer");});}else{var next=newParent;do{next=next.nextSibling;}while(next&&!(next instanceof GeoExt.tree.LayerContainer&&next.firstChild));if(next){newRecordIndex=this.store.findBy(function(r){return next.firstChild.layer===r.get("layer");});}
newRecordIndex++;}}
if(newRecordIndex!==undefined){this.store.insert(newRecordIndex,[record]);window.setTimeout(function(){newParent.reload();oldParent.reload();});}else{this.store.insert(oldRecordIndex,[record]);}
delete newParent.loader._reordering;}
delete this._reordering;},addStoreHandlers:function(node){if(!this._storeHandlers){this._storeHandlers={"add":this.onStoreAdd.createDelegate(this,[node],true),"remove":this.onStoreRemove.createDelegate(this,[node],true)};for(var evt in this._storeHandlers){this.store.on(evt,this._storeHandlers[evt],this);}}},removeStoreHandlers:function(){if(this._storeHandlers){for(var evt in this._storeHandlers){this.store.un(evt,this._storeHandlers[evt],this);}
delete this._storeHandlers;}},createNode:function(attr){if(this.baseAttrs){Ext.apply(attr,this.baseAttrs);}
if(typeof attr.uiProvider=='string'){attr.uiProvider=this.uiProviders[attr.uiProvider]||eval(attr.uiProvider);}
attr.nodeType=attr.nodeType||"gx_layer";return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);},destroy:function(){this.removeStoreHandlers();}});Ext.namespace('GeoExt.grid');GeoExt.grid.FeatureSelectionModelMixin=function(){return{autoActivateControl:true,layerFromStore:true,selectControl:null,bound:false,superclass:null,constructor:function(config){config=config||{};if(config.selectControl instanceof OpenLayers.Control.SelectFeature){if(!config.singleSelect){var ctrl=config.selectControl;config.singleSelect=!(ctrl.multiple||!!ctrl.multipleKey);}}else if(config.layer instanceof OpenLayers.Layer.Vector){this.selectControl=this.createSelectControl(config.layer,config.selectControl);delete config.layer;delete config.selectControl;}
this.superclass=arguments.callee.superclass;this.superclass.constructor.call(this,config);},initEvents:function(){this.superclass.initEvents.call(this);if(this.layerFromStore){var layer=this.grid.getStore()&&this.grid.getStore().layer;if(layer&&!(this.selectControl instanceof OpenLayers.Control.SelectFeature)){this.selectControl=this.createSelectControl(layer,this.selectControl);}}
if(this.selectControl){this.bind(this.selectControl);}},createSelectControl:function(layer,config){config=config||{};var singleSelect=config.singleSelect!==undefined?config.singleSelect:this.singleSelect;config=OpenLayers.Util.extend({toggle:true,multipleKey:singleSelect?null:(Ext.isMac?"metaKey":"ctrlKey")},config);var selectControl=new OpenLayers.Control.SelectFeature(layer,config);layer.map.addControl(selectControl);return selectControl;},bind:function(obj,options){if(!this.bound){options=options||{};this.selectControl=obj;if(obj instanceof OpenLayers.Layer.Vector){this.selectControl=this.createSelectControl(obj,options.controlConfig);}
if(this.autoActivateControl){this.selectControl.activate();}
var layers=this.getLayers();for(var i=0,len=layers.length;i<len;i++){layers[i].events.on({featureselected:this.featureSelected,featureunselected:this.featureUnselected,scope:this});}
this.on("rowselect",this.rowSelected,this);this.on("rowdeselect",this.rowDeselected,this);this.bound=true;}
return this.selectControl;},unbind:function(){var selectControl=this.selectControl;if(this.bound){var layers=this.getLayers();for(var i=0,len=layers.length;i<len;i++){layers[i].events.un({featureselected:this.featureSelected,featureunselected:this.featureUnselected,scope:this});}
this.un("rowselect",this.rowSelected,this);this.un("rowdeselect",this.rowDeselected,this);if(this.autoActivateControl){selectControl.deactivate();}
this.selectControl=null;this.bound=false;}
return selectControl;},featureSelected:function(evt){if(!this._selecting){var store=this.grid.store;var row=store.findBy(function(record,id){return record.data.feature==evt.feature;});if(row!=-1&&!this.isSelected(row)){this._selecting=true;this.selectRow(row,!this.singleSelect);this._selecting=false;this.grid.getView().focusRow(row);}}},featureUnselected:function(evt){if(!this._selecting){var store=this.grid.store;var row=store.findBy(function(record,id){return record.data.feature==evt.feature;});if(row!=-1&&this.isSelected(row)){this._selecting=true;this.deselectRow(row);this._selecting=false;this.grid.getView().focusRow(row);}}},rowSelected:function(model,row,record){var feature=record.data.feature;if(!this._selecting&&feature){var layers=this.getLayers();for(var i=0,len=layers.length;i<len;i++){if(layers[i].selectedFeatures.indexOf(feature)==-1){this._selecting=true;this.selectControl.select(feature);this._selecting=false;break;}}}},rowDeselected:function(model,row,record){var feature=record.data.feature;if(!this._selecting&&feature){var layers=this.getLayers();for(var i=0,len=layers.length;i<len;i++){if(layers[i].selectedFeatures.indexOf(feature)!=-1){this._selecting=true;this.selectControl.unselect(feature);this._selecting=false;break;}}}},getLayers:function(){return this.selectControl.layers||[this.selectControl.layer];}};};GeoExt.grid.FeatureSelectionModel=Ext.extend(Ext.grid.RowSelectionModel,new GeoExt.grid.FeatureSelectionModelMixin);Ext.namespace("GeoExt","GeoExt.data");GeoExt.data.LayerReader=function(meta,recordType){meta=meta||{};if(!(recordType instanceof Function)){recordType=GeoExt.data.LayerRecord.create(recordType||meta.fields||{});}
GeoExt.data.LayerReader.superclass.constructor.call(this,meta,recordType);};Ext.extend(GeoExt.data.LayerReader,Ext.data.DataReader,{totalRecords:null,readRecords:function(layers){var records=[];if(layers){var recordType=this.recordType,fields=recordType.prototype.fields;var i,lenI,j,lenJ,layer,values,field,v;for(i=0,lenI=layers.length;i<lenI;i++){layer=layers[i];values={};for(j=0,lenJ=fields.length;j<lenJ;j++){field=fields.items[j];v=layer[field.mapping||field.name]||field.defaultValue;v=field.convert(v);values[field.name]=v;}
values.layer=layer;records[records.length]=new recordType(values,layer.id);}}
return{records:records,totalRecords:this.totalRecords!=null?this.totalRecords:records.length};}});Ext.namespace("GeoExt.data");GeoExt.data.LayerRecord=Ext.data.Record.create([{name:"layer"},{name:"title",type:"string",mapping:"name"}]);GeoExt.data.LayerRecord.prototype.clone=function(id){var layer=this.get("layer")&&this.get("layer").clone();return new this.constructor(Ext.applyIf({layer:layer},this.data),id||layer.id);};GeoExt.data.LayerRecord.create=function(o){var f=Ext.extend(GeoExt.data.LayerRecord,{});var p=f.prototype;p.fields=new Ext.util.MixedCollection(false,function(field){return field.name;});GeoExt.data.LayerRecord.prototype.fields.each(function(f){p.fields.add(f);});if(o){for(var i=0,len=o.length;i<len;i++){p.fields.add(new Ext.data.Field(o[i]));}}
f.getField=function(name){return p.fields.get(name);};return f;};Ext.namespace("GeoExt.form");GeoExt.form.BasicForm=Ext.extend(Ext.form.BasicForm,{protocol:null,prevResponse:null,autoAbort:true,doAction:function(action,options){if(action=="search"){options=Ext.applyIf(options||{},{protocol:this.protocol,abortPrevious:this.autoAbort});action=new GeoExt.form.SearchAction(this,options);}
return GeoExt.form.BasicForm.superclass.doAction.call(this,action,options);},search:function(options){return this.doAction("search",options);}});Ext.namespace('GeoExt','GeoExt.data');GeoExt.data.FeatureReader=function(meta,recordType){meta=meta||{};if(!(recordType instanceof Function)){recordType=GeoExt.data.FeatureRecord.create(recordType||meta.fields||{});}
GeoExt.data.FeatureReader.superclass.constructor.call(this,meta,recordType);};Ext.extend(GeoExt.data.FeatureReader,Ext.data.DataReader,{totalRecords:null,read:function(response){return this.readRecords(response.features);},readRecords:function(features){var records=[];if(features){var recordType=this.recordType,fields=recordType.prototype.fields;var i,lenI,j,lenJ,feature,values,field,v;for(i=0,lenI=features.length;i<lenI;i++){feature=features[i];values={};if(feature.attributes){for(j=0,lenJ=fields.length;j<lenJ;j++){field=fields.items[j];if(/[\[\.]/.test(field.mapping)){try{v=new Function("obj","return obj."+field.mapping)(feature.attributes);}catch(e){v=field.defaultValue;}}
else{v=feature.attributes[field.mapping||field.name]||field.defaultValue;}
if(field.convert){v=field.convert(v);}
values[field.name]=v;}}
values.feature=feature;values.state=feature.state;values.fid=feature.fid;var id=(feature.state===OpenLayers.State.INSERT)?undefined:feature.id;records[records.length]=new recordType(values,id);}}
return{records:records,totalRecords:this.totalRecords!=null?this.totalRecords:records.length};}});Ext.namespace("GeoExt.data");GeoExt.data.WMSDescribeLayerReader=function(meta,recordType){meta=meta||{};if(!meta.format){meta.format=new OpenLayers.Format.WMSDescribeLayer();}
if(!(typeof recordType==="function")){recordType=Ext.data.Record.create(recordType||meta.fields||[{name:"owsType",type:"string"},{name:"owsURL",type:"string"},{name:"typeName",type:"string"}]);}
GeoExt.data.WMSDescribeLayerReader.superclass.constructor.call(this,meta,recordType);};Ext.extend(GeoExt.data.WMSDescribeLayerReader,Ext.data.DataReader,{read:function(request){var data=request.responseXML;if(!data||!data.documentElement){data=request.responseText;}
return this.readRecords(data);},readRecords:function(data){if(typeof data==="string"||data.nodeType){data=this.meta.format.read(data);}
var records=[],description;for(var i=0,len=data.length;i<len;i++){description=data[i];if(description){records.push(new this.recordType(description));}}
return{totalRecords:records.length,success:true,records:records};}});Ext.namespace("GeoExt.form");GeoExt.form.SearchAction=Ext.extend(Ext.form.Action,{type:"search",response:null,constructor:function(form,options){GeoExt.form.SearchAction.superclass.constructor.call(this,form,options);},run:function(){var o=this.options;var f=GeoExt.form.toFilter(this.form);if(o.clientValidation===false||this.form.isValid()){if(o.abortPrevious&&this.form.prevResponse){o.protocol.abort(this.form.prevResponse);}
this.form.prevResponse=o.protocol.read(Ext.applyIf({filter:f,callback:this.handleResponse,scope:this},o));}else if(o.clientValidation!==false){this.failureType=Ext.form.Action.CLIENT_INVALID;this.form.afterAction(this,false);}},handleResponse:function(response){this.form.prevResponse=null;this.response=response;if(response.success()){this.form.afterAction(this,true);}else{this.form.afterAction(this,false);}
var o=this.options;if(o.callback){o.callback.call(o.scope,response);}}});Ext.namespace("GeoExt.data");GeoExt.data.FeatureRecord=Ext.data.Record.create([{name:"feature"},{name:"state"},{name:"fid"}]);GeoExt.data.FeatureRecord.create=function(o){var f=Ext.extend(GeoExt.data.FeatureRecord,{});var p=f.prototype;p.fields=new Ext.util.MixedCollection(false,function(field){return field.name;});GeoExt.data.FeatureRecord.prototype.fields.each(function(f){p.fields.add(f);});if(o){for(var i=0,len=o.length;i<len;i++){p.fields.add(new Ext.data.Field(o[i]));}}
f.getField=function(name){return p.fields.get(name);};return f;};Ext.namespace('GeoExt');GeoExt.LegendWMS=Ext.extend(Ext.Panel,{imageFormat:"image/gif",defaultStyleIsFirst:true,layer:null,record:null,bodyBorder:false,initComponent:function(){GeoExt.LegendWMS.superclass.initComponent.call(this);if(!this.layer){this.layer=this.record.get("layer");}
this.updateLegend();},getLegendUrl:function(layerName,layerNames){var url;var styles=this.record&&this.record.get("styles");layerNames=layerNames||(this.layer.params.LAYERS instanceof Array)?this.layer.params.LAYERS:this.layer.params.LAYERS.split(",");var styleNames=this.layer.params.STYLES&&this.layer.params.STYLES.split(",");var idx=layerNames.indexOf(layerName);var styleName=styleNames&&styleNames[idx];if(styles&&styles.length>0){if(styleName){Ext.each(styles,function(s){url=(s.name==styleName&&s.legend)&&s.legend.href;return!url;});}else if(this.defaultStyleIsFirst===true&&!styleNames&&!this.layer.params.SLD&&!this.layer.params.SLD_BODY){url=styles[0].legend&&styles[0].legend.href;}}
return url||this.layer.getFullRequestString({REQUEST:"GetLegendGraphic",WIDTH:null,HEIGHT:null,EXCEPTIONS:"application/vnd.ogc.se_xml",LAYER:layerName,LAYERS:null,STYLE:(styleName!=='')?styleName:null,STYLES:null,SRS:null,FORMAT:this.imageFormat});},updateLegend:function(url){var layerNames,layerName,i,len;layerNames=(this.layer.params.LAYERS instanceof Array)?this.layer.params.LAYERS:this.layer.params.LAYERS.split(",");if(this.items){var destroyList=[];this.items.each(function(cmp){i=layerNames.indexOf(cmp.itemId);if(i<0){destroyList.push(cmp);}else{layerName=layerNames[i];var newUrl=url||this.getLegendUrl(layerName,layerNames);if(!OpenLayers.Util.isEquivalentUrl(newUrl,cmp.url)){cmp.updateLegend(newUrl);}}},this);for(i=0,len=destroyList.length;i<len;i++){var cmp=destroyList[i];this.remove(cmp);cmp.destroy();}}
var doLayout=false;for(i=0,len=layerNames.length;i<len;i++){layerName=layerNames[i];if(!this.items||!this.getComponent(layerName)){this.add({xtype:"gx_legendimage",url:url||this.getLegendUrl(layerName,layerNames),itemId:layerName});doLayout=true;}}
if(doLayout){this.doLayout();}}});Ext.namespace("GeoExt.data");GeoExt.data.WMSDescribeLayerStore=function(c){c=c||{};GeoExt.data.WMSDescribeLayerStore.superclass.constructor.call(this,Ext.apply(c,{proxy:c.proxy||(!c.data?new Ext.data.HttpProxy({url:c.url,disableCaching:false,method:"GET"}):undefined),reader:new GeoExt.data.WMSDescribeLayerReader(c,c.fields)}));};Ext.extend(GeoExt.data.WMSDescribeLayerStore,Ext.data.Store);Ext.namespace('GeoExt');GeoExt.FeatureRenderer=Ext.extend(Ext.BoxComponent,{feature:undefined,symbolizers:[OpenLayers.Feature.Vector.style["default"]],symbolType:"Point",resolution:1,minWidth:20,minHeight:20,renderers:["SVG","VML","Canvas"],rendererOptions:null,pointFeature:undefined,lineFeature:undefined,polygonFeature:undefined,renderer:null,initComponent:function(){GeoExt.FeatureRenderer.superclass.initComponent.apply(this,arguments);Ext.applyIf(this,{pointFeature:new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0)),lineFeature:new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([new OpenLayers.Geometry.Point(-8,-3),new OpenLayers.Geometry.Point(-3,3),new OpenLayers.Geometry.Point(3,-3),new OpenLayers.Geometry.Point(8,3)])),polygonFeature:new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(-8,-4),new OpenLayers.Geometry.Point(-6,-6),new OpenLayers.Geometry.Point(6,-6),new OpenLayers.Geometry.Point(8,-4),new OpenLayers.Geometry.Point(8,4),new OpenLayers.Geometry.Point(6,6),new OpenLayers.Geometry.Point(-6,6),new OpenLayers.Geometry.Point(-8,4)])]))});if(!this.feature){this.setFeature(null,{draw:false});}
this.addEvents("click");},initCustomEvents:function(){this.clearCustomEvents();this.el.on("click",this.onClick,this);},clearCustomEvents:function(){if(this.el&&this.el.removeAllListeners){this.el.removeAllListeners();}},onClick:function(){this.fireEvent("click",this);},onRender:function(ct,position){if(!this.el){this.el=document.createElement("div");this.el.id=this.getId();}
if(!this.renderer||!this.renderer.supported()){this.assignRenderer();}
this.renderer.map={getResolution:(function(){return this.resolution;}).createDelegate(this)};this.drawFeature();GeoExt.FeatureRenderer.superclass.onRender.apply(this,arguments);},afterRender:function(){GeoExt.FeatureRenderer.superclass.afterRender.apply(this,arguments);this.initCustomEvents();},onResize:function(w,h){this.setRendererDimensions();GeoExt.FeatureRenderer.superclass.onResize.apply(this,arguments);},setRendererDimensions:function(){var gb=this.feature.geometry.getBounds();var gw=gb.getWidth();var gh=gb.getHeight();var resolution=this.initialConfig.resolution;if(!resolution){resolution=Math.max(gw/this.width||0,gh/this.height||0)||1;}
this.resolution=resolution;var width=Math.max(this.width||this.minWidth,gw/resolution);var height=Math.max(this.height||this.minHeight,gh/resolution);var center=gb.getCenterPixel();var bhalfw=width*resolution/2;var bhalfh=height*resolution/2;var bounds=new OpenLayers.Bounds(center.x-bhalfw,center.y-bhalfh,center.x+bhalfw,center.y+bhalfh);this.renderer.setSize(new OpenLayers.Size(Math.round(width),Math.round(height)));this.renderer.setExtent(bounds,true);},assignRenderer:function(){for(var i=0,len=this.renderers.length;i<len;++i){var Renderer=OpenLayers.Renderer[this.renderers[i]];if(Renderer&&Renderer.prototype.supported()){this.renderer=new Renderer(this.el,this.rendererOptions);break;}}},setSymbolizers:function(symbolizers,options){this.symbolizers=symbolizers;if(!options||options.draw){this.drawFeature();}},setSymbolType:function(type,options){this.symbolType=type;this.setFeature(null,options);},setFeature:function(feature,options){this.feature=feature||this[this.symbolType.toLowerCase()+"Feature"];if(!options||options.draw){this.drawFeature();}},drawFeature:function(){this.renderer.clear();this.setRendererDimensions();for(var i=0,len=this.symbolizers.length;i<len;++i){this.renderer.drawFeature(this.feature.clone(),Ext.apply({},this.symbolizers[i]));}},update:function(options){options=options||{};if(options.feature){this.setFeature(options.feature,{draw:false});}else if(options.symbolType){this.setSymbolType(options.symbolType,{draw:false});}
if(options.symbolizers){this.setSymbolizers(options.symbolizers,{draw:false});}
this.drawFeature();},beforeDestroy:function(){this.clearCustomEvents();if(this.renderer){this.renderer.destroy();}}});Ext.reg('gx_renderer',GeoExt.FeatureRenderer);Ext.namespace("GeoExt");GeoExt.Popup=Ext.extend(Ext.Window,{anchored:true,map:null,panIn:true,unpinnable:true,feature:null,lonlat:null,animCollapse:false,draggable:false,shadow:false,popupCls:"gx-popup",ancCls:null,initComponent:function(){if(this.map instanceof GeoExt.MapPanel){this.map=this.map.map;}
if(!this.map&&this.feature&&this.feature.layer){this.map=this.feature.layer.map;}
if(!this.feature&&this.lonlat){this.feature=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(this.lonlat.lon,this.lonlat.lat));}
if(this.anchored){this.addAnchorEvents();}
this.baseCls=this.popupCls+" "+this.baseCls;this.elements+=',anc';GeoExt.Popup.superclass.initComponent.call(this);},onRender:function(ct,position){GeoExt.Popup.superclass.onRender.call(this,ct,position);this.ancCls=this.popupCls+"-anc";this.createElement("anc",this.el.dom);},initTools:function(){if(this.unpinnable){this.addTool({id:'unpin',handler:this.unanchorPopup.createDelegate(this,[])});}
GeoExt.Popup.superclass.initTools.call(this);},show:function(){GeoExt.Popup.superclass.show.apply(this,arguments);if(this.anchored){this.position();if(this.panIn&&!this._mapMove){this.panIntoView();}}},setSize:function(w,h){if(this.anc){var ancSize=this.anc.getSize();if(typeof w=='object'){h=w.height-ancSize.height;w=w.width;}else if(!isNaN(h)){h=h-ancSize.height;}}
GeoExt.Popup.superclass.setSize.call(this,w,h);},position:function(){var centerLonLat=this.feature.geometry.getBounds().getCenterLonLat();if(this._mapMove===true){var visible=this.map.getExtent().containsLonLat(centerLonLat);if(visible!==this.isVisible()){this.setVisible(visible);}}
if(this.isVisible()){var centerPx=this.map.getViewPortPxFromLonLat(centerLonLat);var mapBox=Ext.fly(this.map.div).getBox();var anc=this.anc;var dx=anc.getLeft(true)+anc.getWidth()/2;var dy=this.el.getHeight();this.setPosition(centerPx.x+mapBox.x-dx,centerPx.y+mapBox.y-dy);}},unanchorPopup:function(){this.removeAnchorEvents();this.draggable=true;this.header.addClass("x-window-draggable");this.dd=new Ext.Window.DD(this);this.anc.remove();this.anc=null;this.tools.unpin.hide();},panIntoView:function(){var centerLonLat=this.feature.geometry.getBounds().getCenterLonLat();var centerPx=this.map.getViewPortPxFromLonLat(centerLonLat);var mapBox=Ext.fly(this.map.div).getBox();var popupPos=this.getPosition(true);popupPos[0]-=mapBox.x;popupPos[1]-=mapBox.y;var panelSize=[mapBox.width,mapBox.height];var popupSize=this.getSize();var newPos=[popupPos[0],popupPos[1]];var padding=this.map.paddingForPopups;if(popupPos[0]<padding.left){newPos[0]=padding.left;}else if(popupPos[0]+popupSize.width>panelSize[0]-padding.right){newPos[0]=panelSize[0]-padding.right-popupSize.width;}
if(popupPos[1]<padding.top){newPos[1]=padding.top;}else if(popupPos[1]+popupSize.height>panelSize[1]-padding.bottom){newPos[1]=panelSize[1]-padding.bottom-popupSize.height;}
var dx=popupPos[0]-newPos[0];var dy=popupPos[1]-newPos[1];this.map.pan(dx,dy);},onMapMove:function(){this._mapMove=true;this.position();delete this._mapMove;},addAnchorEvents:function(){this.map.events.on({"move":this.onMapMove,scope:this});this.on({"resize":this.position,"collapse":this.position,"expand":this.position,scope:this});},removeAnchorEvents:function(){this.map.events.un({"move":this.onMapMove,scope:this});this.un("resize",this.position,this);this.un("collapse",this.position,this);this.un("expand",this.position,this);},beforeDestroy:function(){if(this.anchored){this.removeAnchorEvents();}
GeoExt.Popup.superclass.beforeDestroy.call(this);}});Ext.reg('gx_popup',GeoExt.Popup);Ext.namespace("GeoExt.data");GeoExt.data.AttributeStore=function(c){c=c||{};GeoExt.data.AttributeStore.superclass.constructor.call(this,Ext.apply(c,{proxy:c.proxy||(!c.data?new Ext.data.HttpProxy({url:c.url,disableCaching:false,method:"GET"}):undefined),reader:new GeoExt.data.AttributeReader(c,c.fields||["name","type"])}));};Ext.extend(GeoExt.data.AttributeStore,Ext.data.Store);Ext.namespace("GeoExt.form");GeoExt.form.FormPanel=Ext.extend(Ext.form.FormPanel,{protocol:null,createForm:function(){delete this.initialConfig.listeners;return new GeoExt.form.BasicForm(null,this.initialConfig);},search:function(options){this.getForm().search(options);}});Ext.reg("gx_formpanel",GeoExt.form.FormPanel);Ext.namespace("GeoExt.data");GeoExt.data.WMCReader=function(meta,recordType){meta=meta||{};if(!meta.format){meta.format=new OpenLayers.Format.WMC();}
if(!(typeof recordType==="function")){recordType=GeoExt.data.LayerRecord.create(recordType||meta.fields||[{name:"abstract",type:"string"},{name:"metadataURL",type:"string"},{name:"queryable",type:"boolean"},{name:"formats"},{name:"styles"}]);}
GeoExt.data.WMCReader.superclass.constructor.call(this,meta,recordType);};Ext.extend(GeoExt.data.WMCReader,Ext.data.DataReader,{read:function(request){var data=request.responseXML;if(!data||!data.documentElement){data=request.responseText;}
return this.readRecords(data);},readRecords:function(data){var format=this.meta.format;if(typeof data==="string"||data.nodeType){data=format.read(data);}
var layersContext=data?data.layersContext:undefined;var records=[];if(layersContext){var recordType=this.recordType,fields=recordType.prototype.fields;var i,lenI,j,lenJ,layerContext,values,field,v;for(i=0,lenI=layersContext.length;i<lenI;i++){layerContext=layersContext[i];values={};for(j=0,lenJ=fields.length;j<lenJ;j++){field=fields.items[j];v=layerContext[field.mapping||field.name]||field.defaultValue;v=field.convert(v);values[field.name]=v;}
values.layer=format.getLayerFromContext(layerContext);records.push(new this.recordType(values,values.layer.id));}}
return{totalRecords:records.length,success:true,records:records};}});Ext.namespace("GeoExt.tree");GeoExt.tree.OverlayLayerContainer=Ext.extend(GeoExt.tree.LayerContainer,{constructor:function(config){config=Ext.applyIf(config||{},{text:"Overlays"});config.loader=Ext.applyIf(config.loader||{},{filter:function(record){var layer=record.get("layer");return layer.displayInLayerSwitcher===true&&layer.isBaseLayer===false;}});GeoExt.tree.OverlayLayerContainer.superclass.constructor.call(this,config);}});Ext.tree.TreePanel.nodeTypes.gx_overlaylayercontainer=GeoExt.tree.OverlayLayerContainer;Ext.namespace("GeoExt.data");GeoExt.data.WFSCapabilitiesReader=function(meta,recordType){meta=meta||{};if(!meta.format){meta.format=new OpenLayers.Format.WFSCapabilities();}
if(!(typeof recordType==="function")){recordType=GeoExt.data.LayerRecord.create(recordType||meta.fields||[{name:"name",type:"string"},{name:"abstract",type:"string"}]);}
GeoExt.data.WFSCapabilitiesReader.superclass.constructor.call(this,meta,recordType);};Ext.extend(GeoExt.data.WFSCapabilitiesReader,Ext.data.DataReader,{read:function(request){var data=request.responseXML;if(!data||!data.documentElement){data=request.responseText;}
return this.readRecords(data);},readRecords:function(data){if(typeof data==="string"||data.nodeType){data=this.meta.format.read(data);}
var records=[],layer,l,parts,layerOptions,protocolOptions;var featureTypes=data.featureTypeList.featureTypes;var protocolDefaults={url:data.capability.request.getfeature.href.post};for(var i=0,len=featureTypes.length;i<len;i++){layer=featureTypes[i];if(layer.name){parts=layer.name.split(":");if(parts.length>1){protocolOptions={featureType:parts[1],featurePrefix:parts[0]};}else{protocolOptions={featureType:parts[0],featurePrefix:null};}
if(this.meta.protocolOptions){Ext.apply(protocolOptions,this.meta.protocolOptions,protocolDefaults);}else{Ext.apply(protocolOptions,{},protocolDefaults);}
layerOptions={protocol:new OpenLayers.Protocol.WFS(protocolOptions),strategies:[new OpenLayers.Strategy.Fixed()]};if(this.meta.layerOptions){Ext.apply(layerOptions,this.meta.layerOptions);}
l=new OpenLayers.Layer.Vector(layer.title||layer.name,layerOptions);records.push(new this.recordType(Ext.apply(layer,{layer:l}),l.id));}}
return{totalRecords:records.length,success:true,records:records};}});Ext.namespace('GeoExt');GeoExt.LegendPanel=Ext.extend(Ext.Panel,{dynamic:true,showTitle:true,labelCls:null,bodyStyle:'',layerStore:null,filter:function(record){return true;},initComponent:function(){GeoExt.LegendPanel.superclass.initComponent.call(this);},onRender:function(){GeoExt.LegendPanel.superclass.onRender.apply(this,arguments);if(!this.layerStore){this.layerStore=GeoExt.MapPanel.guess().layers;}
this.layerStore.each(function(record){this.addLegend(record);},this);if(this.dynamic){this.layerStore.on({"add":this.onStoreAdd,"remove":this.onStoreRemove,"clear":this.onStoreClear,"update":this.onStoreUpdate,scope:this});}
this.doLayout();},recordIndexToPanelIndex:function(index){var store=this.layerStore;var count=store.getCount();var panelIndex=-1;var legendCount=this.items?this.items.length:0;for(var i=count-1;i>=0;--i){var layer=store.getAt(i).get("layer");var legendGenerator=GeoExt["Legend"+layer.CLASS_NAME.split(".").pop()];if(layer.displayInLayerSwitcher&&legendGenerator&&(store.getAt(i).get("hideInLegend")!==true)){++panelIndex;if(index===i||panelIndex>legendCount-1){break;}}}
return panelIndex;},onStoreUpdate:function(store,record,operation){var layer=record.get('layer');var legend=this.items?this.getComponent(layer.id):null;if((this.showTitle&&!record.get('hideTitle'))&&(legend&&legend.items.get(0).text!==record.get('title'))){legend.items.get(0).setText(record.get('title'));}
if(legend){legend.setVisible(layer.getVisibility()&&layer.inRange&&layer.displayInLayerSwitcher&&!record.get('hideInLegend'));var url=record.get("legendURL")!=null?record.get("legendURL"):undefined;legend.items.get(1).updateLegend(url);}},onStoreAdd:function(store,records,index){var panelIndex=this.recordIndexToPanelIndex(index+records.length-1);for(var i=0,len=records.length;i<len;i++){this.addLegend(records[i],panelIndex);}
this.doLayout();},onStoreRemove:function(store,record,index){this.removeLegend(record);},removeLegend:function(record){var legend=this.getComponent(record.get('layer').id);if(legend){this.remove(legend,true);this.doLayout();}},onStoreClear:function(store){this.removeAllLegends();},removeAllLegends:function(){this.removeAll(true);this.doLayout();},createLegendSubpanel:function(record){var layer=record.get('layer');var mainPanel=this.createMainPanel(record);if(mainPanel!==null){var legend;if(record.get('legendURL')){legend=new GeoExt.LegendImage({url:record.get('legendURL')});mainPanel.add(legend);}else{var legendGenerator=GeoExt["Legend"+layer.CLASS_NAME.split(".").pop()];if(legendGenerator){legend=new legendGenerator(Ext.applyIf({layer:layer,record:record},this.legendOptions));mainPanel.add(legend);}}}
return mainPanel;},addLegend:function(record,index){if(this.filter(record)===true){index=index||0;var layer=record.get('layer');var legendSubpanel=this.createLegendSubpanel(record);if(legendSubpanel!==null){legendSubpanel.setVisible(layer.getVisibility()&&layer.inRange);this.insert(index,legendSubpanel);}}},createMainPanel:function(record){var layer=record.get('layer');var panel=null;var legendGenerator=GeoExt["Legend"+layer.CLASS_NAME.split(".").pop()];if(layer.displayInLayerSwitcher&&!record.get('hideInLegend')&&legendGenerator){var panelConfig={id:layer.id,border:false,bodyBorder:false,hideMode:'offsets',bodyStyle:this.bodyStyle,items:[new Ext.form.Label({text:(this.showTitle&&!record.get('hideTitle'))?layer.name:'',cls:'x-form-item x-form-item-label'+
(this.labelCls?' '+this.labelCls:'')})]};panel=new Ext.Panel(panelConfig);}
return panel;},onDestroy:function(){if(this.layerStore){this.layerStore.un("add",this.onStoreAdd,this);this.layerStore.un("remove",this.onStoreRemove,this);this.layerStore.un("clear",this.onStoreClear,this);this.layerStore.un("update",this.onStoreUpdate,this);}
GeoExt.LegendPanel.superclass.onDestroy.apply(this,arguments);}});Ext.reg('gx_legendpanel',GeoExt.LegendPanel);Ext.namespace("GeoExt.form");GeoExt.form.toFilter=function(form,logicalOp){if(form instanceof Ext.form.FormPanel){form=form.getForm();}
var filters=[],values=form.getValues(false);for(var prop in values){var s=prop.split("__");var value=values[prop],type;if(s.length>1&&(type=GeoExt.form.toFilter.FILTER_MAP[s[1]])!==undefined){prop=s[0];}else{type=OpenLayers.Filter.Comparison.EQUAL_TO;}
filters.push(new OpenLayers.Filter.Comparison({type:type,value:value,property:prop}));}
return new OpenLayers.Filter.Logical({type:logicalOp||OpenLayers.Filter.Logical.AND,filters:filters});};GeoExt.form.toFilter.FILTER_MAP={"eq":OpenLayers.Filter.Comparison.EQUAL_TO,"ne":OpenLayers.Filter.Comparison.NOT_EQUAL_TO,"lt":OpenLayers.Filter.Comparison.LESS_THAN,"le":OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,"gt":OpenLayers.Filter.Comparison.GREATER_THAN,"ge":OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,"like":OpenLayers.Filter.Comparison.LIKE};Ext.namespace("GeoExt.tree");GeoExt.tree.LayerNodeUI=Ext.extend(Ext.tree.TreeNodeUI,{constructor:function(config){GeoExt.tree.LayerNodeUI.superclass.constructor.apply(this,arguments);},render:function(bulkRender){var a=this.node.attributes;if(a.checked===undefined){a.checked=this.node.layer.getVisibility();}
GeoExt.tree.LayerNodeUI.superclass.render.apply(this,arguments);var cb=this.checkbox;if(a.checkedGroup){var radio=Ext.DomHelper.insertAfter(cb,['<input type="radio" name="',a.checkedGroup,'_checkbox" class="',cb.className,cb.checked?'" checked="checked"':'','"></input>'].join(""));radio.defaultChecked=cb.defaultChecked;Ext.get(cb).remove();this.checkbox=radio;}
this.enforceOneVisible();},onClick:function(e){if(e.getTarget('.x-tree-node-cb',1)){this.toggleCheck(this.isChecked());}else{GeoExt.tree.LayerNodeUI.superclass.onClick.apply(this,arguments);}},toggleCheck:function(value){value=(value===undefined?!this.isChecked():value);GeoExt.tree.LayerNodeUI.superclass.toggleCheck.call(this,value);this.enforceOneVisible();},enforceOneVisible:function(){var attributes=this.node.attributes;var group=attributes.checkedGroup;if(group){var layer=this.node.layer;var checkedNodes=this.node.getOwnerTree().getChecked();var checkedCount=0;Ext.each(checkedNodes,function(n){var ui=n.getUI();var l=n.layer
if(!n.hidden&&n.attributes.checkedGroup===group){checkedCount++;if(l!=layer&&attributes.checked){l.setVisibility(false);}}});if(checkedCount===0&&attributes.checked==false){var ui=this.node.getUI();layer.setVisibility(true);}}},appendDDGhost:function(ghostNode){var n=this.elNode.cloneNode(true);var radio=Ext.DomQuery.select("input[type='radio']",n);Ext.each(radio,function(r){r.name=r.name+"_clone";});ghostNode.appendChild(n);}});GeoExt.tree.LayerNode=Ext.extend(Ext.tree.AsyncTreeNode,{layer:null,layerStore:null,constructor:function(config){config.leaf=config.leaf||!(config.children||config.loader);if(!config.iconCls&&!config.children){config.iconCls="gx-tree-layer-icon";}
if(config.loader&&!(config.loader instanceof Ext.tree.TreeLoader)){config.loader=new GeoExt.tree.LayerParamLoader(config.loader);}
this.defaultUI=this.defaultUI||GeoExt.tree.LayerNodeUI;Ext.apply(this,{layer:config.layer,layerStore:config.layerStore});if(config.text){this.fixedText=true;}
GeoExt.tree.LayerNode.superclass.constructor.apply(this,arguments);},render:function(bulkRender){var layer=this.layer instanceof OpenLayers.Layer&&this.layer;if(!layer){if(!this.layerStore||this.layerStore=="auto"){this.layerStore=GeoExt.MapPanel.guess().layers;}
var i=this.layerStore.findBy(function(o){return o.get("title")==this.layer;},this);if(i!=-1){layer=this.layerStore.getAt(i).get("layer");}}
if(!this.rendered||!layer){var ui=this.getUI();if(layer){this.layer=layer;if(layer.isBaseLayer){this.draggable=false;Ext.applyIf(this.attributes,{checkedGroup:"gx_baselayer"});}
if(!this.text){this.text=layer.name;}
ui.show();this.addVisibilityEventHandlers();}else{ui.hide();}
if(this.layerStore instanceof GeoExt.data.LayerStore){this.addStoreEventHandlers(layer);}}
GeoExt.tree.LayerNode.superclass.render.apply(this,arguments);},addVisibilityEventHandlers:function(){this.layer.events.on({"visibilitychanged":this.onLayerVisibilityChanged,scope:this});this.on({"checkchange":this.onCheckChange,scope:this});},onLayerVisibilityChanged:function(){if(!this._visibilityChanging){this.getUI().toggleCheck(this.layer.getVisibility());}},onCheckChange:function(node,checked){if(checked!=this.layer.getVisibility()){this._visibilityChanging=true;var layer=this.layer;if(checked&&layer.isBaseLayer&&layer.map){layer.map.setBaseLayer(layer);}else{layer.setVisibility(checked);}
delete this._visibilityChanging;}},addStoreEventHandlers:function(){this.layerStore.on({"add":this.onStoreAdd,"remove":this.onStoreRemove,"update":this.onStoreUpdate,scope:this});},onStoreAdd:function(store,records,index){var l;for(var i=0;i<records.length;++i){l=records[i].get("layer");if(this.layer==l){this.getUI().show();break;}else if(this.layer==l.name){this.render();break;}}},onStoreRemove:function(store,record,index){if(this.layer==record.get("layer")){this.getUI().hide();}},onStoreUpdate:function(store,record,operation){var layer=record.get("layer");if(!this.fixedText&&(this.layer==layer&&this.text!==layer.name)){this.setText(layer.name);}},destroy:function(){var layer=this.layer;if(layer instanceof OpenLayers.Layer){layer.events.un({"visibilitychanged":this.onLayerVisibilityChanged,scope:this});}
delete this.layer;var layerStore=this.layerStore;if(layerStore){layerStore.un("add",this.onStoreAdd,this);layerStore.un("remove",this.onStoreRemove,this);layerStore.un("update",this.onStoreUpdate,this);}
delete this.layerStore;this.un("checkchange",this.onCheckChange,this);GeoExt.tree.LayerNode.superclass.destroy.apply(this,arguments);}});Ext.tree.TreePanel.nodeTypes.gx_layer=GeoExt.tree.LayerNode;Ext.namespace("GeoExt.tree");GeoExt.tree.LayerParamNode=Ext.extend(Ext.tree.TreeNode,{layer:null,param:null,item:null,delimiter:null,allItems:null,constructor:function(attributes){var config=attributes||{};config.iconCls=config.iconCls||"gx-tree-layerparam-icon";config.text=config.text||config.item;typeof config.layer=="string"?false:config.layer.getVisibility();this.param=config.param;this.item=config.item;this.delimiter=config.delimiter||",";GeoExt.tree.LayerParamNode.superclass.constructor.apply(this,arguments);},render:function(bulkRender){var layer=this.attributes.layer;if(typeof layer=="string"){var store=this.attributes.layerStore||GeoExt.MapPanel.guess().layers;var i=store.findBy(function(o){return o.get("title")==layer;});layer=store.getAt(i).get("layer");}
this.layer=layer;this.allItems=this.getItems();var visibility=layer.getVisibility();this.attributes.checked=this.attributes.checked==null?visibility:this.attributes.checked;if(this.attributes.checked!==visibility){this.onCheckChange(this,!visibility);}
this.addVisibilityEventHandlers();GeoExt.tree.LayerParamNode.superclass.render.apply(this,arguments);},getItems:function(){var paramValue=this.layer.params[this.param];return paramValue instanceof Array?paramValue:(paramValue?paramValue.split(this.delimiter):[]);},createParams:function(items){var params={};params[this.param]=this.layer.params[this.param]instanceof Array?items:items.join(this.delimiter);return params;},addVisibilityEventHandlers:function(){this.layer.events.on({"visibilitychanged":this.onLayerVisibilityChanged,scope:this});this.on({"checkchange":this.onCheckChange,scope:this});},onLayerVisibilityChanged:function(){if(this.getItems().length===0){this.layer.mergeNewParams(this.createParams(this.allItems));}
var visible=this.layer.getVisibility();if(visible&&this.getItems().indexOf(this.item)!==-1){this.getUI().toggleCheck(true);}
if(!visible){this.layer.mergeNewParams(this.createParams([]));this.getUI().toggleCheck(false);}},onCheckChange:function(node,checked){var layer=this.layer;var newItems=[];var curItems=this.getItems();if(checked===true&&layer.getVisibility()===false&&curItems.length===this.allItems.length){curItems=[];}
Ext.each(this.allItems,function(item){if((item!==this.item&&curItems.indexOf(item)!==-1)||(checked===true&&item===this.item)){newItems.push(item);}},this);var visible=(newItems.length>0);visible&&layer.mergeNewParams(this.createParams(newItems));if(visible!==layer.getVisibility()){layer.setVisibility(visible);}
(!visible)&&layer.mergeNewParams(this.createParams([]));},destroy:function(){var layer=this.layer;if(layer instanceof OpenLayers.Layer){layer.events.un({"visibilitychanged":this.onLayerVisibilityChanged,scope:this});}
delete this.layer;this.un("checkchange",this.onCheckChange,this);GeoExt.tree.LayerNode.superclass.destroy.apply(this,arguments);}});Ext.tree.TreePanel.nodeTypes.gx_layerparam=GeoExt.tree.LayerParamNode;Ext.namespace("GeoExt.tree");GeoExt.tree.RadioButtonMixin=function(){return(function(){var superclass;return{constructor:function(node){node.addEvents("radiochange");superclass=arguments.callee.superclass;superclass.constructor.apply(this,arguments);},render:function(bulkRender){if(!this.rendered){superclass.render.apply(this,arguments);var a=this.node.attributes;if(a.radioGroup){Ext.DomHelper.insertBefore(this.anchor,['<input type="radio" class="gx-tree-radio" name="',a.radioGroup,'_radio"></input>'].join(""));}}},onClick:function(e){var el=e.getTarget('.gx-tree-radio',1);if(el){el.defaultChecked=el.checked;this.fireEvent("radiochange",this.node);}else{superclass.onClick.apply(this,arguments);}}};})();};Ext.namespace('GeoExt');GeoExt.LegendImage=Ext.extend(Ext.BoxComponent,{url:null,defaultImgSrc:null,imgCls:null,initComponent:function(){GeoExt.LegendImage.superclass.initComponent.call(this);if(this.defaultImgSrc===null){this.defaultImgSrc=Ext.BLANK_IMAGE_URL;}
this.autoEl={tag:"img","class":(this.imgCls?this.imgCls:""),src:this.defaultImgSrc};},updateLegend:function(url){this.url=url;var el=this.getEl();if(el){el.un("error",this.onImageLoadError,this);el.on("error",this.onImageLoadError,this,{single:true});el.dom.src=url;}},onRender:function(ct,position){GeoExt.LegendImage.superclass.onRender.call(this,ct,position);if(this.url){this.updateLegend(this.url);}},onDestroy:function(){var el=this.getEl();if(el){el.un("error",this.onImageLoadError,this);}
GeoExt.LegendImage.superclass.onDestroy.apply(this,arguments);},onImageLoadError:function(){this.getEl().dom.src=this.defaultImgSrc;}});Ext.reg('gx_legendimage',GeoExt.LegendImage);Ext.namespace("GeoExt");GeoExt.LayerOpacitySlider=Ext.extend(Ext.Slider,{layer:null,complementaryLayer:null,delay:5,changeVisibilityDelay:5,aggressive:false,changeVisibility:false,value:null,constructor:function(config){if(config.layer){if(config.layer instanceof OpenLayers.Layer){this.layer=config.layer;}else if(config.layer instanceof GeoExt.data.LayerRecord){this.layer=config.layer.get('layer');}
if(config.complementaryLayer instanceof OpenLayers.Layer){this.complementaryLayer=config.complementaryLayer;}else if(config.complementaryLayer instanceof
GeoExt.data.LayerRecord){this.complementaryLayer=config.complementaryLayer.get('layer');}
delete config.layer;delete config.complementaryLayer;}
GeoExt.LayerOpacitySlider.superclass.constructor.call(this,config);},initComponent:function(){if(this.layer&&this.layer.opacity!==null){this.value=parseInt(this.layer.opacity*(this.maxValue-this.minValue));}else if(this.value==null){this.value=this.maxValue;}
GeoExt.LayerOpacitySlider.superclass.initComponent.call(this);if(this.changeVisibility&&this.layer&&(this.layer.opacity==0||this.value==this.minValue)){this.layer.setVisibility(false);}
if(this.complementaryLayer&&((this.layer&&this.layer.opacity==1)||(this.value==this.maxValue))){this.complementaryLayer.setVisibility(false);}
if(this.aggressive===true){this.on('change',this.changeLayerOpacity,this,{buffer:this.delay});}else{this.on('changecomplete',this.changeLayerOpacity,this);}
if(this.changeVisibility===true){this.on('change',this.changeLayerVisibility,this,{buffer:this.changeVisibilityDelay});}
if(this.complementaryLayer){this.on('change',this.changeComplementaryLayerVisibility,this,{buffer:this.changeVisibilityDelay});}},changeLayerOpacity:function(slider,value){if(this.layer){this.layer.setOpacity(value/100.0);}},changeLayerVisibility:function(slider,value){var currentVisibility=this.layer.getVisibility();if(value==this.minValue&&currentVisibility===true){this.layer.setVisibility(false);}else if(value>this.minValue&&currentVisibility==false){this.layer.setVisibility(true);}},changeComplementaryLayerVisibility:function(slider,value){var currentVisibility=this.complementaryLayer.getVisibility();if(value==this.maxValue&&currentVisibility===true){this.complementaryLayer.setVisibility(false);}else if(value<this.maxValue&&currentVisibility==false){this.complementaryLayer.setVisibility(true);}},addToMapPanel:function(panel){this.on({render:function(){var el=this.getEl();el.setStyle({position:"absolute",zIndex:panel.map.Z_INDEX_BASE.Control});el.on({mousedown:this.stopMouseEvents,click:this.stopMouseEvents});},scope:this});},removeFromMapPanel:function(panel){var el=this.getEl();el.un({mousedown:this.stopMouseEvents,click:this.stopMouseEvents,scope:this});},stopMouseEvents:function(e){e.stopEvent();}});Ext.reg('gx_opacityslider',GeoExt.LayerOpacitySlider);Ext.namespace("GeoExt.data");GeoExt.data.AttributeReader=function(meta,recordType){meta=meta||{};if(!meta.format){meta.format=new OpenLayers.Format.WFSDescribeFeatureType();}
GeoExt.data.AttributeReader.superclass.constructor.call(this,meta,recordType||meta.fields);};Ext.extend(GeoExt.data.AttributeReader,Ext.data.DataReader,{read:function(request){var data=request.responseXML;if(!data||!data.documentElement){data=request.responseText;}
return this.readRecords(data);},readRecords:function(data){var attributes;if(data instanceof Array){attributes=data;}else{attributes=this.meta.format.read(data).featureTypes[0].properties;}
var recordType=this.recordType;var fields=recordType.prototype.fields;var numFields=fields.length;var attr,values,name,record,ignore,matches,value,records=[];for(var i=0,len=attributes.length;i<len;++i){ignore=false;attr=attributes[i];values={};for(var j=0;j<numFields;++j){name=fields.items[j].name;value=attr[name];if(this.meta.ignore&&this.meta.ignore[name]){matches=this.meta.ignore[name];if(typeof matches=="string"){ignore=(matches===value);}else if(matches instanceof Array){ignore=(matches.indexOf(value)>-1);}else if(matches instanceof RegExp){ignore=(matches.test(value));}
if(ignore){break;}}
values[name]=attr[name];}
if(!ignore){records[records.length]=new recordType(values);}}
return{success:true,records:records,totalRecords:records.length};}});Ext.namespace("GeoExt");GeoExt.ZoomSliderTip=Ext.extend(GeoExt.SliderTip,{template:'<div>Zoom Level: {zoom}</div>'+'<div>Resolution: {resolution}</div>'+'<div>Scale: 1 : {scale}</div>',compiledTemplate:null,init:function(slider){this.compiledTemplate=new Ext.Template(this.template);GeoExt.ZoomSliderTip.superclass.init.call(this,slider);},getText:function(slider){var data={zoom:slider.getZoom(),resolution:slider.getResolution(),scale:Math.round(slider.getScale())};return this.compiledTemplate.apply(data);}});Ext.namespace("GeoExt.data");GeoExt.data.ScaleStore=Ext.extend(Ext.data.Store,{map:null,constructor:function(config){var map=(config.map instanceof GeoExt.MapPanel?config.map.map:config.map);delete config.map;config=Ext.applyIf(config,{reader:new Ext.data.JsonReader({},["level","resolution","scale"])});GeoExt.data.ScaleStore.superclass.constructor.call(this,config);if(map){this.bind(map);}},bind:function(map,options){this.map=(map instanceof GeoExt.MapPanel?map.map:map);this.map.events.register('changebaselayer',this,this.populateFromMap);if(this.map.baseLayer){this.populateFromMap();}else{this.map.events.register('addlayer',this,this.populateOnAdd);}},unbind:function(){if(this.map){this.map.events.unregister('addlayer',this,this.populateOnAdd);this.map.events.unregister('changebaselayer',this,this.populateFromMap);delete this.map;}},populateOnAdd:function(evt){if(evt.layer.isBaseLayer){this.populateFromMap();this.map.events.unregister('addlayer',this,this.populateOnAdd);}},populateFromMap:function(){var zooms=[];var resolutions=this.map.baseLayer.resolutions;var units=this.map.baseLayer.units;for(var i=resolutions.length-1;i>=0;i--){var res=resolutions[i];zooms.push({level:i,resolution:res,scale:OpenLayers.Util.getScaleFromResolution(res,units)});}
this.loadData(zooms);}});Ext.namespace("GeoExt.data");GeoExt.data.FeatureStoreMixin=function(){return{layer:null,reader:null,addFeatureFilter:null,addRecordFilter:null,constructor:function(config){config=config||{};config.reader=config.reader||new GeoExt.data.FeatureReader({},config.fields);var layer=config.layer;delete config.layer;if(config.features){config.data=config.features;}
delete config.features;var options={initDir:config.initDir};delete config.initDir;arguments.callee.superclass.constructor.call(this,config);if(layer){this.bind(layer,options);}},bind:function(layer,options){if(this.layer){return;}
this.layer=layer;options=options||{};var initDir=options.initDir;if(options.initDir==undefined){initDir=GeoExt.data.FeatureStore.LAYER_TO_STORE|GeoExt.data.FeatureStore.STORE_TO_LAYER;}
var features=layer.features.slice(0);if(initDir&GeoExt.data.FeatureStore.STORE_TO_LAYER){var records=this.getRange();for(var i=records.length-1;i>=0;i--){this.layer.addFeatures([records[i].get("feature")]);}}
if(initDir&GeoExt.data.FeatureStore.LAYER_TO_STORE){this.loadData(features,true);}
layer.events.on({"featuresadded":this.onFeaturesAdded,"featuresremoved":this.onFeaturesRemoved,"featuremodified":this.onFeatureModified,scope:this});this.on({"load":this.onLoad,"clear":this.onClear,"add":this.onAdd,"remove":this.onRemove,"update":this.onUpdate,scope:this});},unbind:function(){if(this.layer){this.layer.events.un({"featuresadded":this.onFeaturesAdded,"featuresremoved":this.onFeaturesRemoved,"featuremodified":this.onFeatureModified,scope:this});this.un("load",this.onLoad,this);this.un("clear",this.onClear,this);this.un("add",this.onAdd,this);this.un("remove",this.onRemove,this);this.un("update",this.onUpdate,this);this.layer=null;}},getRecordFromFeature:function(feature){var record=null;if(feature.state!==OpenLayers.State.INSERT){record=this.getById(feature.id);}else{var index=this.findBy(function(r){return r.get("feature")===feature;});if(index>-1){record=this.getAt(index);}}
return record;},onFeaturesAdded:function(evt){if(!this._adding){var features=evt.features,toAdd=features;if(typeof this.addFeatureFilter=="function"){toAdd=[];var i,len,feature;for(var i=0,len=features.length;i<len;i++){feature=features[i];if(this.addFeatureFilter(feature)!==false){toAdd.push(feature);}}}
this._adding=true;this.loadData(toAdd,true);delete this._adding;}},onFeaturesRemoved:function(evt){if(!this._removing){var features=evt.features,feature,record,i;for(i=features.length-1;i>=0;i--){feature=features[i];record=this.getRecordFromFeature(feature);if(record!==undefined){this._removing=true;this.remove(record);delete this._removing;}}}},onFeatureModified:function(evt){if(!this._updating){var feature=evt.feature;var record=this.getRecordFromFeature(feature);if(record!==undefined){record.beginEdit();attributes=feature.attributes;if(attributes){var fields=this.recordType.prototype.fields;for(var i=0,len=fields.length;i<len;i++){var field=fields.items[i];var key=field.mapping||field.name;if(key in attributes){record.set(field.name,field.convert(attributes[key]));}}}
record.set("state",feature.state);record.set("fid",feature.fid);record.data["feature"]=feature;this._updating=true;record.endEdit();delete this._updating;}}},addFeaturesToLayer:function(records){var i,len,features,record;if(typeof this.addRecordFilter=="function"){features=[];for(i=0,len=records.length;i<len;i++){record=records[i];if(this.addRecordFilter(record)!==false){features.push(record.get("feature"));}}}else{features=new Array((len=records.length));for(i=0;i<len;i++){features[i]=records[i].get("feature");}}
if(features.length>0){this._adding=true;this.layer.addFeatures(features);delete this._adding;}},onLoad:function(store,records,options){if(!options||options.add!==true){this._removing=true;this.layer.removeFeatures(this.layer.features);delete this._removing;this.addFeaturesToLayer(records);}},onClear:function(store){this._removing=true;this.layer.removeFeatures(this.layer.features);delete this._removing;},onAdd:function(store,records,index){if(!this._adding){this.addFeaturesToLayer(records);}},onRemove:function(store,record,index){if(!this._removing){var feature=record.get("feature");if(this.layer.getFeatureById(feature.id)!=null){this._removing=true;this.layer.removeFeatures([record.get("feature")]);delete this._removing;}}},onUpdate:function(store,record,operation){if(!this._updating){var defaultFields=new GeoExt.data.FeatureRecord().fields;var feature=record.get("feature");if(record.fields){var cont=this.layer.events.triggerEvent("beforefeaturemodified",{feature:feature});if(cont!==false){var attributes=feature.attributes;record.fields.each(function(field){var key=field.mapping||field.name;if(!defaultFields.containsKey(key)){attributes[key]=record.get(field.name);}});this._updating=true;this.layer.events.triggerEvent("featuremodified",{feature:feature});delete this._updating;if(this.layer.getFeatureById(feature.id)!=null){this.layer.drawFeature(feature);}}}}}};};GeoExt.data.FeatureStore=Ext.extend(Ext.data.Store,new GeoExt.data.FeatureStoreMixin);GeoExt.data.FeatureStore.LAYER_TO_STORE=1;GeoExt.data.FeatureStore.STORE_TO_LAYER=2;Ext.namespace("GeoExt.data");GeoExt.data.WFSCapabilitiesStore=function(c){c=c||{};GeoExt.data.WFSCapabilitiesStore.superclass.constructor.call(this,Ext.apply(c,{proxy:c.proxy||(!c.data?new Ext.data.HttpProxy({url:c.url,disableCaching:false,method:"GET"}):undefined),reader:new GeoExt.data.WFSCapabilitiesReader(c,c.fields)}));};Ext.extend(GeoExt.data.WFSCapabilitiesStore,Ext.data.Store);Ext.namespace("GeoExt");GeoExt.MapPanel=Ext.extend(Ext.Panel,{map:null,layers:null,center:null,zoom:null,extent:null,initComponent:function(){if(!(this.map instanceof OpenLayers.Map)){this.map=new OpenLayers.Map(Ext.applyIf(this.map||{},{allOverlays:true}));}
var layers=this.layers;if(!layers||layers instanceof Array){this.layers=new GeoExt.data.LayerStore({layers:layers,map:this.map});}
if(typeof this.center=="string"){this.center=OpenLayers.LonLat.fromString(this.center);}else if(this.center instanceof Array){this.center=new OpenLayers.LonLat(this.center[0],this.center[1]);}
if(typeof this.extent=="string"){this.extent=OpenLayers.Bounds.fromString(this.extent);}else if(this.extent instanceof Array){this.extent=OpenLayers.Bounds.fromArray(this.extent);}
GeoExt.MapPanel.superclass.initComponent.call(this);},updateMapSize:function(){if(this.map){this.map.updateSize();}},renderMap:function(){var map=this.map;map.render(this.body.dom);if(map.layers.length>0){if(this.center||this.zoom!=null){map.setCenter(this.center,this.zoom);}else if(this.extent){map.zoomToExtent(this.extent);}else{map.zoomToMaxExtent();}}},afterRender:function(){GeoExt.MapPanel.superclass.afterRender.apply(this,arguments);if(!this.ownerCt){this.renderMap();}else{this.ownerCt.on("move",this.updateMapSize,this);this.ownerCt.on({"afterlayout":{fn:this.renderMap,scope:this,single:true}});}},onResize:function(){GeoExt.MapPanel.superclass.onResize.apply(this,arguments);this.updateMapSize();},onBeforeAdd:function(item){if(typeof item.addToMapPanel==="function"){item.addToMapPanel(this);}
GeoExt.MapPanel.superclass.onBeforeAdd.apply(this,arguments);},remove:function(item,autoDestroy){if(typeof item.removeFromMapPanel==="function"){item.removeFromMapPanel(this);}
GeoExt.MapPanel.superclass.remove.apply(this,arguments);},beforeDestroy:function(){if(this.ownerCt){this.ownerCt.un("move",this.updateMapSize,this);}
if(!this.initialConfig.map||!(this.initialConfig.map instanceof OpenLayers.Map)){if(this.map&&this.map.destroy){this.map.destroy();}}
delete this.map;GeoExt.MapPanel.superclass.beforeDestroy.apply(this,arguments);}});GeoExt.MapPanel.guess=function(){return Ext.ComponentMgr.all.find(function(o){return o instanceof GeoExt.MapPanel;});};Ext.reg('gx_mappanel',GeoExt.MapPanel);Ext.namespace('GeoExt','GeoExt.data');GeoExt.data.ProtocolProxy=function(config){Ext.apply(this,config);GeoExt.data.ProtocolProxy.superclass.constructor.apply(this,arguments);};Ext.extend(GeoExt.data.ProtocolProxy,Ext.data.DataProxy,{protocol:null,abortPrevious:true,response:null,load:function(params,reader,callback,scope,arg){if(this.fireEvent("beforeload",this,params)!==false){var o={params:params||{},request:{callback:callback,scope:scope,arg:arg},reader:reader};var cb=OpenLayers.Function.bind(this.loadResponse,this,o);if(this.abortPrevious){this.abortRequest();}
var options={params:params,callback:cb,scope:this};Ext.applyIf(options,arg);this.response=this.protocol.read(options);}else{callback.call(scope||this,null,arg,false);}},abortRequest:function(){if(this.response){this.protocol.abort(this.response);this.response=null;}},loadResponse:function(o,response){if(response.success()){var result=o.reader.read(response);this.fireEvent("load",this,o,o.request.arg);o.request.callback.call(o.request.scope,result,o.request.arg,true);}else{this.fireEvent("loadexception",this,o,response);o.request.callback.call(o.request.scope,null,o.request.arg,false);}}});Ext.namespace("GeoExt");GeoExt.Action=Ext.extend(Ext.Action,{control:null,map:null,uScope:null,uHandler:null,uToggleHandler:null,uCheckHandler:null,constructor:function(config){this.uScope=config.scope;this.uHandler=config.handler;this.uToggleHandler=config.toggleHandler;this.uCheckHandler=config.checkHandler;config.scope=this;config.handler=this.pHandler;config.toggleHandler=this.pToggleHandler;config.checkHandler=this.pCheckHandler;var ctrl=this.control=config.control;delete config.control;if(ctrl){if(config.map){config.map.addControl(ctrl);delete config.map;}
if((config.pressed||config.checked)&&ctrl.map){ctrl.activate();}
ctrl.events.on({activate:this.onCtrlActivate,deactivate:this.onCtrlDeactivate,scope:this});}
arguments.callee.superclass.constructor.call(this,config);},pHandler:function(cmp){var ctrl=this.control;if(ctrl&&ctrl.type==OpenLayers.Control.TYPE_BUTTON){ctrl.trigger();}
if(this.uHandler){this.uHandler.apply(this.uScope,arguments);}},pToggleHandler:function(cmp,state){this.changeControlState(state);if(this.uToggleHandler){this.uToggleHandler.apply(this.uScope,arguments);}},pCheckHandler:function(cmp,state){this.changeControlState(state);if(this.uCheckHandler){this.uCheckHandler.apply(this.uScope,arguments);}},changeControlState:function(state){if(state){if(!this._activating){this._activating=true;this.control.activate();this._activating=false;}}else{if(!this._deactivating){this._deactivating=true;this.control.deactivate();this._deactivating=false;}}},onCtrlActivate:function(){var ctrl=this.control;if(ctrl.type==OpenLayers.Control.TYPE_BUTTON){this.enable();}else{this.safeCallEach("toggle",[true]);this.safeCallEach("setChecked",[true]);}},onCtrlDeactivate:function(){var ctrl=this.control;if(ctrl.type==OpenLayers.Control.TYPE_BUTTON){this.disable();}else{this.safeCallEach("toggle",[false]);this.safeCallEach("setChecked",[false]);}},safeCallEach:function(fnName,args){var cs=this.items;for(var i=0,len=cs.length;i<len;i++){if(cs[i][fnName]){cs[i][fnName].apply(cs[i],args);}}}});Ext.namespace("GeoExt.data");GeoExt.data.WMSCapabilitiesStore=function(c){c=c||{};GeoExt.data.WMSCapabilitiesStore.superclass.constructor.call(this,Ext.apply(c,{proxy:c.proxy||(!c.data?new Ext.data.HttpProxy({url:c.url,disableCaching:false,method:"GET"}):undefined),reader:new GeoExt.data.WMSCapabilitiesReader(c,c.fields)}));};Ext.extend(GeoExt.data.WMSCapabilitiesStore,Ext.data.Store);Ext.namespace("GeoExt");GeoExt.LayerOpacitySliderTip=Ext.extend(GeoExt.SliderTip,{template:'<div>{opacity}%</div>',compiledTemplate:null,init:function(slider){this.compiledTemplate=new Ext.Template(this.template);GeoExt.LayerOpacitySliderTip.superclass.init.call(this,slider);},getText:function(slider){var data={opacity:slider.getValue()};return this.compiledTemplate.apply(data);}});/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Lars Kneschke <l.kneschke@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.widgets');

/**
 * Widget to display maps
 *
 * @namespace   Tine.widgets
 * @class       Tine.widgets.MapPanel
 * @extends     GeoExt.MapPanel
 */
Tine.widgets.MapPanel = Ext.extend(GeoExt.MapPanel, {
    zoom: 4,
    map: null,
    layers: null,
    
    /**
     * @private
     */
    initComponent: function() {
        this.map =  new OpenLayers.Map();
        this.layers = [
            new OpenLayers.Layer.OSM()
        ];
        
        if(this.center instanceof Array) {
            this.center = new OpenLayers.LonLat(this.center[0], this.center[1]).transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
        }

        Tine.widgets.MapPanel.superclass.initComponent.call(this);
    },
    
    setCenter: function(lon, lat) {
        this.center = new OpenLayers.LonLat(lon, lat).transform(new OpenLayers.Projection("EPSG:4326"), this.map.getProjectionObject());
        this.map.setCenter(this.center);
        
        // add a marker
        var size = new OpenLayers.Size(32,32);
        var offset = new OpenLayers.Pixel(0, -size.h);
        var icon = new OpenLayers.Icon('images/oxygen/32x32/actions/flag-red.png', size, offset);
        
        var markers = new OpenLayers.Layer.Markers( "Markers" );
        markers.addMarker(new OpenLayers.Marker(this.center, icon));
        this.map.addLayer(markers);
    },
    
    beforeDestroy: function() {
        delete this.map;
        this.supr().beforeDestroy.apply(this, arguments);
    }
});

Ext.reg('widget-mappanel', Tine.widgets.MapPanel);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets');

/**
 * @namespace   Tine.widgets
 * @class       Tine.widgets.ContentTypeTreePanel
 * @extends     Ext.tree.TreePanel
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @param       {Object} config Configuration options
 * @description
 * <p>Utility class for generating content type trees as used in the apps westpanel</p>
 *<p>Example usage:</p>
<pre><code>
var modulePanel =  new Tine.widgets.ContentTypeTreePanel({
    app: Tine.Tinebase.appMgr.get('Timetracker'),
    contentTypes: [{model: 'Timesheet', requiredRight: null}, {model: 'Timeaccount', requiredRight: 'manage'}],
    contentType: 'Timeaccount'
});
</code></pre>
 */
Tine.widgets.ContentTypeTreePanel = function(config) {
    Ext.apply(this, config);
        
    Tine.widgets.ContentTypeTreePanel.superclass.constructor.call(this);
};

Ext.extend(Tine.widgets.ContentTypeTreePanel, Ext.tree.TreePanel, {
    rootVisible : false,
    border : false,
    collapsible : true,

    root: null,
    
    titleCollapse: true,
    title: '',
    baseCls: 'ux-arrowcollapse',

    recordClass: null,
    
    /**
     * @cfg {Tine.Tinebase.Application} app
     */
    app: null,
    
    /**
     * @cfg {Array} contentTypes
     */
    contentTypes: null,
    
    /**
     * @cfg {String} contentType 
     */
    contentType: null,
    
    /**
     * init
     */  
    initComponent: function() {
        Tine.widgets.ContentTypeTreePanel.superclass.initComponent.call(this);
        
        var treeRoot = new Ext.tree.TreeNode({
            expanded: true,
            text : '',
            allowDrag : false,
            allowDrop : false,
            icon : false
        });
    
        this.setRootNode(treeRoot);
        var treeRoot = this.getRootNode();
        
        this.recordClass = Tine[this.app.appName].Model[this.contentType];
        
        Ext.each(this.contentTypes, function(ct) {
            var modelName = ct.meta ? ct.meta.modelName : ct.model; 
            var recordClass = Tine[this.app.appName].Model[modelName];
            // check requiredRight if any
            if ( ct.requiredRight && (!Tine.Tinebase.common.hasRight(ct.requiredRight, this.app.appName, recordClass.getMeta('recordsName').toLowerCase()))) return true;
            var child = new Ext.tree.TreeNode({
                id : 'treenode-' + recordClass.getMeta('modelName'),
                iconCls: this.app.appName + modelName,
                text: recordClass.getRecordsName(),
                leaf : true,
                containerType: Tine.Tinebase.container.TYPE_SHARED,
                container: Ext.apply(Tine[this.app.name].registry.get('defaultContainer'), {name: this.app.i18n._hidden(recordClass.getMeta('containerName'))})                 
            });
            
            child.on('click', function() {
                        this.app.getMainScreen().activeContentType = modelName;
                        this.app.getMainScreen().show();
                    }, this);

            // append generic ctx-items (Tine.widgets.tree.ContextMenu)
                    
            if(ct.genericCtxActions) {
                this['contextMenu' + modelName] = Tine.widgets.tree.ContextMenu.getMenu({
                    nodeName: this.app.i18n._hidden(recordClass.getMeta('recordsName')),
                    actions: ct.genericCtxActions,
                    scope: this,
                    backend: 'Tinebase_Container',
                    backendModel: 'Container'
                });
          
            child.on('contextmenu', function(node, event) {
                if(node.leaf) {
                    this.ctxNode = node;
                    this['contextMenu' + modelName].showAt(event.getXY());
                }
            }, this);
             }       
                    
            treeRoot.appendChild(child);
        }, this);
    },
    
    /**
     * is called after render this panel, selects active node by contentType
     */
    afterRender: function() {
        Tine.widgets.ContentTypeTreePanel.superclass.afterRender.call(this);
        
        var activeNode = this.root.findChildBy(function(node) {
            if(node.id == 'treenode-' + this.contentType) return true;
            return false;
        }, this);
        
        if(activeNode) activeNode.select();
        
        this.on('click', function(event) {
            if(activeNode) activeNode.select();
        }, this);
        
    }
 
});
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <alex@stintzing.net>
 * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine.widgets.form');
 
 /**
 * @namespace   Tine.widgets.form
 * @class       Tine.widgets.form.RecordPickerManager
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 */

Tine.widgets.form.RecordPickerManager = function() {

    var items = {};
  
    return {
        /**
         * returns a registered recordpicker or creates the default one
         * @param {String/Tinebase.Application} appName      expands to recordClass: Tine[appName].Model[modelName],
         * @param {String/Tinebase.data.Record} modelName               recordProxy: Tine[appName][modelName.toLowerCase() + 'Backend'])
         * @param {Object} config       additional Configuration
         * @return {Object} recordpicker
         */
        get: function(appName, modelName, config) {
            config = config || {};
    
            var appName = Ext.isString(appName) ? appName : appName.appName,
                modelName = Ext.isFunction(modelName) ? modelName.getMeta('modelName') : modelName,
                key = appName+modelName;

            if(items[key]) {   // if registered
                if(Ext.isString(items[key])) { // xtype
                    return Ext.ComponentMgr.create(config, items[key]);
                } else {
                    return new items[key](config);
                }
            } else {    // not registered, create default
                var defaultconfig = {
                    recordClass: Tine.Tinebase.data.RecordMgr.get(appName, modelName),
                    recordProxy: Tine[appName][modelName.toLowerCase() + 'Backend'],
                    loadingText: _('Searching...')
                };
                Ext.apply(defaultconfig, config);
                return new Tine.Tinebase.widgets.form.RecordPickerComboBox(defaultconfig);
            }
        },
        
        /**
         * Registers a component
         * @param {String} appName          the application registered for
         * @param {String} modelName        the registered model name
         * @param {String/Object} component the component or xtype to register 
         */
        register: function(appName, modelName, component) {
            
            if(!Tine.hasOwnProperty('log')) {
                this.register.defer(100, this, [appName, modelName, component]);
                return false;
            }
            
            var appName = Ext.isString(appName) ? appName : appName.appName,
                modelName = Ext.isFunction(modelName) ? modelName.getMeta('modelName') : modelName,
                key = appName+modelName;

            if(!items[key]) {
                Tine.log.debug('RecordPickerManager::registerItem: ' + appName + modelName);
                items[key] = component;
            }
        }
    };
}();
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <alex@stintzing.net>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.widgets.dialog');

/**
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.AddToRecordPanel
 * @extends     Ext.FormPanel
 * @author      Alexander Stintzing <alex@stintzing.net>
 */

Tine.widgets.dialog.AddToRecordPanel = Ext.extend(Ext.FormPanel, {

    /**
     * the name of the application the record to be added belongs to
     * @cfg {String}
     */
    appName : null,
    
    /**
     * the record class of the record the selected records should be added to
     * @cfg {Tine.Tinebase.data.Record}
     */
    recordClass: null,
    
    /**
     * the selection filter if is filter select
     * @type {Object}
     */
    selectionFilter: null,
    
    /**
     * count of records to add to
     * @type {Integer}
     */
    count: null,
    
    /**
     * the calling app
     * @type {String}
     */
    callingApp: null,
    
    /**
     * the calling model
     * @type {String}
     */
    callingModel: null,
    
    /**
     * the relations to add as Json Encoded array or bool on filterSelection
     * @type {Boolean|String} 
     */
    addRelations: null,
    
    // private
    app: null,
    layout : 'fit',
    border : false,
    cls : 'tw-editdialog',
    labelAlign : 'top',
    anchor : '100% 100%',
    deferredRender : false,
    buttonAlign : null,
    bufferResize : 500,
    
    /**
     * initializes the component
     */
    initComponent: function() {
         
        if (!this.app) {
            this.app = Tine.Tinebase.appMgr.get(this.appName);
        }
            
        Tine.log.debug('initComponent: appName: ', this.appName);
        Tine.log.debug('initComponent: app: ', this.app);

        // init actions
        this.initActions();
        // init buttons and tbar
        this.initButtons();
        
        // get items for this dialog
        this.items = this.getFormItems();

        Tine.widgets.dialog.AddToRecordPanel.superclass.initComponent.call(this);
    },
    
    /**
     * initializes the actions
     */
    initActions: function() {
        this.action_cancel = new Ext.Action({
            text : _('Cancel'),
            minWidth : 70,
            scope : this,
            handler : this.onCancel,
            iconCls : 'action_cancel'
        });
        
        this.action_update = new Ext.Action({
            text : _('Ok'),
            minWidth : 70,
            scope : this,
            handler : this.onUpdate,
            iconCls : 'action_saveAndClose'
        });
    },

    /**
     * create the buttons
     */
    initButtons : function() {
        this.fbar = [ '->', this.action_cancel, this.action_update ];
    },
    
    /**
     * is called on render, creates keymap
     * @param {} ct
     * @param {} position
     */
    onRender : function(ct, position) {
        Tine.widgets.dialog.AddToRecordPanel.superclass.onRender.call(this, ct, position);

        // generalized keybord map for edit dlgs
        new Ext.KeyMap(this.el, [ {
            key : [ 10, 13 ], // ctrl + return
            ctrl : true,
            fn : this.onUpdate,
            scope : this
        }]);

    },
    
    /**
     * is called on cancel
     */
    onCancel: function() {
        this.fireEvent('cancel');
        this.purgeListeners();
        this.window.close();
    },
    
    /**
     * returns true if the form is valid, must be overridden
     * @return {Boolean}
     */
    isValid: Ext.emptyFn,
    
    /**
     * returns the items of the form, must be overridden
     * @return {Object} with the configured items
     */
    getFormItems: Ext.emptyFn,
    
    /**
     * returns the configuration for the relations to be created, must be overridden
     * @return {Object}
     */
    getRelationConfig: function() {
        return {};
    },
    
    /**
     * returns the selected record. may be overridden.
     * @return {Object} config for the record to edit
     */
    getRecord: function() {
        var recordId = this.searchBox.getValue(), 
            record = this.searchBox.store.getById(recordId);
        return record;
    },
    
    /**
     * is called when ok-button is pressed and edit dialog should be opened
     */
    onUpdate: function() {
        if(this.isValid()) {
            if(this.app) {
                var ms = this.app.getMainScreen();
                if(ms) {
                    var cp = ms.getCenterPanel();
                }
            }
            cp.onEditInNewWindow({actionType: 'edit'}, this.getRecord() ? this.getRecord() : this.searchBox.selectedRecord, [{
                ptype:          'addrelations_edit_dialog',
                selectionFilter: this.selectionFilter,
                addRelations:    this.addRelations,
                relationConfig:  this.getRelationConfig(),
                callingApp:      this.callingApp,
                callingModel:    this.callingModel
            }]);
            this.onCancel();
        }
    }
});/*!
 * Ext JS Library 3.3.0
 * Copyright(c) 2006-2010 Ext JS, Inc.
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ns("Ext.ux");

/**
 * @class Ext.ux.FieldLabeler
 * <p>A plugin for Field Components which renders standard Ext form wrapping and labels
 * round the Field at render time regardless of the layout of the Container.</p>
 * <p>Usage:</p>
 * <pre><code>
    {
        xtype: 'combo',
        plugins: [ Ext.ux.FieldLabeler ],
        triggerAction: 'all',
        fieldLabel: 'Select type',
        store: typeStore
    }
 * </code></pre>
 */
Ext.ux.FieldLabeler = (function(){

//  Pulls a named property down from the first ancestor Container it's found in
    function getParentProperty(propName) {
        for (var p = this.ownerCt; p; p = p.ownerCt) {
            if (p[propName]) {
                return p[propName];
            }
        }
    }

    return {

//      Add behaviour at important points in the Field's lifecycle.
        init: function(f) {
//          Replace the Field's onRender method with a sequence that calls the plugin's onRender after the Field's onRender
            f.onRender = f.onRender.createSequence(this.onRender);

//          We need to completely override the onResize method because of the complexity
            f.onResize = this.onResize;

//          Replace the Field's onDestroy method with a sequence that calls the plugin's onDestroy after the Field's onRender
            f.onDestroy = f.onDestroy.createSequence(this.onDestroy);
        },

        onRender: function() {
//          Do nothing if being rendered by a form layout
            if (this.ownerCt) {
                if (this.ownerCt.layout instanceof Ext.layout.FormLayout) {
                    return;
                }
            }

            this.resizeEl = (this.wrap || this.el).wrap({
                cls: 'x-form-element',
                style: (Ext.isIE || Ext.isOpera) ? 'position:absolute;top:0;left:0;overflow:visible' : ''
            });
            this.positionEl = this.itemCt = this.resizeEl.wrap({
                cls: 'x-form-item '
            });
            if (this.nextSibling()) {
                this.margins = {
                    top: 0,
                    right: 0,
                    bottom: this.positionEl.getMargins('b'),
                    left: 0
                };
            }
            this.actionMode = 'itemCt';

//          If our Container is hiding labels, then we're done!
            if (!Ext.isDefined(this.hideLabels)) {
                this.hideLabels = getParentProperty.call(this, "hideLabels");
            }
            if (this.hideLabels) {
                this.resizeEl.setStyle('padding-left', '0px');
                return;
            }

//          Collect the info we need to render the label from our Container.
            if (!Ext.isDefined(this.labelSeparator)) {
                this.labelSeparator = getParentProperty.call(this, "labelSeparator");
            }
            if (!Ext.isDefined(this.labelPad)) {
                this.labelPad = getParentProperty.call(this, "labelPad");
            }
            if (!Ext.isDefined(this.labelAlign)) {
                this.labelAlign = getParentProperty.call(this, "labelAlign") || 'left';
            }
            this.itemCt.addClass('x-form-label-' + this.labelAlign);

            if(this.labelAlign == 'top'){
                if (!this.labelWidth) {
                    this.labelWidth = 'auto';
                }
                this.resizeEl.setStyle('padding-left', '0px');
            } else {
                if (!Ext.isDefined(this.labelWidth)) {
                    this.labelWidth = getParentProperty.call(this, "labelWidth") || 100;
                }
                this.resizeEl.setStyle('padding-left', (this.labelWidth + (this.labelPad || 5)) + 'px');
                this.labelWidth += 'px';
            }

            this.label = this.itemCt.insertFirst({
                tag: 'label',
                cls: 'x-form-item-label',
                style: {
                    width: this.labelWidth
                },
                html: this.fieldLabel + (this.labelSeparator || ':')
            });
        },

//      private
//      Ensure the input field is sized to fit in the content area of the resizeEl (to the right of its padding-left)
//      We perform all necessary sizing here. We do NOT call the current class's onResize because we need this control
//      we skip that and go up the hierarchy to Ext.form.Field
        onResize: function(w, h) {
            Ext.form.Field.prototype.onResize.apply(this, arguments);
            w -= this.resizeEl.getPadding('l');
            if (this.getTriggerWidth) {
                this.wrap.setWidth(w);
                this.el.setWidth(w - this.getTriggerWidth());
            } else {
                this.el.setWidth(w);
            }
            if (this.el.dom.tagName.toLowerCase() == 'textarea') {
                var h = this.resizeEl.getHeight(true);
                if (!this.hideLabels && (this.labelAlign == 'top')) {
                    h -= this.label.getHeight();
                }
                this.el.setHeight(h);
            }
        },

//      private
//      Ensure that we clean up on destroy.
        onDestroy: function() {
            this.itemCt.remove();
        }
    };
})();
Ext.applyIf(Array.prototype, {
    /**
     * Returns an object with ids of records to delete, to create or to update
     *
     * @param {Array} toCompare Array to compare with
     * @return object An object with sub array properties 'toDelete', 'toCreate' and 'toUpdate'
     */
    getMigration: function(toCompare) {
        return {
            toDelete: this.diff(toCompare),
            toCreate: toCompare.diff(this),
            toUpdate: this.intersect(toCompare)
        };
    },
    
    /**
     * Returns an array containing all the entries from this array that are not present in any of the other arrays.
     * 
     * @param {Array} array1
     * @param {Array} [array2]
     * @param {Array} [...]
     */
    diff: function() {
        var allItems = [],
            diffs = [];
        
        // create an array containing all items of all args
        for (var i=0; i<arguments.length; i++) {
            allItems = allItems.concat(arguments[i]);
        }
        
        // check which item is not present in all args
        Ext.each(this, function(item) {
            if (allItems.indexOf(item) < 0) {
                diffs.push(item);
            }
        }, this);
        
        
        return diffs;
    },
    
    /**
     * simple map fn
     * 
     * @param {Function} fn
     */
    map: function(fn) {
        var map = [];
        Ext.each(this, function(v, i) {
            map.push(fn.call(this, v, i));
        }, this);
        
        return map;
    },
    
    /**
     * returns an array containing all the values of this array that are present in all the arguments.
     * 
     * @param {Array} array1
     * @param {Array} [array2]
     * @param {Array} [...]
     */
    intersect: function() {
        var allItems = [],
            intersect = [];
        
        // create an array containing all items of all args
        for (var i=0; i<arguments.length; i++) {
            allItems = allItems.concat(arguments[i]);
        }
        
        // check which item is not present in all args
        Ext.each(this, function(item) {
            if (allItems.indexOf(item) >= 0) {
                intersect.push(item);
            }
        }, this);
        
        
        return intersect;
    },
    
    /**
     * Creates a copy of this Array, filtered to contain only unique values.
     * @return {Array} The new Array containing unique values.
     */
    unique: function() {
        return Ext.unique(this);
    }
});

/*
var testArr = ["green", "red", "blue", "red"],
    diff1 = testArr.diff(["green", "yellow", "red"]),
    diff2 = testArr.diff(["green"], "yellow", "red", "blue");

if (diff1.length !== 1) console.error('Failed asserting that diff contains one entry');
if (diff1[0] !== "blue") console.error('Failed asserting that diff is "blue"');
if (diff2.length !== 0) console.error('Failed asserting that diff is empty');
*/
Ext.ns('Ext.ux');

/**
 * logging extension (only to console atm.)
 * 
 * NOTE: we extend the console object here so the console writer preserves 
 *       line number and files in its logging output!
 *       
 * @namespace   Ext.ux.log
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Ext.ux.log = window.console || {};

Ext.apply(Ext.ux.log, {
    /**
     * @cfg {Number} prio (defaults to 7)
     */
    PRIO: 7,
    
    /**
     * possible log levels
     * @type Object
     */
    priorities: {
        EMERG:    0,  // Emergency: system is unusable
        ALERT:    1,  // Alert: action must be taken immediately
        CRIT:     2,  // Critical: critical conditions
        ERR:      3,  // Error: error conditions
        ERROR:    3,  // Error: error conditions
        WARN:     4,  // Warning: warning conditions
        NOTICE:   5,  // Notice: normal but significant condition
        INFO:     6,  // Informational: informational messages
        DEBUG:    7,   // Debug: debug messages
        TRACE:    8   // Debug: debug messages
        
    },
    
    // only console writer atm.
    prioLogFnMap : {
        EMERG:    console.error,
        ALERT:    console.error,
        CRIT:     console.error,
        ERR:      console.error,
        ERROR:    console.error,
        WARN:     console.warn,
        NOTICE:   console.info,
        INFO:     console.info,
        DEBUG:    console.log,
        TRACE:    console.log
    },
    
    /**
     * returns current logging priority
     * 
     * @return {}
     */
    getPrio: function() {
        return this.PRIO;
    },
    
    /**
     * set logging priority
     * 
     * @namespace Ext.ux.log
     * @param {Number} prio
     */
    setPrio: function(prio) {
        this.PRIO = prio;
        for (var name in this.priorities) {
            this[name] = this[name.toLowerCase()] = prio >= this.priorities[name] ? this.prioLogFnMap[name] : Ext.emptyFn;
        }
    }
});

// init Ext.ux.log
Ext.onReady(function() {
    Ext.ux.log.setPrio(Ext.LOGLEVEL);
    Ext.ux.log.debug('logger initialized');
});
/*
 * Tine 2.0
 * 
 * @package     Ext
 * @subpackage  ux
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Ext.ux');

/**
 * @namespace   Ext.ux
 * @class       Ext.ux.ConnectionStatus
 * @constructor
 */
Ext.ux.ConnectionStatus = function(config) {
    Ext.apply(this, config);
    Ext.ux.ConnectionStatus.superclass.constructor.call(this);
    
};

Ext.extend(Ext.ux.ConnectionStatus, Ext.Button, {
    /**
     * @cfg {boolean} showIcon
     */
    showIcon: true,
    
    /**
     * @property {String}
     */
    status: 'unknown',
    /**
     * @private
     */
    iconCls: 'x-ux-connectionstatus-unknown',
    
    /**
     * @property {bool} Browser supports online offline detection
     */
    isSuppoeted: null,
    
    /**
     * @private
     */
    handler: function() {
        if (this.isSuppoeted){
            this.toggleStatus();
        }
    },
    
    initComponent: function() {
        Ext.ux.ConnectionStatus.superclass.initComponent.call(this);
        
        this.onlineText = '(' + _('online') + ')';
        this.offlineText = '(' + _('offline') + ')';
        this.unknownText = '(' + _('unknown') + ')';
        
        // M$ IE has not online/offline events yet
        if (Ext.isIE6 || Ext.isIE7 || ! window.navigator || window.navigator.onLine === undefined) {
            this.setStatus('unknown');
            this.isSupported = false;
        } else {
            this.setStatus(window.navigator.onLine ? 'online' : 'offline');
            this.isSupported = true;
        }
       
        //if (Ext.isGecko3) {
            Ext.getBody().on('offline', function() {
                this.setStatus('offline', true);
            }, this);
            
            Ext.getBody().on('online', function() {
                this.setStatus('online', true);
            }, this);
        //}
    },

    /**
     * toggles online status
     */
    toggleStatus: function() {
        this.setStatus(this.status == 'online' ? 'offline' : 'online');
    },
    
    setIconClass: function(iconCls) {
        this.supr().setIconClass.call(this, this.showIcon ? iconCls : '');
    },
    
    /**
     * sets online status
     */
    setStatus: function(status) {
        switch (status) {
            case 'online':
                this.status = status;
                this.setText(this.onlineText);
                this.setIconClass('x-ux-connectionstatus-online');
                break;
            case 'offline':
                this.status = status;
                this.setText(this.offlineText);
                this.setIconClass('x-ux-connectionstatus-offline');
                break;
            case 'unknown':
                this.status = status;
                this.setText(this.unknownText);
                this.setIconClass('x-ux-connectionstatus-unknown');
                // as HTML implementation status is verry poor atm. don't bother the user with this state
                this.hide();
                break;
            default:
                console.error('no such status:"' + status + '"');
                break;
        }
    }
});
/*
 * Tine 2.0
 * 
 * @license     MIT, BSD, and GPL
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Ext.ux.direct');

/**
 * @namespace   Ext.ux.direct
 * @class       Ext.ux.direct.JsonRpcProvider
 * @extends     Ext.direct.RemotingProvider
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * Ext.Direct provider for seamless integration with JSON RPC servers
 * 
 *  Ext.Direct.addProvider(Ext.apply(Ext.app.JSONRPC_API, {
        'type'     : 'jsonrpcprovider',
        'url'      : Ext.app.JSONRPC_API
    }));
 * 
 */
Ext.ux.direct.JsonRpcProvider = Ext.extend(Ext.direct.RemotingProvider, {
    
    /**
     * @cfg {Boolean} paramsAsHash
     */
    //paramsAsHash: true,
    
    /**
     * @cfg {Boolean} useNamedParams
     */
    useNamedParams: false,
    
    // private
    initAPI : function() {
        for (var method in this.services){
            var mparts = method.split('.');
            var cls = this.namespace[mparts[0]] || (this.namespace[mparts[0]] = {});
            cls[mparts[1]] = this.createMethod(mparts[0], Ext.apply(this.services[method], {
                name: mparts[1],
                len: this.services[method].parameters.length
            }));
        }
    },
    
    // private
    doCall : function(c, m, args) {
        // support named/hashed parameters e.g. from DirectProxy
        if (args[args.length-1].paramsAsHash) {
            var o = args.shift();
            for (var i = 0; i < m.parameters.length; i++) {
                args.splice(i,0, o[m.parameters[i].name]);
            }
        }
        
        return Ext.ux.direct.JsonRpcProvider.superclass.doCall.call(this, c, m, args);
    },
    
    // private
    getCallData: function(t){
        var method = t.action + '.' + t.method,
            m  = t.provider.services[method],
            data = t.data || [];
        
        if (this.useNamedParams) {
            var data = {};
            Ext.each(m.parameters, function(param, i) {
                data[param['name']] = t.data[i];
                
                /* NOTE: Ext.Direct and the automatically creaeted DirectFn's don't support optional params, cause 
                 * callback and scope are expected on fixed positions!
                 
                var value = typeof t.data[i] !== 'function' ? t.data[i] : undefined;
                
                if (value === undefined && ! param.optional) {
                    value = param['default'];
                }
                
                if (value !== undefined) {
                    data[param['name']] = value;
                }
                */
            }, this);
        }
        
        return {
            jsonrpc: '2.0',
            method: method,
            params: data,
            id: t.tid
        };
    },
    
    // private
    onData: function(opt, success, xhr) {
        var rs = [].concat(Ext.decode(xhr.responseText));
        
        xhr.responseText = [];
        Ext.each(rs, function(rpcresponse, i) {
            xhr.responseText[i] = {
                type: rpcresponse.result ? 'rpc' : 'exception',
                result: rpcresponse.result,
                tid: rpcresponse.id,
                error: rpcresponse.error
            };
            
            if (xhr.responseText[i].type === 'rpc') {
                delete xhr.responseText.error;
            }
        });
        
        return Ext.ux.direct.JsonRpcProvider.superclass.onData.apply(this, arguments);
    }
});

Ext.Direct.PROVIDERS['jsonrpcprovider'] = Ext.ux.direct.JsonRpcProvider;
/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
Ext.ns('Ext.ux');

Ext.ux.DatePickerWeekPlugin = function(config) {
    Ext.apply(this, config || {});
    
};

Ext.ux.DatePickerWeekPlugin.prototype = {
    /**
     * @cfg {String} weekHeaderString
     */
    weekHeaderString: 'WK',
    
    init: function(picker) {
        picker.onRender = picker.onRender.createSequence(this.onRender, picker);
        picker.update = picker.update.createSequence(this.update, picker);
        picker.handleDateClick = picker.handleDateClick.createSequence(this.handleDateClick, picker);
        picker.showMonthPicker = picker.showMonthPicker.createInterceptor(this.inspectMonthPickerClick, picker);
        picker.weekHeaderString = this.weekHeaderString;
        
        picker.getRowEl = this.getRowEl.createDelegate(picker);
        picker.selectWeek = this.selectWeek.createDelegate(picker);
        picker.clearSelection = this.clearSelection.createDelegate(picker);
        picker.onWkCellOver = this.onWkCellOver.createDelegate(picker);
        picker.onWkCellOut = this.onWkCellOut.createDelegate(picker);
    },
    
    onRender: function() {
        var innerCal = Ext.DomQuery.selectNode('table[class=x-date-inner]', this.getEl().dom);
        var trs = Ext.DomQuery.select('tr', innerCal);
        for (var el, i=0; i<trs.length; i++) {
            el = Ext.DomHelper.insertFirst(trs[i], i==0 ? '<th class="x-date-picker-wk-hd">' + this.weekHeaderString + '</th>' : '<td class="x-date-picker-wk"><a class="x-date-date" tabindex="1" hidefocus="on" href="#"><em><span>'+ i +'</span></em></td>');
            Ext.get(el).addListener({
                scope: this,
                mouseover: this.onWkCellOver,
                mouseout: this.onWkCellOut
            });
        }
        
        // update again;
        this.update(this.value);
        
        // shit, datePicker is not on BaxComponent ;-(
        //this.picker.getEl().container.on('resize', function() {
        //    console.log(this.picker.getEl());
        //}, this);
    },
    
    onWkCellOver: function(e, t) {
        var tr = e.getTarget('tr', 10 , true);
        tr.addClass('x-date-picker-wk-wkrowover');
    },
    
    onWkCellOut: function(e, t) {
        var tr = e.getTarget('tr', 10, true);
        tr.removeClass('x-date-picker-wk-wkrowover')
    },
    
    update: function(date, forceRefresh, weekNumber){
        var firstOfMonth = date.getFirstDateOfMonth(),
            startingPos = firstOfMonth.getDay()-this.startDay;

        if(startingPos < 0){
            startingPos += 7;
        }
        
        // NOTE "+1" to ensure ISO week!
        var startDate = firstOfMonth.add(Date.DAY, -1*startingPos + 1);
        
        var wkCells = Ext.DomQuery.select('td[class=x-date-picker-wk]', this.getEl().dom);
        for (var i=0, id; i<wkCells.length; i++) {
            id = Ext.id() + ':' + startDate.add(Date.DAY, i*7).format('Y-m-d');
            wkCells[i].firstChild.firstChild.innerHTML = '<span id="' + id + '">' + startDate.add(Date.DAY, i*7).getWeekOfYear() + '</span>';
        }
        
        if (weekNumber) {
            this.clearSelection();
            
            if (! Ext.isArray(weekNumber)) {
                weekNumber = [weekNumber];
            }
            
            for (var i=0, row; i<weekNumber.length; i++) {
                row = this.getRowEl(weekNumber[i]);
                this.selectWeek(row);
            }
        }
        
    },
    
    handleDateClick: function(e) {
        target = e.getTarget('td[class=x-date-picker-wk]');
        if (target) {
            var row = target.parentNode;
            var weekNumber = target.firstChild.firstChild.firstChild.innerHTML;
            
            if (Ext.DomQuery.select('td[class=x-date-prevday]', row).length > 3 ) {
                this.showPrevMonth()
            } else if (Ext.DomQuery.select('td[class=x-date-nextday]', row).length > 4) {
                this.showNextMonth()
            } 
            
            // get row again
            row = this.getRowEl(weekNumber);
            
            // set new date value
            var value = Date.parseDate(row.firstChild.firstChild.firstChild.firstChild.id.split(':')[1], 'Y-m-d');
            this.setValue(value);
            
            // set selection
            this.clearSelection();
            this.selectWeek(row);
            
            this.fireEvent("select", this, this.value, weekNumber);
        }
    },
    
    getRowEl: function(weekNumber) {
        var wktds = Ext.DomQuery.select('td[class=x-date-picker-wk]', this.getEl().dom);
        for (var i=0; i<wktds.length; i++) {
            if (wktds[i].firstChild.firstChild.firstChild.innerHTML == weekNumber) {
                var rowEl = wktds[i].parentNode;
                break;
            }
        }
        
        return rowEl;
    },
    
    clearSelection: function() {
        var innerCal = Ext.DomQuery.selectNode('table[class=x-date-inner]', this.getEl().dom);
        var dates = Ext.DomQuery.select('td', innerCal);
        for (var i=0; i<dates.length; i++) {
            Ext.fly(dates[i]).removeClass('x-date-selected');
        }
    },
    
    /**
     * inspects month picker onClick event method
     * return false to cancel original onClick handler
     */
    inspectMonthPickerClick: Ext.emptyFn,
    
    selectWeek: function(rowEl) {
        if (rowEl) {
            // set new selection
            for (var j=1; j<rowEl.childNodes.length; j++) {
                Ext.fly(rowEl.childNodes[j]).addClass('x-date-selected');
            }
        }
    }
};
/*
 * Tine 2.0
 * 
 * @license     New BSD License
 * @author      www.steinarmyhre.com
 *
 */
 
Ext.ns('Ext.ux');

/**
 * @namespace   Ext.ux
 * @class       Ext.ux.ButtonLockedToggle & Ext.ux.tbButtonLockedToggle
 * @extends     Ext.Button
 * @description
 * The normal button, when enableToggle is used and toggleGroup is set correctly, still allows the user
 * to toggle off the toggled button by pressing on it. This class overrides the toggle-method so that
 * the toggled button is impossible to 'untoggle' other than programmatically or as a reaction
 * to any of the other buttons in the group getting toggled on.
 *
 * Toggle is by the way a very strange word when you repeat it enough.
 *
 * @author www.steinarmyhre.com
 * @constructor
 * Identical to Ext.Button and/or Ext.Toolbar.Button except that enableToggle is true by default.
 * @param (Object/Array) config A config object
 */
Ext.ux.ButtonLockedToggle = Ext.extend(Ext.Button,{
    enableToggle: true,

    toggle: function(state){
        if(state === undefined && this.pressed) {
            return;
        }
        state = state === undefined ? !this.pressed : state;
        if(state != this.pressed){
            if(state){
                if (this.el) this.el.addClass("x-btn-pressed");
                this.pressed = true;
                this.fireEvent("toggle", this, true);
            }else{
                if (this.el) this.el.removeClass("x-btn-pressed");
                this.pressed = false;
                this.fireEvent("toggle", this, false);
            }
            if(this.toggleHandler){
                this.toggleHandler.call(this.scope || this, this, state);
            }
        }
    }
});

//Ext.ux.tbButtonLockedToggle = Ext.extend(Ext.Toolbar.Button, Ext.ux.ButtonLockedToggle);
Ext.reg('btnlockedtoggle', Ext.ux.ButtonLockedToggle);
Ext.reg('tbbtnlockedtoggle', Ext.ux.ButtonLockedToggle);
//Ext.reg('tbbtnlockedtoggle', Ext.ux.tbButtonLockedToggle);
/*
 * Tine 2.0
 * 
 * @package     Ext
 * @subpackage  ux
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

/**
 * increase depth of cell selector depth for event delegation (defaults to 4) 
 */
if (Ext.grid.GridView.prototype.cellSelectorDepth < 6) {
    Ext.grid.GridView.prototype.cellSelectorDepth = 6;
}

Ext.ns('Ext.ux');

/**
 * Percentage select combo box
 * 
 * @namespace   Ext.ux
 * @class       Ext.ux.PercentCombo
 * @extends     Ext.form.ComboBox
 */
Ext.ux.PercentCombo = Ext.extend(Ext.form.ComboBox, {
    /**
     * @cfg {bool} autoExpand Autoexpand comboBox on focus.
     */
    autoExpand: false,
    /**
     * @cfg {bool} blurOnSelect blurs combobox when item gets selected
     */
    blurOnSelect: false,
    
    displayField: 'value',
    valueField: 'key',
    mode: 'local',
    triggerAction: 'all',
    emptyText: 'percent ...',
    lazyInit: false,
    forceSelection: true,
    
    //private
    initComponent: function(){
        Ext.ux.PercentCombo.superclass.initComponent.call(this);
        // allways set a default
        if(!this.value) {
            this.value = 0;
        }
            
        this.store = new Ext.data.SimpleStore({
            fields: ['key','value'],
            data: [
                    ['0',    '0%'],
                    ['10',  '10%'],
                    ['20',  '20%'],
                    ['30',  '30%'],
                    ['40',  '40%'],
                    ['50',  '50%'],
                    ['60',  '60%'],
                    ['70',  '70%'],
                    ['80',  '80%'],
                    ['90',  '90%'],
                    ['100','100%']
                ]
        });
        
        if (this.autoExpand) {
            this.lazyInit = false;
            this.on('focus', function(){
                this.selectByValue(this.getValue());
                this.onTriggerClick();
            });
        }
        
        if (this.blurOnSelect){
            this.on('select', function(){
                this.fireEvent('blur', this);
            }, this);
        }
    },
    
    setValue: function(value) {
        value = value ? value : 0;
        Ext.ux.PercentCombo.superclass.setValue.call(this, value);
    }
});
Ext.reg('extuxpercentcombo', Ext.ux.PercentCombo);

/**
 * Renders a percentage value to a percentage bar
 * @constructor
 */
Ext.ux.PercentRenderer = function(percent) {
    if (! Ext.ux.PercentRenderer.template) {
        Ext.ux.PercentRenderer.template = new Ext.XTemplate(
            '<div class="x-progress-wrap PercentRenderer">',
            '<div class="x-progress-inner PercentRenderer">',
                '<div class="x-progress-bar PercentRenderer" style="width:{percent}%">',
                    '<div class="PercentRendererText PercentRenderer">',
                        '<div>{percent}%</div>',
                    '</div>',
                '</div>',
                '<div class="x-progress-text x-progress-text-back PercentRenderer">',
                    '<div>&#160;</div>',
                '</div>',
            '</div>',
        '</div>'
        ).compile();
    }
    
    return Ext.ux.PercentRenderer.template.apply({percent: percent});
};

/**
 * Renders a percentage value to a percentage bar / uploadrow
 * @constructor
 */
Ext.ux.PercentRendererWithName = function(value, metadata, record) {
  
    var metaStyle = '';
    
    if(record.fileRecord) {
        record = record.fileRecord;
    }
    
    if(record.get('type') == 'folder') {
        metadata.css = 'x-tinebase-typefolder';
    }
    else {

        var contenttype =  record.get('contenttype');
        if(contenttype) {
            var iconClass = contenttype.replace("/", "-");
            metadata.css = iconClass + '_16x16 ';
        }
               
        metadata.css += 'standardFileClass_16x16';
    }
    
    
    if (!Tine.Tinebase.uploadManager.isHtml5ChunkedUpload()) {

        var fileName = value;
        if (typeof value == 'object') {
            fileName = value.name;
        } 
    
        if(record.get('status') == 'uploading') {
            metadata.css = 'x-tinebase-uploadrow';
        }
        
        return fileName;
    }
    
    if (! Ext.ux.PercentRendererWithName.template) {
        Ext.ux.PercentRendererWithName.template = new Ext.XTemplate(
            '<div class="x-progress-wrap PercentRenderer" style="{display}">',
            '<div class="x-progress-inner PercentRenderer">',
                '<div class="x-progress-bar PercentRenderer" style="width:{percent}%;{additionalStyle}">',
                    '<div class="PercentRendererText PercentRenderer">',
                         '{fileName}',
                    '</div>',
                '</div>',
                '<div class="x-progress-text x-progress-text-back PercentRenderer">',
                    '<div>&#160;</div>',
                '</div>',
            '</div>',
        '</div>'
        ).compile();
    }
    
    if(value == undefined) {
        return '';
    }              
    
    var fileName = value;

    if (typeof value == 'object') {
        fileName = value.name;
    }
    
    var percent = record.get('progress');

    var additionalStyle = '';
    if(record.get('status') == 'paused' && percent < 100) {
        fileName = _('(paused)') + '&#160;&#160;' + fileName;
        additionalStyle = 'background-image: url(\'styles/images/tine20/progress/progress-bg-y.gif\') !important;';
    }
       
    var display = 'width:0px';
    if(percent > -1 && percent < 100) {
        display = '';
        var renderedField = Ext.ux.PercentRendererWithName.template.apply({percent: percent, display: display, fileName: fileName
            , additionalStyle: additionalStyle}) ;
        return renderedField;
    }
    else {
        return fileName;
    }
    

};
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.namespace('Tine.Tinebase');

/**
 * Tinebase Application Starter
 * 
 * @namespace   Tine.Tinebase
 * @function    Tine.MailAccounting.MailAggregateGridPanel
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 */
Tine.Tinebase.ApplicationStarter = {
    
    /**
     * the applictions the user has access to
     * @type 
     */
    userApplications: null,
    
    /**
     * type mapping
     * @type {Object}
     */
    types: {
        'date':     'date',
        'datetime': 'date',
        'time':     'date',
        'string':   'string',
        'boolean':  'bool',
        'integer':  'int',
        'float':    'float'
    },
    
    /**
     * maps php filters to value types of js filter
     * @type {Object}
     */
    filters: {
        'Tinebase_Model_Filter_Id': null,
        'Tinebase_Model_Filter_Text': 'string',
        'Tinebase_Model_Filter_Date': 'date',
        'Tinebase_Model_Filter_DateTime': 'date',
        'Tinebase_Model_Filter_Bool': 'bool',
        'Tinebase_Model_Filter_ForeignId': 'foreign',
        'Tinebase_Model_Filter_Int': 'number',
        'Tinebase_Model_Filter_User': 'user'
    },
    
    /**
     * initializes the starter
     */
    init: function() {
        // Wait until appmgr is initialized
        if (!Tine.Tinebase.hasOwnProperty('appMgr')) {
            this.init.defer(100, this);
            return;
        }
        if(!this.userApplications) {
            this.userApplications = Tine.Tinebase.registry.get('userApplications');
            this.createStructure(true)
        }
    },
    
    /**
     * returns the field
     * @param {Object} fieldconfig
     * @return {Object}
     */
    getField: function(fieldconfig, key) {     
        // default type is auto
        var field = {name: key};
        
        if (fieldconfig.type) {
            // add pre defined type
            field.type = this.types[fieldconfig.type];
            switch (fieldconfig.type) {
                case 'datetime':
                    field.dateFormat = Date.patterns.ISO8601Long;
                    break;
                case 'date':
                    field.dateFormat = Date.patterns.ISO8601Long;
                    break;
                case 'time':
                    field.dateFormat = Date.patterns.ISO8601Time;
                case 'foreign':
                    field.type = fieldconfig.options.app + '.' + fieldconfig.options.model;
                    break;
            }
            // allow overwriting date pattern in model
            if (fieldconfig.hasOwnProperty('dateFormat')) {
                field.dateFormat = fieldconfig.dateFormat;
            }
        }
        
        // TODO: create field registry, add fields here
        return field;
    },
    /**
     * returns the grid renderer
     * @param {Object} config
     * @param {String} field
     * @return {Function}
     */
    getGridRenderer: function(config, field, appName, modelName) {
        var gridRenderer = null;
        if(config && field) {
            switch (config.type) {
                case 'foreign':
                    gridRenderer = function(value, row, record) {
                        var foreignRecordClass = Tine[config.options.app].Model[config.options.model];
                        var titleProperty = foreignRecordClass.getMeta('titleProperty');
                        return record.get(field) ? Ext.util.Format.htmlEncode(record.get(field)[titleProperty]) : '';
                    }
                    break;
                case 'integer':
                    if(config.hasOwnProperty('specialType')) {
                        var useDecimal = (config.specialType == 'bytes1000');
                        gridRenderer = function(a,b,c) {
                            return Tine.Tinebase.common.byteRenderer(a, b, c, 2, useDecimal);
                        }
                    }
                    break;
                case 'user':
                    gridRenderer = Tine.Tinebase.common.usernameRenderer;
                    break;
                case 'keyfield': 
                    gridRenderer = Tine.Tinebase.widgets.keyfield.Renderer.get(appName, config.name);
                    break;
                case 'date':
                    gridRenderer = Tine.Tinebase.common.dateRenderer;
                    break;
                case 'datetime':
                    gridRenderer = Tine.Tinebase.common.dateTimeRenderer;
                    break;
                 case 'time':
                    gridRenderer = Tine.Tinebase.common.timeRenderer;
                    break;
                case 'tag':
                    gridRenderer = Tine.Tinebase.common.tagsRenderer;
                    break;
                case 'container':
                    gridRenderer = Tine.Tinebase.common.containerRenderer;
                    break;
                case 'boolean':
                    gridRenderer = Tine.Tinebase.common.booleanRenderer;
                    break;
                 default:
                    gridRenderer = function(value) {
                        return Ext.util.Format.htmlEncode(value);
                    }
                }
           }
        return gridRenderer;
    },

    /**
     * returns filter
     * @param {String} key
     * @param {Object} filterconfig
     * @param {Object} fieldconfig
     * @return {Object}
     */
    getFilter: function(key, filterconfig, fieldconfig, appName, modelName) {
        // take field label if no filterlabel is defined
        var label = (filterconfig && filterconfig.label) ? filterconfig.label : (fieldconfig && fieldconfig.label) ? fieldconfig.label : null;
        var app = Tine.Tinebase.appMgr.get(appName);
        
        if (! label && (key != 'query')) {
            return null;
        }
        // prepare filter
        var filter = {
            label: app.i18n._(label),
            field: key
        };

        if (filterconfig) {
            // if js filter is defined in filterconfig.options, take this and return
            if(filterconfig.hasOwnProperty('options') && (filterconfig.options.hasOwnProperty('jsFilterType') || filterconfig.options.hasOwnProperty('jsFilterValueType'))) {
                if(filterconfig.options.jsFilterType) {
                    filter.filtertype = filterconfig.options.jsFilterType;
                }
                if(filterconfig.options.jsFilterValueType) {
                    filter.valueType = filterconfig.options.jsFilterValueType;
                }
                return filter;
            } 
            
            switch (filterconfig.filter) {
                case 'Tinebase_Model_Filter_Bool':
                    filter.valueType = this.filters[filterconfig.filter];
                    filter.defaultValue = false;
                    break;
                case 'Tinebase_Model_Filter_ForeignId':
                    // create generic foreign id filter
                    var filterclass = Ext.extend(Tine.widgets.grid.ForeignRecordFilter, {
                        foreignRecordClass: fieldconfig.options.app + '.' + fieldconfig.options.model,
                        linkType: 'foreignId',
                        ownField: key
                    });
                    var a = appName.toLowerCase();
                    var b = fieldconfig.options.model.toLowerCase();
                    var fc = a + '.' + b; 
                    Tine.widgets.grid.FilterToolbar.FILTERS[fc] = filterclass;
                    filter = {filtertype: fc};
                    break;
                case 'Tinebase_Model_Filter_Tag':
                    filter = {filtertype: 'tinebase.tag', app: appName};
                    break;
                case 'Tinebase_Model_Filter_Container':
                    filter = {filtertype: 'tine.widget.container.filtermodel', app: appName, recordClass: appName + '.' + modelName};
                    break;
                case 'Tinebase_Model_Filter_Query':
                    filter = Ext.apply(filter, {label: _('Quick search'), operators: ['contains']});
                    break;
                default:
                    if(this.filters[filterconfig.filter]) {  // use pre-defined default filter (this.filters)
                        filter.valueType = this.filters[filterconfig.filter];
                    } else {    // try to find registered filter
                        var keys = filterconfig.filter.split('_'),
                            filterkey = keys[0].toLowerCase() + '.' + keys[2].toLowerCase();
                            filterkey = filterkey.replace(/filter/g, '');
            
                        if(Tine.widgets.grid.FilterToolbar.FILTERS[filterkey]) {
                            filter = {filtertype: filterkey};
                        } else { // set to null if no filter could be found
                            filter = null;
                        }
                    }
                }
            }
        return filter;
    },
    
    /**
     * if application starter should be used, here the js contents are (pre-)created
     */
    createStructure: function(initial) {
        var start = new Date();
        Ext.each(this.userApplications, function(app) {
            var appName = app.name;
            Ext.namespace('Tine.' + appName);
            if(Tine[appName].registry && Tine[appName].registry.get('models')) {
                Tine[appName].isAuto = true;
                var models = Tine[appName].registry.get('models');
                var contentTypes = [];

                // create translation
                Tine[appName].i18n = new Locale.Gettext();
                Tine[appName].i18n.textdomain(appName);
                
                // iterate models of this app
                Ext.iterate(models, function(model, config) {
                    Ext.namespace('Tine.' + appName, 'Tine.' + appName + '.Model');
                    var modelArrayName = model + 'Array';
                    var rA = [];
                    contentTypes.push(config);
                    
                    var defaultData = {},
                        filterModel = [];
                    // iterate record fields
                    Ext.each(config.keys, function(key) {
                        // add field to model array
                        rA.push(this.getField(config.fields[key], key));
                        // create default data
                        defaultData[key] = config.fields[key].standard ? config.fields[key].standard : null;
                        // if field config has label, create grid renderer
                        if(config.fields[key].label) {
                            // register grid renderer
                            if(initial) {
                                var renderer = this.getGridRenderer(config.fields[key], key, appName, model);
                                if(renderer) {
                                    Tine.widgets.grid.RendererManager.register(appName, model, key, renderer);
                                }
                            }
                        }
                    }, this);
                    
                    if(config.hasOwnProperty('meta')) {
                        // relations
                        if(config.meta.hasRelations) {
                            rA.push('relations');
                        }
                        // tags
                        if(config.meta.hasTags) {
                            rA.push('tags');
                        }
                        // customfields
                        if(config.meta.hasCustomFields || config.meta.hasCustomfields) {
                            rA.push('customfields');
                        }
                        // notes
                        if(config.meta.hasNotes) {
                            rA.push('notes');
                        }
                        Ext.iterate(config.filter, function(key, filter) {
                            // create Filter Model
                            var f = this.getFilter(key, filter, config.fields[key], appName, model);
                            if (f) {
                                filterModel.push(f);
                            }
                        }, this);
                    }
                    // add generic fields if modlog is active
                    Tine[appName].Model[modelArrayName] = (config.meta && config.meta.useModlog) ? Tine.Tinebase.Model.genericFields.concat(rA) : rA;
                    
                    // create model
                    if(! Tine[appName].Model.hasOwnProperty(model)) {
                        Tine[appName].Model[model] = Tine.Tinebase.data.Record.create(Tine[appName].Model[modelArrayName], Ext.apply(config.meta ? config.meta : {}, {
                            appName: appName,
                            modelName: model
                        }));
                    }
                    Ext.namespace('Tine.' + appName);
                    // create recordProxy
                    var recordProxyName = model.toLowerCase() + 'Backend';
                    if(! Tine[appName].hasOwnProperty(recordProxyName)) {
                        Tine[appName][recordProxyName] = new Tine.Tinebase.data.RecordProxy({
                            appName: appName,
                            modelName: model,
                            recordClass: Tine[appName].Model[model]
                        });
                    }

                    // create container tree panel, if needed
                    var containerTreePanelName = model + 'TreePanel';
                    if(! Tine[appName].hasOwnProperty(containerTreePanelName)) {
                        Tine[appName][containerTreePanelName] = Ext.extend(Tine.widgets.container.TreePanel, {
                            filterMode: 'filterToolbar',
                            recordClass: Tine[appName].Model[model]
                        });
                    }
                    
                    // create default data
                    if(config.meta) {
                        // get default container if needed
                        if(config.meta.containerProperty) {
                            var defaultContainer = Tine[appName].registry.get('default' + model + 'Container');
                            if (defaultContainer) {
                                defaultData[config.meta.containerProperty] = defaultContainer;
                            }
                        }
                        
                        // add notes if needed
                        if(config.meta.hasNotes) {
                            defaultData['notes'] = [];
                        }
                        // add tags if needed
                        if(config.meta.hasTags) {
                            defaultData['tags'] = [];
                        }
                        // add customfields if needed
                        if(config.meta.hasCustomFields) {
                            defaultData['customfields'] = {};
                        }
                        // overwrite function
                        Tine[appName].Model[model].getDefaultData = function() {
                            if(!dd) var dd = Ext.decode(Ext.encode(defaultData));
                            return dd;
                        };
                        Tine[appName].Model[model].getDefaultData();
                    }
                    
                    // create the filter model
                    if (!Ext.isFunction(Tine[appName].Model[model].getFilterModel)) {
                        Tine[appName].Model[model].getFilterModel = function() {
                            if(!pF) var pF = Ext.decode(Ext.encode(filterModel));
                            return pF;
                        };
                        Tine[appName].Model[model].getFilterModel();
                    }

                    // create filter panel
                    var filterPanelName = model + 'FilterPanel';
                    if (! Tine[appName].hasOwnProperty(filterPanelName)) {
                        Tine[appName][filterPanelName] = function(config) {
                            Ext.apply(this, config);
                            Tine[appName][filterPanelName].superclass.constructor.call(this);
                        };
                        Ext.extend(Tine[appName][filterPanelName], Tine.widgets.persistentfilter.PickerPanel);
                    }

                    // create main screen
                    if(! Tine[appName].hasOwnProperty('MainScreen')) {
                        Tine[appName].MainScreen = Ext.extend(Tine.widgets.MainScreen, {
                            app: appName,
                            contentTypes: contentTypes,
                            activeContentType: model
                        });
                    }
                    
                    var editDialogName = model + 'EditDialog';
                    // create editDialog openWindow function only when edit dialog exists
                    if(Tine[appName].hasOwnProperty(editDialogName)) {
                        if(config.meta.containerProperty) {
                            Tine[appName][editDialogName].prototype.showContainerSelector = true;
                        }
                        if(!Ext.isFunction(Tine[appName][editDialogName].openWindow)) {
                            Tine[appName][editDialogName].openWindow  = function (cfg) {
                                var id = (cfg.record && cfg.record.id) ? cfg.record.id : 0;
                                var window = Tine.WindowFactory.getWindow({
                                    width: Tine[appName][editDialogName].prototype.windowWidth ? Tine[appName][editDialogName].prototype.windowWidth : 600,
                                    height: Tine[appName][editDialogName].prototype.windowHeight ? Tine[appName][editDialogName].prototype.windowHeight : 230,
                                    name: Tine[appName][editDialogName].prototype.windowNamePrefix + id,
                                    contentPanelConstructor: 'Tine.' + appName + '.' + editDialogName,
                                    contentPanelConstructorConfig: cfg
                                });
                                return window;
                            };
                        }
                    }
                    // create Gridpanel
                    var gridPanelName = model + 'GridPanel';
                    if (! Tine[appName].hasOwnProperty(gridPanelName)) {
                        Tine[appName][gridPanelName] = Ext.extend(Tine.widgets.grid.GridPanel, {
                            modelConfig: config,
                            app: Tine[appName], 
                            recordProxy: Tine[appName][recordProxyName],
                            recordClass: Tine[appName].Model[model]
                        });
                    } else {
                         Ext.apply(Tine[appName][gridPanelName].prototype, {
                            modelConfig: config,
                            app: Tine[appName], 
                            recordProxy: Tine[appName][recordProxyName],
                            recordClass: Tine[appName].Model[model]
                        });
                    }
                }, this);
            }
        }, this);
        
        var stop = new Date();
    }
}/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux');

/**
 * Class for handling of native browser popup window.
 * <p>This class is intended to make the usage of a native popup window as easy as dealing with a modal window.<p>
 * <p>Example usage:</p>
 * <pre><code>
 var win = new Ext.ux.PopupWindow({
     name: 'TasksEditWindow',
     width: 700,
     height: 300,
     url:index.php?method=Tasks.editTask&taskId=5
 });
 * </code></pre>
 * 
 * @namespace   Ext.ux
 * @class       Ext.ux.PopupWindow
 * @extends     Ext.Component
 */
Ext.ux.PopupWindow = function(config) {
    Ext.apply(this, config);
    Ext.ux.PopupWindow.superclass.constructor.call(this);
};

Ext.extend(Ext.ux.PopupWindow, Ext.Component, {
    /**
     * @cfg    {String}
     * @param  {String} url
     * @desc   url to open
     */
    url: null,
    /**
     * @cfg {String} internal name of new window
     */
    name: 'new window',
    /**
     * @cfg {Int} width of new window
     */
    width: 500,
    /**
     * @cfg {Int} height of new window
     */
    height: 500,
    /**
     * @cfg {Bolean}
     */
    modal: false,
    /**
     * @cfg {String}
     */
    layout: 'fit',
    /**
     * @cfg {String}
     */
    title: null,
    /**
     * @cfg {String} Name of a constructor to create item property
     */
    contentPanelConstructor: null,
    /**
     * @cfg {Object} Config object to pass to itemContructor
     */
    contentPanelConstructorConfig: {},
    /**
     * @property {Browser Window}
     */
    popup: null,
    /**
     * @property {Ext.ux.PopupWindowMgr}
     */
    windowManager: null,
    
    /**
     * @private
     */
    initComponent: function(){
        if (! this.title) {
            this.title = Tine.title;
        }
        
        this.windowManager = Ext.ux.PopupWindowMgr;
        Ext.ux.PopupWindow.superclass.initComponent.call(this);
        
        //limit the window size
        this.width = Math.min(screen.availWidth, this.width);
        this.height = Math.min(screen.availHeight, this.height);

        // open popup window first to save time
        this.popup = Tine.Tinebase.common.openWindow(this.name, this.url, this.width, this.height);
        
        //. register window ( in fact register complete PopupWindow )
        this.windowManager.register(this);
        
        // does not work on reload!
        //this.popup.PopupWindow = this;
        
        // strange problems in FF
        //this.injectFramework(this.popup);

        this.addEvents({
            /**
             * @event beforecolse
             * @desc Fires before the Window is closed. A handler can return false to cancel the close.
             * @param {Ext.ux.PopupWindow}
             */
            "beforeclose" : true,
            /**
             * @event render
             * @desc  Fires after the viewport in the popup window is rendered
             * @param {Ext.ux.PopupWindow} 
             */
            "render" : true,
            /**
             * @event close
             * @desc  Fired, when the window got closed
             */
            "close" : true
        });
        
        // NOTE: Do not register unregister with this events, 
        //       as it would be broken on window reloads!
        /*
        if (this.popup.addEventListener) {
            this.popup.addEventListener('load', this.onLoad, true);
            this.popup.addEventListener('unload', this.onClose, true);
        } else if (this.popup.attachEvent) {
            this.popup.attachEvent('onload', this.onLoad);
            this.popup.attachEvent('onunload', this.onClose);
        } else {
            this.popup.onload = this.onLoad;
            this.popup.onunload = this.onClose;
        }
        */
    },
    
    /**
     * rename window name
     * 
     * @param {String} new name
     */
    rename: function(newName) {
        this.windowManager.unregister(this);
        this.name = this.popup.name = newName;
        this.windowManager.register(this);
    },
    
    /**
     * Sets the title text for the panel and optionally the icon class.
     * 
     * @param {String} title The title text to set
     * @param {String} iconCls (optional) iconCls A user-defined CSS class that provides the icon image for this panel
     */
    setTitle: function(title, iconCls) {
        if (this.popup && this.popup.document) {
            this.popup.document.title = title;
        }
    },
    
    /**
     * Closes the window, removes it from the DOM and destroys the window object. 
     * The beforeclose event is fired before the close happens and will cancel 
     * the close action if it returns false.
     */
    close: function() {
        if(this.fireEvent("beforeclose", this) !== false){
            this.fireEvent('close', this);
            this.purgeListeners();
            this.windowManager.unregister(this);
            this.popup.close();
        }
    },
    
    /**
     * @private
     * 
     * called after this.popups native onLoad
     * note: 'this' references the popup, whereas window references the parent
     */
    onLoad: function() {
        this.Ext.onReady(function() {
            //console.log(this);
            //console.log(window);
        }, this);
    },
    
    /**
     * @private
     * 
     * note: 'this' references the popup, whereas window references the parent
     */
    onClose: function() {

    }
});

/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 

/**
 * An object that represents a group of {@link Ext.ux.PopupWindow}
 * 
 * @namespace   Ext.ux
 * @class       Ext.ux.PopupWindowGroup
 * @constructor
 */
Ext.ux.PopupWindowGroup = function(config) {
    config = config || {};
    
    /**
     * @cfg {window} mainWindow
     */
    var mainWindow = config.mainWindow || window;
    
    // mark mainWindow
    mainWindow.isMainWindow = true;
    
    var list = {};
    var accessList = [];
    var front = null;
    
    

    // private
    var cleanupClosedWindows = function() {
        var doc;
        for(var id in list){
            try {
                var newDate = new Date().getTime();

                if(list[id].registerTime && (list[id].registerTime/1 + 2000 > newDate/1)) {
                    
                    continue;
                }
                
                doc = list[id].popup.document;
                if(!Ext.isIE && !doc.defaultView) {
                    doc = false;
                }
            } catch(e)  {
                doc = false;
                
            }
            if (! doc) {
                accessList.remove(list[id]);
                delete list[id];
            }
        }
    };
    /*
    // private
    var sortWindows = function(d1, d2){
        return (!d1._lastAccess || d1._lastAccess < d2._lastAccess) ? -1 : 1;
    };

    // private
    var orderWindows = function(){
        var a = accessList, len = a.length;
        if(len > 0){
            a.sort(sortWindows);
            var seed = a[0].manager.zseed;
            for(var i = 0; i < len; i++){
                var win = a[i];
                if(win && !win.hidden){
                    win.setZIndex(seed + (i*10));
                }
            }
        }
        activateLast();
    };

    // private
    var setActiveWin = function(win){
        if(win != front){
            if(front){
                front.setActive(false);
            }
            front = win;
            if(win){
                win.setActive(true);
            }
        }
    };

    // private
    var activateLast = function(){
        for(var i = accessList.length-1; i >=0; --i) {
            if(!accessList[i].hidden){
                setActiveWin(accessList[i]);
                return;
            }
        }
        // none to activate
        setActiveWin(null);
    };
*/
    return {

        register : function(win){

            win.registerTime = new Date().getTime();
            cleanupClosedWindows();
            if (! win.popup) {
                console.error('pure window instead of Ext.ux.PopupWindow got registered');
            }
            list[win.name] = win;
            accessList.push(win);
            //win.on('hide', activateLast);
        },

        unregister : function(win){
            delete list[win.name];
            //win.un('hide', activateLast);
            accessList.remove(win);
        },

        /**
         * Gets a registered window by name.
         * @param {String/Object} name The name of the window or a browser window object
         * @return {Ext.ux.PopupWindow}
         */
        get : function(name){
            cleanupClosedWindows();
            name = typeof name == "object" ? name.name : name;
            return list[name];
        },
        
        /**
         * returns main window of this window group
         * 
         * @return window
         */
        getMainWindow: function() {
            return mainWindow;
        },
        
        /**
         * Brings the specified window to the front of any other active windows.
         * @param {String/Object} win The id of the window or a {@link Ext.Window} instance
         * @return {Boolean} True if the dialog was brought to the front, else false
         * if it was already in front
         */
        bringToFront : function(win){
            win = this.get(win);
            if(win != front){
                win._lastAccess = new Date().getTime();
                if(win.popup.focus) {
                    win.popup.focus();
                }
                
//                front = win;
                // NOTE: we don't recognise the front window yet
//                if (Ext.isOpera) {
//                    Ext.Msg.alert(_("The window you want to work with is backgrounded. Your browser doesn't support to foreground the window for you, so you need to use your operating systems window switching features. Please send complaints to your browser vendor!"))
//                }
                //orderWindows();
                return true;
            }
            return false;
        },
        
        /**
         * Sends the specified window to the back of other active windows.
         * @param {String/Object} win The id of the window or a {@link Ext.Window} instance
         * @return {Ext.Window} The window
         */
        /*
        sendToBack : function(win){
            win = this.get(win);
            win._lastAccess = -(new Date().getTime());
            orderWindows();
            return win;
        },
        */
        
        /**
         * Hides all windows in the group.
         */
        /*
        hideAll : function(){
            for(var id in list){
                if(list[id] && typeof list[id] != "function" && list[id].isVisible()){
                    list[id].hide();
                }
            }
        },
        */
        
        /**
         * Gets the currently-active window in the group.
         * @return {Ext.Window} The active window
         */
        /*
        getActive : function(){
            return front;
        },
        */

        /**
         * Returns zero or more windows in the group using the custom search function passed to this method.
         * The function should accept a single {@link Ext.ux.PopupWindow} reference as its only argument and should
         * return true if the window matches the search criteria, otherwise it should return false.
         * @param {Function} fn The search function
         * @param {Object} scope (optional) The scope in which to execute the function (defaults to the window
         * that gets passed to the function if not specified)
         * @return {Array} An array of zero or more matching windows
         */
        getBy : function(fn, scope){
            cleanupClosedWindows();
            var r = [];
            for(var i = accessList.length-1; i >=0; --i) {
                var win = accessList[i];
                if(fn.call(scope||win, win) !== false){
                    r.push(win);
                }
            }
            return r;
        },

        /**
         * Executes the specified function once for every window in the group, passing each
         * window as the only parameter. Returning false from the function will stop the iteration.
         * @param {Function} fn The function to execute for each item
         * @param {Object} scope (optional) The scope in which to execute the function
         */
        each : function(fn, scope){
            cleanupClosedWindows();
            for(var id in list){
                if(list[id] && typeof list[id] != "function"){
                    if(fn.call(scope || list[id], list[id]) === false){
                        return;
                    }
                }
            }
        }
    };
};

/**
 * @class Ext.ux.PopupWindowMgr
 * @extends Ext.ux.PopupWindowGroup
 * The default global window group that is available automatically.  To have more than one group of 
 * popup windows, create additional instances of {@link Ext.ux.PopupWindowGroup} as needed.
 * @singleton
 */
try {
    Ext.ux.PopupWindowMgr = window.opener.Ext.ux.PopupWindowMgr ? 
        window.opener.Ext.ux.PopupWindowMgr :
        new Ext.ux.PopupWindowGroup();
} catch (e) {
    // we might have no access no opener
    Ext.ux.PopupWindowMgr = new Ext.ux.PopupWindowGroup();
}
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * 
 * TODO         play sound
 */    
 
Ext.ns('Ext.ux.Notification');
    
Ext.ux.Notification = function(){
    return {
        show: function(title, text){
            
            // define icon url
            // TODO use relative path here
            var iconUrl = window.location.href.replace(/#+.*/, '') + '/images/tine_logo.png';
            
            // JETPACK firefox extension (@link https://jetpack.mozillalabs.com/)
            if (window.jetpack !== undefined) {
                jetpack.notifications.show({
                    title: title, 
                    body: text, 
                    icon: iconUrl
                });
                
            // CALLOUT firefox extension (@link http://github.com/lackac/callout)
            } else if (window.callout !== undefined) {
                callout.notify(title, text, {
                    icon: iconUrl,
                    href: document.location.href
                });
                
            // webkit notifications
            } else if (window.webkitNotifications !== undefined && window.webkitNotifications.checkPermission() == 0) { // 0 is PERMISSION_ALLOWED
                var notification = window.webkitNotifications.createNotification(iconUrl, title, text);
                notification.show();
                setTimeout(function(){
                    notification.cancel();
                }, 15000);
                
            // default behaviour
            } else {
                Ext.ux.MessageBox.msg(title, text);
            }
        }
    };
}();
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

/*global Ext*/

Ext.ns('Ext.ux');

/**
 * @namespace   Ext.ux
 * @class       Ext.ux.WindowFactory
 * @contructor
 *
 * @cfg {String} windowType type of window {Ext|Browser|Air}
 */
Ext.ux.WindowFactory = function (config) {
    Ext.apply(this, config);
    
    switch (this.windowType) 
    {
    case 'Browser' :
        this.windowClass = Ext.ux.PopupWindow;
        this.windowManager = Ext.ux.PopupWindowMgr;
        break;
    case 'Ext' :
        this.windowClass = Ext.Window;
        this.windowManager = Ext.WindowMgr;
        break;
    case 'Air' :
        this.windowClass = Ext.air.NativeWindow;
        this.windowManager = Ext.air.NativeWindowManager;
        break;
    default :
        console.error('No such windowType: ' + this.windowType);
        break;
    }
    
    //Ext.ux.WindowFactory.superclass.constructor.call(this);
};

/**
 * @class Ext.ux.WindowFactory
 */
Ext.ux.WindowFactory.prototype = {
    
    /**
     * @private
     */
    windowClass: null,
    /**
     * @private
     */
    windowManager: null,
    
    /**
     * @rivate
     */
    getBrowserWindow: function (config) {
        var win = Ext.ux.PopupWindowMgr.get(config.name);
        
        if (! win) {
            win = new this.windowClass(config);
        }
        
        Ext.ux.PopupWindowMgr.bringToFront(win);
        return win;
    },
    
    /**
     * @private
     */
    getExtWindow: function (c) {
        // add titleBar
        c.height = c.height + 20;
        // border width
        c.width = c.width + 16;
        
        //limit the window size
        c.height = Math.min(Ext.getBody().getBox().height, c.height);
        c.width = Math.min(Ext.getBody().getBox().width, c.width);
        
        c.layout = c.layout || 'fit';
        c.items = {
            layout: 'card',
            border: false,
            activeItem: 0,
            isWindowMainCardPanel: true,
            items: [this.getCenterPanel(c)]
        }
        
        // we can only handle one window yet
        c.modal = true;
        
        var win = new Ext.Window(c);
        c.items.items[0].window = win;
        
        // if initShow property is present and it is set to false don't show window, just return reference
        if (c.hasOwnProperty('initShow') && c.initShow === false) {
    return win;
    }
        
        win.show();
        return win;
    },
    
    /**
     * constructs window items from config properties
     */
    getCenterPanel: function (config) {
        var items;
        if (config.contentPanelConstructor) {
            config.contentPanelConstructorConfig = config.contentPanelConstructorConfig || {};

            /*
             * IE fix for listeners
             * 
             * In IE we have two problems when dealing with listeners across windows
             * 1. listeners (functions) are defined in the parent window. a typeof (some function from parent) returns object in IE
             *    the Ext.Observable code can't deal with this
             * 2. listeners get executed by fn.apply(scope, arguments). For some reason in IE this dosn't work with functions defined
             *    in an other window.
             *    
             * To work around this, we create new fresh listeners in the new window and proxy the event calls
             * 
             * TODO there is a bug in this function -> it does not work correctly if scope is defined in listeners object and is not the first entry ... :(
             */
            var ls = config.contentPanelConstructorConfig.listeners;
            if (ls /* && Ext.isIE */) {
                var lsProxy = {};
                for (var p in ls) {
                    if (ls.hasOwnProperty(p) && p !== 'scope') {
                        // NOTE apply dosn't work here for some strange reason, so we hope that there are not more than 5 params
                        if (ls[p].fn) {
lsProxy[p] = function () {
ls[p].fn.call(ls[p].scope, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
};
                        } else {
lsProxy[p] = function () {
ls[p].call(ls.scope, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
};
                        }
                    }
                }
                config.contentPanelConstructorConfig.listeners = lsProxy;
            }
            
            // place a reference to current window class in the itemConstructor.
            // this may be overwritten depending on concrete window implementation
            config.contentPanelConstructorConfig.window = config;
            
            // (re-) create auto apps on BrowserWindows
            if(this.windowType == 'Browser') {
                Tine.Tinebase.ApplicationStarter.init();
            }
            // find the constructor in this context
            var parts = config.contentPanelConstructor.split('.'),
            ref = window;
            
            for (var i = 0; i < parts.length; i += 1) {
                ref = ref[parts[i]];
            }
            
            // finally construct the content panel
            items = new ref(config.contentPanelConstructorConfig);
        } else {
            items = config.items ? config.items : {};
        }
        
        return items;
    },
    
    /**
     * getWindow
     * 
     * creates new window if not already exists.
     * brings window to front
     */
    getWindow: function (config) {
        var windowType = (config.modal) ? 'Ext' : this.windowType;
        
        // Names are only allowed to be alnum
        config.name = Ext.isString(config.name) ? config.name.replace(/[^a-zA-Z0-9_]/g, '') : config.name;
        
        if (! config.title && config.contentPanelConstructorConfig && config.contentPanelConstructorConfig.title) {
        config.title = config.contentPanelConstructorConfig.title;
            delete config.contentPanelConstructorConfig.title;
        }
        
        switch (windowType) 
        {
        case 'Browser' :
            return this.getBrowserWindow(config);
        case 'Ext' :
            return this.getExtWindow(config);
        case 'Air' :
            return this.getAirWindow(config);
        default :
            console.error('No such windowType: ' + this.windowType);
            break;
        }
    }
};
/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */


/**
 * @class Ext.ux.SliderTip
 * @extends Ext.Tip
 * Simple plugin for using an Ext.Tip with a slider to show the slider value
 */
Ext.ux.SliderTip = Ext.extend(Ext.Tip, {
    minWidth: 10,
    offsets : [0, -10],
    init : function(slider){
        slider.on('dragstart', this.onSlide, this);
        slider.on('drag', this.onSlide, this);
        slider.on('dragend', this.hide, this);
        slider.on('destroy', this.destroy, this);
    },

    onSlide : function(slider){
        this.show();
        this.body.update(this.getText(slider));
        this.doAutoWidth();
        this.el.alignTo(slider.thumb, 'b-t?', this.offsets);
    },

    getText : function(slider){
        return slider.getValue();
    }
});
/*
 * @author Pontifex
 * @version 1.0
 *
 * @class Ext.ux.Wizard
 */
Ext.ns('Ext.ux');

/**
 * Ext.ux.Wizard Extension Class
 * 
 * @namespace   Ext.ux
 * @class       Ext.ux.Wizard
 * @extends     Ext.Panel
 * @constructor
 * @param {Object} config Configuration options
 */
Ext.ux.Wizard = function(config) {
 
    var _config = Ext.apply({
        
        layout: 'card',
        activeItem: 0,
        bodyStyle: 'paddingTop:15px',
        defaults: {
                // applied to each contained panel
                border: false
            },
        
        buttons  : [
            {text:'Previous', handler: this.movePrevious, scope: this, disabled:true},
            {text:'Next', handler: this.moveNext, scope: this},
            {text:'Finish', handler: this.finishHanlder, scope: this, disabled:true},
            {text:'Cancel', handler: this.hideHanlder, scope: this}
        ]
    }, config||{});
    
    this.currentItem = 0;
    this.template = new Ext.Template('Step {current} of {count}');
    this.mandatorySteps = _config.mandatorySteps;

    Ext.ux.Wizard.superclass.constructor.call(this, _config);
    
    this.addEvents('leave', 'activate', 'finish', 'cancel');
    this.on('render', function(){
        this.footer.addClass('x-panel-footer-wizard');
        this.footer.insertFirst({html: '<div class="x-panel-footer-wizard-status">&nbsp;</div>'});
        this.setStatus();
        return true;
    });
 
}; // end of Ext.ux.Wizard constructor
 
// extend
Ext.extend(Ext.ux.Wizard, Ext.Panel, {
    
    getCurrentStep: function() {
        return this.currentItem + 1;
    },
    getStepCount: function() {
        return this.items.items.length;
    },
    setCurrentStep: function(step) {
        this.move(step-1);
    },
     
    /**
     * @private
     */
    beforeMove: function(currentItem, nextItem, forward) {
        return this.fireEvent('leave', currentItem, nextItem, forward);
    },
    
    /**
     * @private
     */
    setStatus: function() {
        var BUTTON_PREVIOUS = 0;
        var BUTTON_NEXT = 1;
        var BUTTON_FINISH = 2;
        var BUTTON_CANCEL = 3;

        var isFirstItem = (this.getCurrentStep() == 1);
        var isLastItem = (this.getCurrentStep() == this.getStepCount());
        var minimunSteps = isNaN(parseInt(this.mandatorySteps)) ?
                           this.getStepCount() :
                           Math.min(Math.max(parseInt(this.mandatorySteps), 1), this.getStepCount());

        this.buttons[BUTTON_PREVIOUS].setDisabled(isFirstItem);
        this.buttons[BUTTON_NEXT].setDisabled(isLastItem);
        this.buttons[BUTTON_FINISH].setDisabled(!(isLastItem || (minimunSteps < this.getCurrentStep())));
                
        this.footer.first('div div', true).firstChild.innerHTML = this.template.applyTemplate({'current': this.getCurrentStep(), 'count': this.getStepCount()});
    },
    /**
     * @private
     */
    move: function(item) {
        if(item >= 0 && item < this.items.items.length) {
            
            if(this.beforeMove(this.layout.activeItem, this.items.items[item], item > this.currentItem)) {
                this.layout.setActiveItem(item);
                this.currentItem = item;
    
                this.setStatus();
                this.fireEvent('activate', this.layout.activeItem);
            }
        }
    },
    
    /**
     * @private
     */
    moveNext:function(btn,e){
        this.move(this.currentItem+1);
    },
    
    /**
     * @private
     */
    movePrevious:function(btn,e){
        this.move(this.currentItem-1);
    },
    
    /**
     * @private
     */
    hideHanlder :function(){
        if(this.fireEvent('cancel')) {
            this.hide();
        }
    },
    
    /**
     * @private
     */
    finishHanlder:function(){
        if(this.fireEvent('finish')) {
            this.hide();
        }
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux');

/**
 * Generic widget for a twintriggerd search field
 * 
 * @namespace   Ext.ux
 * @class       Ext.ux.SearchField
 * @extends     Ext.form.TwinTriggerField
 */
Ext.ux.SearchField = Ext.extend(Ext.form.TwinTriggerField, {
    /**
     * @cfg {String} paramName
     */
    paramName : 'query',
    /**
     * @cfg {Bool} selectOnFocus
     */
    selectOnFocus : true,
    /**
     * @cfg {String} emptyText
     */
    emptyText: '',
    
    validationEvent:false,
    validateOnBlur:false,
    trigger1Class:'x-form-clear-trigger',
    trigger2Class:'x-form-search-trigger',
    hideTrigger1:true,
    width:180,
    hasSearch : false,
    /**
     * @private
     */
    initComponent : function(){
        this.emptyText = _('enter searchfilter');
        
        Ext.ux.SearchField.superclass.initComponent.call(this);
        this.on('specialkey', function(f, e){
            if(e.getKey() == e.ENTER){
                this.onTrigger2Click();
            }
        }, this);
    },
    /**
     * @private
     */
    onTrigger1Click : function(){
        if(this.hasSearch){
            this.el.dom.value = '';
            this.fireEvent('change', this, this.getRawValue(), this.startValue);
            this.startValue = this.getRawValue();
            this.triggers[0].hide();
            this.hasSearch = false;
        }
    },
    /**
     * @private
     */
    onTrigger2Click : function(){
        var v = this.getRawValue();
        this.fireEvent('change', this, this.getRawValue(), this.startValue);
        this.startValue = this.getRawValue();
        this.hasSearch = true;
        this.triggers[0][v.length < 1 ? 'hide' : 'show']();
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Ext.ux.form');

/**
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.BrowseButton
 * @extends     Ext.Button
 */
Ext.ux.BrowseButton = Ext.extend(Ext.Button, {

    initComponent: function() {
        this.plugins = this.plugins || [];
        this.plugins.push( new Ext.ux.file.BrowsePlugin({}));
        
        Ext.ux.BrowseButton.superclass.initComponent.call(this);
    }
});

Ext.reg('browsebutton', Ext.ux.BrowseButton);
/*!
 * Ext JS Library 3.1.1
 * Copyright(c) 2006-2010 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ns('Ext.ux.grid');

/**
 * @class Ext.ux.grid.CheckColumn
 * @extends Object
 * GridPanel plugin to add a column with check boxes to a grid.
 * <p>Example usage:</p>
 * <pre><code>
// create the column
var checkColumn = new Ext.grid.CheckColumn({
   header: 'Indoor?',
   dataIndex: 'indoor',
   id: 'check',
   width: 55
});

// add the column to the column model
var cm = new Ext.grid.ColumnModel([{
       header: 'Foo',
       ...
    },
    checkColumn
]);

// create the grid
var grid = new Ext.grid.EditorGridPanel({
    ...
    cm: cm,
    plugins: [checkColumn], // include plugin
    ...
});
 * </code></pre>
 * In addition to storing a Boolean value within the record data, this
 * class toggles a css class between <tt>'x-grid3-check-col'</tt> and
 * <tt>'x-grid3-check-col-on'</tt> to alter the background image used for
 * a column.
 */
Ext.ux.grid.CheckColumn = function(config){
    Ext.apply(this, config);
    if(!this.id){
        this.id = Ext.id();
    }
    this.renderer = this.renderer.createDelegate(this);
};

Ext.ux.grid.CheckColumn.prototype ={
    init : function(grid){
        this.grid = grid;
        this.grid.on('render', function(){
            var view = this.grid.getView();
            view.mainBody.on('mousedown', this.onMouseDown, this);
        }, this);
    },

    onMouseDown : function(e, t){
        if(Ext.fly(t).hasClass(this.createId())){
            e.stopEvent();
            var index = this.grid.getView().findRowIndex(t);
            var record = this.grid.store.getAt(index);
            record.set(this.dataIndex, !record.data[this.dataIndex]);
        }
    },

    renderer : function(v, p, record){
        p.css += ' x-grid3-check-col-td';
        return String.format('<div class="x-grid3-check-col{0} {1}">&#160;</div>', v ? '-on' : '', this.createId());
    },
    
    createId : function(){
        return 'x-grid3-cc-' + this.id;
    }
};

// register ptype
Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn);

// backwards compat
Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn;/*
 * Tine 2.0
 * 
 * @package     Ext
 * @subpackage  ux
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Ext.ux', 'Ext.ux.grid');

/**
 * Class for creating a edittable grid with quick add row on top.
 * <p>As form field for the quick add row, the quickaddField of the column definition is used.<p>
 * <p>The event 'newentry' is fired after the user finished editing the new row.<p>
 * <p>Example usage:</p>
 * <pre><code>
 var g =  new Ext.ux.grid.QuickaddGridPanel({
     ...
     quickaddMandatory: 'summary',
     columns: [
         {
             ...
             id: 'summary',
             quickaddField = new Ext.form.TextField({
                 emptyText: 'Add a task...'
             })
         },
         {
             ...
             id: 'due',
             quickaddField = new Ext.form.DateField({
                 ...
             })
         }
     ]
 });
 * </code></pre>
 *
 * @namespace   Ext.ux.grid
 * @class       Ext.ux.grid.QuickaddGridPanel
 * @extends     Ext.grid.EditorGridPanel
 */
Ext.ux.grid.QuickaddGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
    /**
     * @cfg {String} quickaddMandatory 
     * Mandatory field which must be set before quickadd fields will be enabled
     */
    quickaddMandatory: false,
    /**
     * if set, the quickadd fields are validated on blur
     * @type Boolean
     */
    validate: false,
    
    /**
     * @cfg {Bool} resetAllOnNew
     * reset all fields after new record got created (per default only den mandatory field gets resetted).
     */
    resetAllOnNew: false,
    
    /**
     * @property {Bool} adding true if a quickadd is in process
     */
    adding: false,
    
    /**
     * @private
     */
    initComponent: function(){
        
        this.idPrefix = Ext.id();
        
        Ext.ux.grid.QuickaddGridPanel.superclass.initComponent.call(this);
        this.addEvents(
            /**
             * @event newentry
             * Fires when add process is finished 
             * @param {Object} data of the new entry
             */
            'newentry'
        );
        
        this.cls = 'x-grid3-quickadd';
        
        // The customized header template
        this.initTemplates();
        
        // add our fields after view is rendered
        this.getView().afterRender = this.getView().afterRender.createSequence(this.renderQuickAddFields, this);
        
        // init handlers
        this.quickaddHandlers = {
            scope: this,
            blur: function(field){
                this.doBlur.defer(250, this);
            },
            specialkey: function(f, e){
                var key = e.getKey();
                switch (key) {
                    case e.ENTER:
                        e.stopEvent();
                        f.el.blur();
                        if (f.triggerBlur){
                            f.triggerBlur();
                        }
                        break;
                    case e.ESC:
                        e.stopEvent();
                        f.setValue('');
                        f.el.blur();
                        break;
                }
            }
        };
    },
    
    /**
     * renders the quick add fields
     */
    renderQuickAddFields: function() {
        Ext.each(this.getCols(), function(column){
            if (column.quickaddField) {
                column.quickaddField.render(this.getQuickAddWrap(column));
                column.quickaddField.setDisabled(column.id != this.quickaddMandatory);
                column.quickaddField.on(this.quickaddHandlers);
            }
        },this);
        
        this.colModel.on('configchange', this.syncFields, this);
        //this.colModel.on('widthchange', this.syncFields, this);
        this.colModel.on('hiddenchange', this.syncFields, this);
        this.on('resize', this.syncFields);
        this.on('columnresize', this.syncFields);
        this.syncFields();
        
        this.colModel.getColumnById(this.quickaddMandatory).quickaddField.on('focus', this.onMandatoryFocus, this);
    },
    
    /**
     * @private
     */
    doBlur: function(){
        
        // check if all quickadd fields are blured, validate them if required
        var focusedOrInvalid;
        Ext.each(this.getCols(true), function(item){
            if(item.quickaddField && (item.quickaddField.hasFocus || item.quickaddField.hasFocusedSubPanels || (this.validate && !item.quickaddField.isValid()))) {
                focusedOrInvalid = true;
            }
        }, this);
        
        // only fire a new record if no quickaddField is focused
        if (!focusedOrInvalid) {
            var data = {};
            Ext.each(this.getCols(true), function(item){
                if(item.quickaddField){
                    data[item.id] = item.quickaddField.getValue();
                    item.quickaddField.setDisabled(item.id != this.quickaddMandatory);
                }
            }, this);
            
            if (this.colModel.getColumnById(this.quickaddMandatory).quickaddField.getValue() != '') {
                if (this.fireEvent('newentry', data)){
                    this.colModel.getColumnById(this.quickaddMandatory).quickaddField.setValue('');
                    if(this.validate) {
                        this.colModel.getColumnById(this.quickaddMandatory).quickaddField.clearInvalid();
                    }
                    if (this.resetAllOnNew) {
                        var columns = this.colModel.config;
                        for (var i = 0, len = columns.length; i < len; i++) {
                            if(columns[i].quickaddField != undefined){
                               columns[i].quickaddField.setValue('');
                            }
                        }
                    }
                }
            }
            
            this.adding = false;
        }
        
    },
    
    /**
     * gets columns
     * 
     * @param {Boolean} visibleOnly
     * @return {Array}
     */
    getCols: function(visibleOnly) {
        if(visibleOnly === true){
            var visibleCols = [];
            Ext.each(this.colModel.config, function(column) {
                if (! column.hidden) {
                    visibleCols.push(column);
                }
            }, this);
            return visibleCols;
        }
        return this.colModel.config;
    },
    
    /**
     * returns wrap el for quick add filed of given col
     * 
     * @param {Ext.grid.Colum} col
     * @return {Ext.Element}
     */
    getQuickAddWrap: function(column) {
        return Ext.get(this.idPrefix + column.id);
    },
    
    /**
     * @private
     */
    initTemplates: function() {
        this.getView().templates = this.getView().templates ? this.getView().templates : {};
        var ts = this.getView().templates;
        
        var newRows = '';

        var cm = this.colModel;
        var ncols = cm.getColumnCount();
        for (var i=0; i<ncols; i++) {
            var colId = cm.getColumnId(i);
            newRows += '<td><div class="x-small-editor" id="' + this.idPrefix + colId + '"></div></td>';
        }
        
        ts.header = new Ext.Template(
            '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
            '<thead><tr class="x-grid3-hd-row">{cells}</tr></thead>',
            '<tbody><tr class="new-row">',
                newRows,
            '</tr></tbody>',
            '</table>'
        );
    },
    
    /**
     * @private
     */
    syncFields: function() {
        var newRowEl = Ext.get(Ext.DomQuery.selectNode('tr[class=new-row]', this.getView().mainHd.dom));
        
        var columns = this.getCols();
        for (var column, tdEl, i=columns.length -1; i>=0; i--) {
            column = columns[i];
            var tdEl = this.getQuickAddWrap(column).parent();
            
            // resort columns
            newRowEl.insertFirst(tdEl);
            
            // set hidden state
            tdEl.dom.style.display = column.hidden ? 'none' : '';
            
            // resize
            //tdEl.setWidth(column.width);
            if (column.quickaddField) {
                column.quickaddField.setSize(column.width -1);
            }
        }
    },
    
    /**
     * @private
     */
    onMandatoryFocus: function() {
        this.adding = true;
        Ext.each(this.getCols(true), function(item){
            if(item.quickaddField){
                item.quickaddField.setDisabled(false);
            }
        }, this);
    }
        
});
/*
 * Ext JS Library 2.1
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */
 
/**
 * @namespace   Ext.ux.grid
 * @class       Ext.ux.grid.RowExpander
 * @extends     Ext.util.Observable
 */
Ext.ux.grid.RowExpander = function(config){
    Ext.apply(this, config);

    this.addEvents({
        beforeexpand : true,
        expand: true,
        beforecollapse: true,
        collapse: true
    });

    Ext.ux.grid.RowExpander.superclass.constructor.call(this);

    if(this.tpl){
        if(typeof this.tpl == 'string'){
            this.tpl = new Ext.Template(this.tpl);
        }
        this.tpl.compile();
    }

    this.state = {};
    this.bodyContent = {};
};

    Ext.extend(Ext.ux.grid.RowExpander, Ext.util.Observable, {
        header: "",
        width: 20,
        sortable: false,
        fixed:true,
        dataIndex: '',
        id: 'expander',
        lazyRender : true,
        enableCaching: false,
    
        getRowClass : function(record, rowIndex, p, ds){
            p.cols = p.cols-1;
            var content = this.bodyContent[record.id];
            if(!content && !this.lazyRender){
                content = this.getBodyContent(record, rowIndex);
            }
            if(content){
                p.body = content;
            }
            return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';
        },
    
        init : function(grid){
            this.grid = grid;
    
            var view = grid.getView();
            view.getRowClass = this.getRowClass.createDelegate(this);
    
            view.enableRowBody = true;
    
            grid.on('render', function(){
                view.mainBody.on('mousedown', this.onMouseDown, this);
            }, this);
        },
    
        getBodyContent : function(record, index){
            if(!this.enableCaching){
                return this.tpl.apply(record.data);
            }
            var content = this.bodyContent[record.id];
            if(!content){
                content = this.tpl.apply(record.data);
                this.bodyContent[record.id] = content;
            }
            return content;
        },
    
        onMouseDown : function(e, t){
            if(t.className == 'x-grid3-row-expander'){
                e.stopEvent();
                var row = e.getTarget('.x-grid3-row');
                this.toggleRow(row);
            }
        },
    
        renderer : function(v, p, record){
            p.cellAttr = 'rowspan="2"';
            return '<div class="x-grid3-row-expander">&#160;</div>';
        },
    
        beforeExpand : function(record, body, rowIndex){
            if(this.fireEvent('beforexpand', this, record, body, rowIndex) !== false){
                if(this.tpl && this.lazyRender){
                    body.innerHTML = this.getBodyContent(record, rowIndex);
                }
                return true;
            }else{
                return false;
            }
        },
    
        toggleRow : function(row){
            if(typeof row == 'number'){
                row = this.grid.view.getRow(row);
            }
            this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);
        },
    
        expandRow : function(row){
            if(typeof row == 'number'){
                row = this.grid.view.getRow(row);
            }
            var record = this.grid.store.getAt(row.rowIndex);
            var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
            if(this.beforeExpand(record, body, row.rowIndex)){
                this.state[record.id] = true;
                Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
                this.fireEvent('expand', this, record, body, row.rowIndex);
            }
        },
    
        collapseRow : function(row){
            if(typeof row == 'number'){
                row = this.grid.view.getRow(row);
            }
            var record = this.grid.store.getAt(row.rowIndex);
            var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
            if(this.fireEvent('beforcollapse', this, record, body, row.rowIndex) !== false){
                this.state[record.id] = false;
                Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
                this.fireEvent('collapse', this, record, body, row.rowIndex);
            }
        }
});
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @subpackage  widgets
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux.grid');

/**
 * Paging toolbar with build in selection support
 *
 * @namespace   Ext.ux.grid
 * @class       Ext.ux.grid.PagingToolbar
 * @extends     Ext.PagingToolbar
 * @constructor
 * @param       {Object} config
 */
Ext.ux.grid.PagingToolbar = Ext.extend(Ext.PagingToolbar, {
    /**
     * @cfg {Bool} displayPageInfo 
     * True to display the displayMsg (defaults to false)
     */
    displayPageInfo: false,
    /**
     * @cfg {Bool} displaySelectionHelper
     * True to display the selectionMsg (defaults to false)
     */
    displaySelectionHelper: false,
    /**
     * @cfg {Ext.grid.AbstractSelectionModel}
     */
    sm: null,
    
    /**
     * @cfg {Bool} disableSelectAllPages
     */
    disableSelectAllPages: false,
    
    /**
     * @private
     */
    initComponent: function() {
        // initialise i18n
        this.selHelperText = {
            'main'         : _('{0} selected'),
            'deselect'     : _('Unselect all'),
            'selectall'    : _('Select all pages ({0} records)'),
            'toggle'       : _('Toggle selection')
        };

        Ext.ux.grid.PagingToolbar.superclass.initComponent.call(this);
    },
    
    /**
     * @private
     */
    onRender : function(ct, position) {
        Ext.ux.grid.PagingToolbar.superclass.onRender.call(this, ct, position);
        
        if (this.displaySelectionHelper) {
            this.renderSelHelper();
        }
    },
    
    /**
     * @private
     */
    renderSelHelper: function() {
        this.deselectBtn = new Ext.Action({
            iconCls: 'x-ux-pagingtb-deselect',
            text: this.getSelHelperText('deselect'),
            scope: this,
            handler: function() {this.sm.clearSelections();}
        });
        this.selectAllPages = new Ext.Action({
            iconCls: 'x-ux-pagingtb-selectall',
            text: this.getSelHelperText('selectall'),
            scope: this,
            disabled: this.disableSelectAllPages,
            handler: function() {this.sm.selectAll();}
        });
        this.toggleSelectionBtn = new Ext.Action({
            iconCls: 'x-ux-pagingtb-toggle',
            text: this.getSelHelperText('toggle'),
            scope: this,
            handler: function() {this.sm.toggleSelection();}
        });
        
        this.addSeparator();
        this.selHelperBtn = new Ext.Action({
            xtype: 'tbsplit',
            text: this.getSelHelperText('main'),
            iconCls: 'x-ux-pagingtb-main',
            menu: new Ext.menu.Menu({
                items: [
                    this.deselectBtn,
                    this.selectAllPages,
                    this.toggleSelectionBtn
                ]
            })
        });
        
        this.add(this.selHelperBtn);
        
        // update buttons when data or selection changes
        this.sm.on('selectionchange', this.updateSelHelper, this);
        this.store.on('load', this.updateSelHelper, this);
    },
    
    /**
     * update all button descr.
     */
    updateSelHelper: function() {
        this.selHelperBtn.setText(this.getSelHelperText('main'));
        this.selectAllPages.setText(this.getSelHelperText('selectall'));
    },

    /**
     * get test for button
     * @param {String} domain 
     * @return {String}
     */
    getSelHelperText: function(domain) {
        var num;
        switch(domain) {
            case 'main':
                num = this.sm.getCount();
                break;
            case 'selectall':
                num = this.store.getTotalCount();
                break;
            default:
                return this.selHelperText[domain];
                break;
        }
        
        return String.format(this.selHelperText[domain], num);
    }    
});
/**
 * Ext.ux.grid.GridViewMenuPlugin
 * Copyright (c) 2008, http://www.siteartwork.de
 *
 * Ext.ux.grid.GridViewMenuPlugin is licensed under the terms of the
 *                  GNU Open Source LGPL 3.0
 * license.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the LGPL as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the LGPL License for more
 * details.
 *
 * You should have received a copy of the GNU LGPL along with
 * this program. If not, see <http://www.gnu.org/licenses/lgpl.html>.
 *
 */

Ext.ns('Ext.ux.grid');

/**
 * Renders a menu button to the upper right corner of the grid this plugin is
 * bound to. The menu items will represent the column model and hide/show
 * the columns on click.
 *
 * Note that you have to set the enableHdMenu-property of the bound grid to
 * "false" so this plugin does not interfere with the header menus of the grid's view.
 *
 * @namespace   Ext.ux.grid
 * @class       Ext.ux.grid.GridViewMenuPlugin
 * @extends     Object
 * @constructor
 *
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 */
Ext.ux.grid.GridViewMenuPlugin = Ext.extend(Object, {

    /**
     * The {Ext.grid.GridView} this plugin is bound to.
     * @type {Ext.grid.GridView}
     * @protected
     */
    _view : null,

    /**
     * The menu button that gets rendered to the upper right corner of the
     * grid's view.
     * @type {Ext.Element}
     * @protected
     */
    _menuBtn : null,

    /**
     * The menu that will be shown when the menu button gets clicked.
     * Named after the "colModel" property of the grid's view so this plugin
     * can easily operate in the scope of the view.
     * @type {Ext.Menu}
     */
    colMenu : null,

    /**
     * The column model of the grid this plugin is bound to.
     * Named after the "cm" property of the grid's view so this plugin
     * can easily operate in the scope of the view.
     * @type {Ext.grid.ColumnModel}
     */
    cm : null,

    /**
     * Inits this plugin.
     * Method is API-only. Will be called automatically from the grid this
     * plugin is bound to.
     *
     * @param {Ext.grid.GridPanel} grid
     *
     * @throws {Exception} throws an exception if the plugin recognizes the
     * grid's "enableHdMenu" property to be set to "true"
     */
    init : function(grid)
    {
        if (grid.enableHdMenu === true) {
            throw("Ext.ux.grid.GridViewMenuPlugin - grid's \"enableHdMenu\" property has to be set to \"false\"");
        }
        
        var v = this._view = grid.getView();
        v.afterMethod('initElements', this.initElements, this);
        v.afterMethod('initData', this.initData, this);
        v.afterMethod('onLayout', this._onLayout, this);
        v.beforeMethod('destroy', this._destroy, this);
        
        this.colMenu = new Ext.menu.Menu({
            listeners: {
                scope: this,
                beforeshow: this._beforeColMenuShow,
                itemclick: this._handleHdMenuClick
            }
        });
        
        this.colMenu.override({
            show : function(el, pos, parentMenu){
                this.parentMenu = parentMenu;
                if(!this.el){
                    this.render();
                }
                this.fireEvent("beforeshow", this);

                // show menu and constrain to viewport if necessary
                // ( + minor offset adjustments for pixel perfection)
                this.showAt(
                    this.el.getAlignToXY(el, pos || this.defaultAlign, [Ext.isWebKit? 2 : 1, 0]), 
                    parentMenu, 
                    true // true to constrain
                );
            }
        });
        

    },

// -------- listeners

    /**
     * Callback for the itemclick event of the menu.
     * Default implementation calls the view's handleHdMenuClick-method in the
     * scope of the view itself.
     *
     * @param {Ext.menu.BaseItem baseItem} item
     * @param {Ext.EventObject} e
     *
     * @return {Boolean} returns false if hiding the column represented by the
     * column is not allowed, otherwise true
     *
     * @protected
     */
    _handleHdMenuClick : function(item, e)
    {
        if (this.colMenu.items.indexOf(item) > 1) {
            return this._view.handleHdMenuClick(item, e);
        }
    },

    /**
     * Listener for the beforeshow-event of the menu.
     * Default implementation calls the view's beforeColMenuShow-method
     * in the scope of this plugin.
     *
     * Overwrite this for custom behavior.
     *
     * @param {Ext.menu.Menu} menu
     *
     * @protected
     */
    _beforeColMenuShow : function(menu)
    {
        this._view.beforeColMenuShow.call(this, menu);
        
        // menu title tweak
        this.colMenu.insert(0, new Ext.menu.Separator());
        this.colMenu.insert(0, new Ext.menu.TextItem({
            text: String.format(
                '<img src="{0}" class="x-menu-item-icon x-cols-icon" />{1}',
                Ext.BLANK_IMAGE_URL,
                this._view.columnsText
            ),
            style: 'line-height:16px;padding:3px 21px 3px 27px;'
        }));
    },

    /**
     * Listener for the click event of the menuBtn element.
     * Used internally to show the menu.
     *
     * @param {Ext.EventObject} e
     * @param {HtmlElement} t
     *
     * @protected
     */
    _handleHdDown : function(e, t)
    {
        if(Ext.fly(t).hasClass('x-grid3-hd-btn')){
            e.stopEvent();
            this.colMenu.show(t, "tr-br?");
        }
    },

// -------- helpers

    /**
     * Builds the element that gets added to teh grid's header for showing
     * the menu.
     * The default implementation will render the menu button into the upper
     * right corner of the grid.
     * Overwrite for custom behavior.
     *
     * @return {Ext.Element}
     *
     * @protected
     */
    _getMenuButton : function()
    {
        var a = document.createElement('a');
        a.className = 'ext-ux-grid-gridviewmenuplugin-menuBtn x-grid3-hd-btn';
        a.href = '#';

        return new Ext.Element(a);
    },

    /**
     * Sequenced function for storing the view's cm property,
     * Called in the scope of this plugin.
     */
    initData : function()
    {
        this.cm = this._view.cm;
    },

    /**
     * Sequenced function for adding the menuBtn to the grid's header.
     * Called in the scope of this plugin.
     */
    initElements : function()
    {
        this.menuBtn = this._getMenuButton();
        this._view.mainHd.dom.appendChild(this.menuBtn.dom);
        this.menuBtn.on("click", this._handleHdDown, this);
    },
    
    /**
     * sets the buttons size
     */
    _onLayout: function() {
        // Note: if hidden, IE returns no offsetHeight and also can not set it
        if (this._view.mainHd.dom.offsetHeight > 1) {
            this.menuBtn.dom.style.height = (this._view.mainHd.dom.offsetHeight-1)+'px';
        }
    },
    
    /**
     * Hooks into the view's destroy method and removes the menu and the menu
     * button.
     *
     * @protected
     */
    _destroy : function()
    {
        if(this.colMenu){
            this.colMenu.removeAll();
            Ext.menu.MenuMgr.unregister(this.colMenu);
            if (this.colMenu.getEl()) {
                this.colMenu.getEl().remove();
            }
            delete this.colMenu;
        }

        if(this._menuBtn){
            this._menuBtn.remove();
            delete this._menuBtn;
        }
    }

});

Ext.ns('Ext.ux.file');

/**
 * a simple file upload manager
 * collects all uploads
 * 
 * @namespace   Ext.ux.file
 * @class       Ext.ux.file.UploadManager
 */
Ext.ux.file.UploadManager = function(config) {
    
    Ext.apply(this, config);
    Ext.ux.file.UploadManager.superclass.constructor.apply(this, arguments);
    
    this.uploads = {};
    
    this.addEvents(
        /**
         * @event uploadcomplete
         * Fires when the upload was done successfully 
         * @param {Ext.ux.file.Upload} this
         * @param {Ext.Record} Ext.ux.file.Upload.file
         */
         'uploadcomplete',
        /**
         * @event uploadfailure
         * Fires when the upload failed 
         * @param {Ext.ux.file.Upload} this
         * @param {Ext.Record} Ext.ux.file.Upload.file
         */
         'uploadfailure',
        /**
         * @event uploadprogress
         * Fires on upload progress (html5 only)
         * @param {Ext.ux.file.Upload} this
         * @param {Ext.Record} Ext.ux.file.Upload.file
         * @param {XMLHttpRequestProgressEvent}
         */
         'uploadprogress',
         /**
          * @event uploadstart
          * Fires on upload progress (html5 only)
          * @param {Ext.ux.file.Upload} this
          * @param {Ext.Record} Ext.ux.file.Upload.file
          */
          'uploadstart',
         /**
          * @event uploadstart
          * Fires on upload progress (html5 only)
          * @param {Ext.ux.file.Upload} this
          * @param {Ext.Record} Ext.ux.file.Upload.file
          */
         'update'
    );
};
    
Ext.extend(Ext.ux.file.UploadManager, Ext.util.Observable, {
        /**
         * @cfg {Int} number of allowed concurrent uploads
         */
        maxConcurrentUploads: 3,
        
        /**
         * current running uploads
         */
        runningUploads: 0,
        
        /**
         * @cfg (String) upload id prefix
         */
        uploadIdPrefix: "tine-upload-",

        /**
         * holds the uploads
         */
        uploads: null,

        /**
         * counts session uploads
         */
        uploadCount: 0,

        /**
         * every upload in the upload manager gets queued initially
         * 
         * @param upload {Ext.ux.file.Upload} Upload object
         * @returns {String} upload id
         */
        queueUpload: function(upload) {
            var uploadId = this.uploadIdPrefix + (1000 + this.uploadCount++).toString();
            if(upload.id && upload.id !== -1) {
                uploadId = upload.id;
            }
            upload.id = uploadId;
            
            this.uploads[uploadId] = upload;
            this.relayEvents(upload, ['update']);
            
            upload.on('uploadcomplete', this.onUploadComplete, this);
            
            return uploadId;
        },

        /**
         * starts the upload
         * 
         * @param id {String} upload id
         * @returns {Ext.ux.file.Upload.file} upload file record
         */
        upload: function(id) {
            return this.uploads[id].upload();
        },

        /**
         * returns upload by id
         * 
         * @param id {String} upload id
         * @returns (Ext.ux.file.Upload} Upload object
         */
        getUpload: function(id) {
            return this.uploads[id];
        },

        /**
         * remove upload from the upload manager
         * 
         * @param {String} upload id
         */
        unregisterUpload: function(id) {
            delete this.uploads[id];
        },
        
        
        /**
         * indicates whether the HTML5 chunked upload is available
         * 
         * @returns {Boolean}
         */
        isHtml5ChunkedUpload: function() {

          if(window.File == undefined) return false;
          if(this.isHostMethod(File.prototype, 'slice') || this.isHostMethod(File.prototype, 'mozSlice') || this.isHostMethod(File.prototype, 'webkitSlice')) {
              return true;
          }
          else {
              return false;
          }
          
        },
        
        /**
         * checks for the existance of a method of an object
         * 
         * @param object    {Object}
         * @param property  {String} method name 
         * @returns {Boolean}
         */
        isHostMethod: function (object, property) {
            var t = typeof object[property];
            return t == 'function' || (!!(t == 'object' && object[property])) || t == 'unknown';
        },
        
        /**
         * are there as much as allowed uploads in progress
         * 
         * @returns {Boolean}
         */
        isBusy: function() {
            return (this.maxConcurrentUploads == this.runningUploads);
        },
        
        /**
         * are there uploads in progress or queued
         */
        isUploadsPending: function (){
            return (this.uploads.length > 0);
        },
        
        /**
         * on upload complete handler
         */
        onUploadComplete: function() {
            
            Tine.Tinebase.uploadManager.runningUploads 
                = Math.max(0, Tine.Tinebase.uploadManager.runningUploads - 1);
    
            for(var uploadKey in Tine.Tinebase.uploadManager.uploads){

                var upload = Tine.Tinebase.uploadManager.uploads[uploadKey];
                if(upload.isQueued() && !upload.isPaused() && !Tine.Tinebase.uploadManager.isBusy()) {
                    upload.setQueued(false);
                    upload.resumeUpload();
                    
                    break;
                }
           }
            
        }, 
        
        /**
         * on upload start handler
         */
        onUploadStart: function() {
            Tine.Tinebase.uploadManager.runningUploads 
                = Math.min(Tine.Tinebase.uploadManager.maxConcurrentUploads, Tine.Tinebase.uploadManager.runningUploads + 1);
        }
    
});
/* Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Martin Jatho <m.jatho@metaways.de>
 * @copyright   Copyright (c) 2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux.file');

/**
 * a simple file upload
 * objects of this class represent a single file uplaod
 * 
 * @namespace   Ext.ux.file
 * @class       Ext.ux.file.Upload
 * @extends     Ext.util.Observable
 * 
 * @constructor
 * @param       Object config
 */
Ext.ux.file.Upload = function(config) {
    
    Ext.apply(this, config);
    
    Ext.ux.file.Upload.superclass.constructor.apply(this, arguments);
    
    this.addEvents(
        /**
         * @event uploadcomplete
         * Fires when the upload was done successfully 
         * @param {Ext.ux.file.Upload} this
         * @param {Ext.Record} Ext.ux.file.Upload.file
         */
         'uploadcomplete',
        /**
         * @event uploadfailure
         * Fires when the upload failed 
         * @param {Ext.ux.file.Upload} this
         * @param {Ext.Record} Ext.ux.file.Upload.file
         */
         'uploadfailure',
        /**
         * @event uploadprogress
         * Fires on upload progress (html5 only)
         * @param {Ext.ux.file.Upload} this
         * @param {Ext.Record} Ext.ux.file.Upload.file
         * @param {XMLHttpRequestProgressEvent}
         */
         'uploadprogress',
         /**
          * @event uploadstart
          * Fires on upload progress (html5 only)
          * @param {Ext.ux.file.Upload} this
          * @param {Ext.Record} Ext.ux.file.Upload.file
          */
          'uploadstart',
         /**
          * @event uploadstart
          * Fires on upload progress (html5 only)
          * @param {Ext.ux.file.Upload} this
          * @param {Ext.Record} Ext.ux.file.Upload.file
          */
         'update'
    );
        
    if (! this.file && this.fileSelector) {
        this.file = this.fileSelector.getFileList()[0];
    }
    
    this.fileSize = (this.file.size ? this.file.size : this.file.fileSize);

    this.maxChunkSize = this.maxPostSize - 16384;
    this.currentChunkSize = this.maxChunkSize;
    
    this.tempFiles = [];
};
 

Ext.extend(Ext.ux.file.Upload, Ext.util.Observable, {
    
    id: -1,
    
    /**
     * @cfg {Int} maxFileUploadSize the maximum file size for traditinal form updoads
     */
    maxFileUploadSize: 20971520, // 20 MB
    /**
     * @cfg {Int} maxPostSize the maximum post size used for html5 uploads
     */
    maxPostSize: 20971520, // 20 MB
    /**
     * @property maxChunkSize the maximum chunk size used for html5 uploads
     * @type Int
     */
    maxChunkSize: 20955136,
    /**
     * @cfg {Int} minChunkSize the minimal chunk size used for html5 uploads
     */
    minChunkSize: 102400,
    
    /**
     *  max number of upload retries
     */
    MAX_RETRY_COUNT: 10,
    
    /**
     *  retry timeout in milliseconds
     */
    RETRY_TIMEOUT_MILLIS: 3000,
    
    /**
     *  retry timeout in milliseconds
     */
    CHUNK_TIMEOUT_MILLIS: 100,
    
    /**
     * @cfg {String} url the url we upload to
     */
    url: 'index.php',
    /**
     * @cfg {Ext.ux.file.BrowsePlugin} fileSelector
     * a file selector
     */
    fileSelector: null,
    
    /**
     * coresponding file record
     */
    fileRecord: null,
    
    /**
     * currentChunk to upload
     */
    currentChunk: null,
       
    /**
     * uploadPath
     */
    uploadPath: '/',
    
    /**
     * file to upload
     */
    file: null,
    
    /**
     * is this upload paused
     */
    paused: false,
    
    /**
     * is this upload queued
     */
    queued: false,    
    
    /**
     * collected tempforary files
     */
    tempFiles: new Array(),
    
    /**
     * did the last chunk upload fail
     */
    lastChunkUploadFailed: false,
    
    /**
     * current chunk to upload
     */
    currentChunk: null,
    
    /**
     * how many retries were made while trying to upload current chunk
     */
    retryCount: 0,
    
    /**
     * size of the current chunk
     */
    currentChunkSize: 0,
    
    /**
     * where the chunk begins in file (byte number)
     */
    currentChunkPosition: 0,
    
    /**
     * size of the file to upload
     */
    fileSize: 0,
    
    
    /**
     * creates a form where the upload takes place in
     * @private
     */
    createForm: function() {
        var form = Ext.getBody().createChild({
            tag:'form',
            action:this.url,
            method:'post',
            cls:'x-hidden',
            id:Ext.id(),
            cn:[{
                tag: 'input',
                type: 'hidden',
                name: 'MAX_FILE_SIZE',
                value: this.maxFileUploadSize
            }]
        });
        return form;
    },
    
    /**
     * perform the upload
     * 
     * @return {Ext.Record} Ext.ux.file.Upload.file
     */
    upload: function() {
        if ((
                (! Ext.isGecko && window.XMLHttpRequest && window.File && window.FileList) || // safari, chrome, ...?
                (Ext.isGecko && window.FileReader) // FF
        ) && this.file) {
     
            // free browse plugin
            this.getInput();
            
            if (this.isHtml5ChunkedUpload()) {

//                if(this.fileSize > this.maxAllowedFileSize) { // admin confgured max file size (nothing technically)
//                    this.createFileRecord(true);
//                    this.fileRecord.beginEdit();
//                    this.fileRecord.set('status', 'failure');
//                    this.fileRecord.endEdit();
//                    this.fireEvent('uploadfailure', this, this.fileRecord);
//                    return this.fileRecord;
//                }
                
                // calculate optimal maxChunkSize       
                // TODO: own method for chunked upload
                
                var chunkMax = this.maxChunkSize;
                var chunkMin = this.minChunkSize;
                var actualChunkSize = this.maxChunkSize;

                if(this.fileSize > 5 * chunkMax) {
                    actualChunkSize = chunkMax;
                }
                else {
                    actualChunkSize = Math.max(chunkMin, this.fileSize / 5);
                }       
                this.maxChunkSize = actualChunkSize;
                
                if(Tine.Tinebase.uploadManager && Tine.Tinebase.uploadManager.isBusy()) {
                    this.createFileRecord(true);
                    this.setQueued(true);
                }
                else {
                    this.createFileRecord(false);
                    this.fireEvent('uploadstart', this);
                    this.fireEvent('update', 'uploadstart', this, this.fileRecord);
                    this.html5ChunkedUpload();
                    
                }
                
                return this.fileRecord;

            } else {
                this.createFileRecord(false);
                this.fireEvent('uploadstart', this);
                this.fireEvent('update', 'uploadstart', this, this.fileRecord);
                this.html5upload();
                
                return this.fileRecord;
            }
        } else {
            return this.html4upload();
        }

    },
    
    /**
     * 2010-01-26 Current Browsers implemetation state of:
     *  http://www.w3.org/TR/FileAPI
     *  
     *  Interface: File | Blob | FileReader | FileReaderSync | FileError
     *  FF       : yes  | no   | no         | no             | no       
     *  safari   : yes  | no   | no         | no             | no       
     *  chrome   : yes  | no   | no         | no             | no       
     *  
     *  => no json rpc style upload possible
     *  => no chunked uploads posible
     *  
     *  But all of them implement XMLHttpRequest Level 2:
     *   http://www.w3.org/TR/XMLHttpRequest2/
     *  => the only way of uploading is using the XMLHttpRequest Level 2.
     */
    html5upload: function() {
                 
        // TODO: move to upload method / checks max post size
        if(this.maxPostSize/1 < this.file.size/1 && !this.isHtml5ChunkedUpload()) {
            this.fileRecord.html5upload = true;
            this.onUploadFail(null, null, this.fileRecord);
            return this.fileRecord;
        }
        
        var defaultHeaders = {
            "Content-Type"          : "application/x-www-form-urlencoded",
            "X-Tine20-Request-Type" : "HTTP",
            "X-Requested-With"      : "XMLHttpRequest"
        };
        
        var xmlData = this.file;
               
        if(this.isHtml5ChunkedUpload()) {
            xmlData = this.currentChunk;
        }

        var conn = new Ext.data.Connection({
            disableCaching: true,
            method: 'POST',
            url: this.url + '?method=Tinebase.uploadTempFile',
            timeout: 300000, // 5 mins
            defaultHeaders: defaultHeaders
        });
                
        var transaction = conn.request({
            headers: {
                "X-File-Name"           : this.fileRecord.get('name'),
                "X-File-Type"           : this.fileRecord.get('type'),
                "X-File-Size"           : this.fileRecord.get('size')
            },
            xmlData: xmlData,
            success: this.onUploadSuccess.createDelegate(this, null, true),
            failure: this.onUploadFail.createDelegate(this, null, true) 
        });

        return this.fileRecord;
    },

    /**
     * Starting chunked file upload
     * 
     * @param {Boolean} whether this restarts a paused upload
     */
    html5ChunkedUpload: function() {
        this.prepareChunk();
        this.html5upload();
    },
    
    /**
     * resume this upload
     */
    resumeUpload: function() {
        this.setPaused(false);
        this.html5ChunkedUpload();
    },
    
    /**
     * calculation the next chunk size and slicing file
     */
    prepareChunk: function() {
        
        if(this.lastChunkUploadFailed) {
            this.currentChunkPosition = Math.max(0
                    , this.currentChunkPosition - this.currentChunkSize);

            this.currentChunkSize = Math.max(this.minChunkSize, this.currentChunkSize / 2);
        }
        else {
            this.currentChunkSize = Math.min(this.maxChunkSize, this.currentChunkSize * 2);
        }
        this.lastChunkUploadFailed = false;
        
        var nextChunkPosition = Math.min(this.fileSize, this.currentChunkPosition 
                +  this.currentChunkSize);
        var newChunk = this.sliceFile(this.file, this.currentChunkPosition, nextChunkPosition);
        
        if(nextChunkPosition/1 == this.fileSize/1) {
            this.lastChunk = true;
        }
                     
        this.currentChunkPosition = nextChunkPosition;
        this.currentChunk = newChunk;
       
    },
    
    /**
     * Setting final fileRecord states
     */
    finishUploadRecord: function(response) {
        
        response = Ext.util.JSON.decode(response.responseText);
        if(response.tempFile) {
            response = response.tempFile;
        }
                
        if(response.error == 0) {
            this.fileRecord.beginEdit();
            this.fileRecord.set('size', response.size);
            this.fileRecord.set('id', response.id);
            this.fileRecord.set('progress', 99);
            this.fileRecord.set('tempFile', '');
            this.fileRecord.set('tempFile', response);
            this.fileRecord.commit(false);
            this.fireEvent('uploadcomplete', this, this.fileRecord);
            this.fireEvent('update', 'uploadcomplete', this, this.fileRecord);

        }       
        else {
            this.fileRecord.beginEdit();
            this.fileRecord.set('status', 'failure');
            this.fileRecord.set('progress', -1);
            this.fileRecord.set('tempFile', '');
            this.fileRecord.set('tempFile', response);
            this.fileRecord.commit(false);
            this.fireEvent('update', 'uploadfailure', this, this.fileRecord);
                       
        }
                
    },
    
   
    /**
     * executed if a chunk or file got uploaded successfully
     */
    onUploadSuccess: function(response, options, fileRecord) {
        
        var responseObj = Ext.util.JSON.decode(response.responseText);

        this.retryCount = 0;

        if(responseObj.status && responseObj.status !== 'success') {
            this.onUploadFail(responseObj, options, fileRecord);
        }

        this.fileRecord.beginEdit();
        this.fileRecord.set('tempFile', responseObj.tempFile);
        this.fileRecord.set('size', 0);
        this.fileRecord.commit(false);

        this.fireEvent('update', 'uploadprogress', this, this.fileRecord);
        
        if(! this.isHtml5ChunkedUpload()) {

            this.finishUploadRecord(response);
        }       
        else {

            this.addTempfile(this.fileRecord.get('tempFile'));
            var percent = parseInt(this.currentChunkPosition * 100 / this.fileSize/1);
            
            if(this.lastChunk) {
                percent = 98;
            }

            this.fileRecord.beginEdit();
            this.fileRecord.set('progress', percent);
            this.fileRecord.commit(false);

            if(this.lastChunk) {

                window.setTimeout((function() {
                    Ext.Ajax.request({
                        timeout: 10*60*1000, // Overriding Ajax timeout - important!
                        params: {
                        method: 'Tinebase.joinTempFiles',
                        tempFilesData: this.tempFiles
                    },
                    success: this.finishUploadRecord.createDelegate(this), 
                    failure: this.finishUploadRecord.createDelegate(this)
                    });
                }).createDelegate(this), this.CHUNK_TIMEOUT_MILLIS);

            }
            else {
                window.setTimeout((function() {
                    if(!this.isPaused()) {
                        this.prepareChunk();
                        this.html5upload();
                    }
                }).createDelegate(this), this.CHUNK_TIMEOUT_MILLIS);
            }                                               

        }  

    },


    /**
     * executed if a chunk / file upload failed
     */
    onUploadFail: function(response, options, fileRecord) {

        if (this.isHtml5ChunkedUpload()) {
            
            this.lastChunkUploadFailed = true;
            this.retryCount++;
            
            if (this.retryCount > this.MAX_RETRY_COUNT) {
                
                this.fileRecord.beginEdit();
                this.fileRecord.set('status', 'failure');
                this.fileRecord.endEdit();

                this.fireEvent('update', 'uploadfailure', this, this.fileRecord);
            }
            else {
                window.setTimeout((function() {
                    this.prepareChunk();
                    this.html5upload();
                }).createDelegate(this), this.RETRY_TIMEOUT_MILLIS);
            }
        }
        else {
            this.fileRecord.beginEdit();
            this.fileRecord.set('status', 'failure');
            this.fileRecord.endEdit();

            this.fireEvent('update', 'uploadfailure', this, this.fileRecord);
        }
    },
    
    
    /**
     * uploads in a html4 fashion
     * 
     * @return {Ext.data.Connection}
     */
    html4upload: function() {
                
        var form = this.createForm();
        var input = this.getInput();
        form.appendChild(input);
        
        this.fileRecord = new Ext.ux.file.Upload.file({
            name: this.fileSelector.getFileName(),
            size: 0,
            type: this.fileSelector.getFileCls(),
            input: input,
            form: form,
            status: 'uploading',
            progress: 0
        });
        
        this.fireEvent('update', 'uploadprogress', this, this.fileRecord);
        
        if(this.maxFileUploadSize/1 < this.file.size/1) {
            this.fileRecord.html4upload = true;
            this.onUploadFail(null, null, this.fileRecord);
            return this.fileRecord;
        }
        
        Ext.Ajax.request({
            fileRecord: this.fileRecord,
            isUpload: true,
            method:'post',
            form: form,
            success: this.onUploadSuccess.createDelegate(this, [this.fileRecord], true),
            failure: this.onUploadFail.createDelegate(this, [this.fileRecord], true),
            params: {
                method: 'Tinebase.uploadTempFile',
                requestType: 'HTTP'
            }
        });
        
        return this.fileRecord;
    },
    
    /**
     * creating initial fileRecord for this upload
     */
    createFileRecord: function(pending) {
               
        var status = "uploading";
        if(pending) {
            status = "pending";
        }

        this.fileRecord = new Ext.ux.file.Upload.file({
            name: this.file.name ? this.file.name : this.file.fileName,  // safari and chrome use the non std. fileX props
            type: (this.file.type ? this.file.type : this.file.fileType), // missing if safari and chrome
            size: 0,
            status: status,
            progress: 0,
            input: this.file,
            uploadKey: this.id
        });
        
        this.fireEvent('update', 'uploadprogress', this, this.fileRecord);

    },
   
    /** 
     * adding temporary file to array 
     * 
     * @param tempfile to add
     */
    addTempfile: function(tempFile) {
        this.tempFiles.push(tempFile);
        return true;
    },
    
    /**
     * returns the temporary files
     * 
     * @returns {Array} temporary files
     */
    getTempfiles: function() {
        return this.tempFiles;
    },
    
    /**
     * pause oder resume file upload
     * 
     * @param paused {Boolean} set true to pause file upload
     */
    setPaused: function(paused) {
        this.paused = paused;
        
        var pausedState = 'paused';
        if(!this.paused) {
            pausedState = 'uploading';
        }
            
        this.fileRecord.beginEdit();
        this.fileRecord.set('status', pausedState);
        this.fileRecord.endEdit();
        this.fireEvent('update', 'uploadpaused', this, this.fileRecord);
    },
    
    /**
     * indicates whether this upload ist paused
     * 
     * @returns {Boolean}
     */
    isPaused: function() {
        return this.paused;
    },
    
    /**
     * checks for the existance of a method of an object
     * 
     * @param object    {Object}
     * @param property  {String} method name 
     * @returns {Boolean}
     */
    isHostMethod: function (object, property) {
        var t = typeof object[property];
        return t == 'function' || (!!(t == 'object' && object[property])) || t == 'unknown';
    },
    
    /**
     * indicates whether the current browser supports der File.slice method
     * 
     * @returns {Boolean}
     */
    isHtml5ChunkedUpload: function() {
                    
        if(window.File == undefined) return false;
        if(this.isHostMethod(File.prototype, 'slice') || this.isHostMethod(File.prototype, 'mozSlice') || this.isHostMethod(File.prototype, 'webkitSlice')) {
            return this.fileSize > this.minChunkSize;
        }
        else {
            return false;
        }
    },
    
    // private
    getInput: function() {
        if (! this.input) {
            this.input = this.fileSelector.detachInputFile();
        }
        
        return this.input;
    },

    /**
     * slices the given file
     * 
     * @param file  File object
     * @param start start position
     * @param end   end position            
     * @param type  file type
     * @returns
     */
    sliceFile: function(file, start, end, type) {
        
        if(file.slice) {
            return file.slice(start, end);
        }
        else if(file.mozSlice) {
            return file.mozSlice(start, end, type);
        }
        else if(file.webkitSlice) {
            return file.webkitSlice(start, end);
        }
        else {
            return false;
        }
        
    },
    
    /**
     * sets dthe queued state of this upload
     * 
     * @param queued {Boolean}
     */
    setQueued: function (queued) {
        this.queued = queued;
        
        var queuedState = 'queued';
        if(!this.queued) {
            queuedState = 'uploading';
        }
            
        this.fileRecord.beginEdit();
        this.fileRecord.set('status', queuedState);
        this.fileRecord.endEdit();
        
        this.fireEvent('update', 'uploadqueued', this, this.fileRecord);
        
    },
    
    /**
     * indicates whethe this upload is queued
     * 
     * @returns {Boolean}
     */
    isQueued: function() {
        return this.queued;
    }
    
            
});

/**
 * upload file record
 */
Ext.ux.file.Upload.file = Ext.data.Record.create([
    {name: 'id', type: 'text', system: true},
    {name: 'uploadKey', type: 'text', system: true},
    {name: 'name', type: 'text', system: true},
    {name: 'size', type: 'number', system: true},
    {name: 'type', type: 'text', system: true},
    {name: 'status', type: 'text', system: true},
    {name: 'progress', type: 'number', system: true},
    {name: 'form', system: true},
    {name: 'input', system: true},
    {name: 'request', system: true},
    {name: 'path', system: true},
    {name: 'tempFile', system: true}
]);

Ext.ux.file.Upload.file.getFileData = function(file) {
    return Ext.copyTo({}, file.data, ['tempFile', 'name', 'path', 'size', 'type']);
};
/*
 * Tine 2.0
 * 
 * @license     New BSD License
 * @author      loeppky - based on the work done by MaximGB in Ext.ux.UploadDialog (http://extjs.com/forum/showthread.php?t=21558)
 *
 */
Ext.ns('Ext.ux.file');

/**
 * @namespace   Ext.ux.file
 * @class       Ext.ux.file.BrowsePlugin
 * @param       {Object} config Configuration options
 */
Ext.ux.file.BrowsePlugin = function(config) {
    Ext.apply(this, config);
};

Ext.ux.file.BrowsePlugin.prototype = {
    /**
     * @cfg {Boolean} multiple
     * allow multiple files to be selected (HTML 5 only)
     */
    multiple: false,
    /**
     * @cfg {Ext.Element} dropEl 
     * element used as drop target if enableFileDrop is enabled
     */    
    dropEl: null,
    /**
     * @cfg {Boolean} enableFileDrop
     * @see http://www.w3.org/TR/2008/WD-html5-20080610/editing.html
     * 
     * enable drops from OS (defaults to true)
     */
    enableFileDrop: true,
    /**
     * @cfg {Boolean} enableFileDialog
     * 
     * enable file dialog on click(defaults to true)
     */
    enableFileDialog: true,
    /**
     * @cfg {String} inputFileName
     * Name to use for the hidden input file DOM element.  Deaults to "file".
     */
    inputFileName: 'file',
    /**
     * @cfg {Function} onBrowseButtonClick
     */
    onBrowseButtonClick: Ext.emptyFn,
    /**
     * @property inputFileEl
     * @type Ext.Element
     * Element for the hiden file input.
     * @private
     */
    input_file: null,
    /**
     * @cfg handler
     * @type Function
     * The handler originally defined for the Ext.Button during construction using the "handler" config option.
     * We need to null out the "handler" property so that it is only called when a file is selected.
     * @private
     */
    handler: null,
    /**
     * @cfg scope
     * @type Object
     * The scope originally defined for the Ext.Button during construction using the "scope" config option.
     * While the "scope" property doesn't need to be nulled, to be consistent with handler, we do.
     * @private
     */
    scope: null,
    
    currentTreeNodeUI: null,
    
    /*
     * Protected Ext.Button overrides
     */
    /**
     * @see Ext.Button.initComponent
     */
    init: function(cmp){
        if(cmp.handler) this.handler = cmp.handler;
        this.scope = cmp.scope || window;
        cmp.handler = null;
        cmp.scope = null;
        
        this.component = cmp;
        
        cmp.on('afterrender', this.onRender, this);
        cmp.on('show', this.syncWrap, this);
        cmp.on('resize', this.syncWrap, this);
        cmp.on('afterlayout', this.syncWrap, this);
        
        // chain fns
        if (typeof cmp.setDisabled == 'function') {
            cmp.setDisabled = cmp.setDisabled.createSequence(function(disabled) {
                if (this.input_file) {
                    this.input_file.dom.disabled = disabled;
                }
            }, this);
        }
        
        if (typeof cmp.enable == 'function') {
            cmp.enable = cmp.enable.createSequence(function() {
                if (this.input_file) {
                    this.input_file.dom.disabled = false;
                }
            }, this);
        }
        
        if (typeof cmp.disable == 'function') {
            cmp.disable = cmp.disable.createSequence(function() {
                if (this.input_file) {
                    this.input_file.dom.disabled = true;
                }
            }, this);
        }
        
        if (typeof cmp.destroy == 'function') {
            cmp.destroy = cmp.destroy.createSequence(function() {
                var input_file = this.detachInputFile(true);
                if (input_file) {
                    input_file.remove();
                }
                input_file = null;
            }, this);
        }
        
    },
    
    /**
     * @see Ext.Button.onRender
     */
    onRender: function() {
       
        this.button_container = this.buttonCt || this.component.el.child('tbody') || this.component.el;
        this.button_container.position('relative');
        this.wrap = this.component.el.wrap({cls:'tbody'});

        // NOTE: wrap a button is complex, its doLayout moves the wrap
        if (this.component.ownerCt && this.component.btnEl/* && this.component.ownerCt.el.hasClass('x-toolbar')*/) {
            this.component.ownerCt.on('afterlayout', function() {
                if (this.wrap.first() !== this.component.el) {
                    this.wrap.insertBefore(this.component.el);
                    this.wrap.insertFirst(this.component.el);
                }
                this.syncWrap();
            }, this);
            
            this.component.ownerCt.on('show', this.syncWrap, this);
            this.component.ownerCt.on('resize', this.syncWrap, this);
        }
        
        if (this.enableFileDialog) this.createInputFile();
      
        if (this.enableFileDrop && !Ext.isIE) {
            if (! this.dropEl) {
                if (this.dropElSelector) {
                    this.dropEl = this.wrap.up(this.dropElSelector);
                } else {
                    this.dropEl = this.button_container;
                }
            }
            
            this.dropEl.on('dragleave', function(e) {
                e.stopPropagation();
                e.preventDefault();

                this.createMouseEvent(e, 'mouseout');
            }, this);

            // @see http://dev.w3.org/html5/spec/Overview.html#the-dragevent-and-datatransfer-interfaces
            this.dropEl.on('dragover', function(e) {

                e.stopPropagation();
                e.preventDefault();
                                         
                this.createMouseEvent(e, 'mouseover');
                
            }, this);
            
            this.dropEl.on('drop', function(e, target) {
               
                e.stopPropagation();
                e.preventDefault();
                
                // a bit hackish, I know...
                this.onBrowseButtonClick();
                
                var dt = e.browserEvent.dataTransfer;
                var files = dt.files;
                
                this.onInputFileChange(e, null, null, files);
            }, this);
        }
    },
    
    syncWrap: function() {
        if (this.button_container) {
            var button_size = this.button_container.getSize();
            this.wrap.setSize(button_size);
        }
    },
    
    createInputFile: function() {
        this.input_file = this.wrap.createChild(Ext.apply({
            tag: 'input',
            type: 'file',
            size: 1,
            name: this.inputFileName || Ext.id(this.component.el),
            style: "position: absolute; display: block; border: none; cursor: pointer;"
        }, this.multiple ? {multiple: true} : {}));

        this.input_file.dom.disabled = this.component.disabled;
        
        var button_box = this.button_container.getBox();
        
        this.wrap.setBox(button_box);

        this.wrap.applyStyles('overflow: hidden; position: relative;');
        
        // ALL but IE
        this.wrap.on('mousemove', function(e) {
            var xy = e.getXY();
            this.input_file.setXY([xy[0] - this.input_file.getWidth()/2, xy[1] - 5]);
            
            // if split button
            if(this.component.btnEl) {
                var buttonEl = Ext.get(this.wrap.dom);
                var buttonX = buttonEl.getX(),
                    buttonWidth = buttonEl.getWidth(),
                    mouseX = xy[0];
                
                if(mouseX - buttonX > this.component.btnEl.dom.clientWidth) {
                    this.input_file.dom.style.zIndex = -1;
                }
                else {
                    this.input_file.dom.style.zIndex = '';
                }
            }
            
        }, this, {buffer: 20});
        
        
        // IE
        this.button_container.on('mousemove', function(e) {
            var xy = e.getXY();
            this.input_file.setXY([xy[0] - this.input_file.getWidth()/2, xy[1] - 5]);
            
            // if split button
            if(this.component.btnEl) {
                var buttonEl = Ext.get(this.button_container.dom);
                var buttonX = buttonEl.getX(),
                    buttonWidth = buttonEl.getWidth(),
                    mouseX = xy[0];
                
                if(mouseX - buttonX > this.component.btnEl.dom.clientWidth) {
                    this.input_file.dom.style.zIndex = -1;
                }
                else {
                    this.input_file.dom.style.zIndex = '';
                }
            }
        }, this, {buffer: 30});
        
        this.input_file.setOpacity(0.0);

        Ext.fly(this.input_file).on('click', function(e) {
            this.onBrowseButtonClick();
        }, this);
        
        // FIX mouseover / out
        if (! this.supressOverFix && Ext.isFunction(this.component.onMouseOut)) {
            this.component.onMouseOut = this.component.onMouseOut.createInterceptor(function(e) {
                if (this.isMouseOver) {
                    return false;
                }
                this.isMouseOver = false;
            }, this);
            
            this.wrap.on('mouseover', function(e) {
                this.isMouseOver = true;
                if (this.component.el.hasClass('x-btn') && !this.component.disabled) {
                    this.component.el.addClass('x-btn-over');
                }
            }, this);
    
            this.wrap.on('mouseout', function(e) {
                this.isMouseOver = false;
                if (this.component.el.hasClass('x-btn') && !this.component.disabled) {
                    this.component.el.removeClass('x-btn-over');
                }
            }, this);
        }
        
        if (this.component.handleMouseEvents) {
            this.wrap.on('mouseover', this.component.onMouseOver || Ext.emptyFn, this.component);
            this.wrap.on('mousedown', this.component.onMouseDown || Ext.emptyFn, this.component);
            this.wrap.on('contextmenu', this.component.onContextMenu || Ext.emptyFn, this.component);
        }
        
        if(this.component.tooltip){
            if(typeof this.component.tooltip == 'object'){
                Ext.QuickTips.register(Ext.apply({target: this.input_file}, this.component.tooltip));
            } 
            else {
                this.input_file.dom[this.component.tooltipType] = this.component.tooltip;
            }
        }
        
        this.input_file.on('change', this.onInputFileChange, this);
        this.input_file.on('click', function(e) { e.stopPropagation(); });
    },
    
    /**
     * Handler when inputFileEl changes value (i.e. a new file is selected).
     * @param {FileList} files when input comes from drop...
     * @private
     */
    onInputFileChange: function(e, target, options, files){
        if (window.FileList) { // HTML5 FileList support
            this.files = files ? files : this.input_file.dom.files;
        } else {
            this.files = [{
                name : this.input_file.getValue().split(/[\/\\]/).pop()
            }];
            this.files[0].type = this.getFileCls();
        }
        
        if (this.handler) {
            this.handler.call(this.scope, this, e);
        }
    },
    
    /**
     * Detaches the input file associated with this BrowseButton so that it can be used for other purposed (e.g. uplaoding).
     * The returned input file has all listeners and tooltips applied to it by this class removed.
     * @param {Boolean} whether to create a new input file element for this BrowseButton after detaching.
     * True will prevent creation.  Defaults to false.
     * @return {Ext.Element} the detached input file element.
     */
    detachInputFile : function(no_create) {
        var result = this.input_file;
        
        no_create = no_create || false;
        
        if (this.input_file) {
            if (typeof this.component.tooltip == 'object') {
                Ext.QuickTips.unregister(this.input_file);
            }
            else {
                this.input_file.dom[this.component.tooltipType] = null;
            }
            this.input_file.removeAllListeners();
        }
        this.input_file = null;
        
        if (this.enableFileDialog && !no_create) {
            this.createInputFile();
        }
        return result;
    },
    
    getFileList: function() {
        return this.files;
    },
    
    /**
     * @return {Ext.Element} the input file element
     */
    getInputFile: function(){
        return this.input_file;
    },
    /**
     * get file name
     * @return {String}
     */
    getFileName:function() {
        var file = this.getFileList()[0];
        return file.name ? file.name : file.fileName;
    },
    
    /**
     * returns file class based on name extension
     * @return {String} class to use for file type icon
     */
    getFileCls: function() {

        var fparts = this.getFileName().split('.');
        if(fparts.length === 1) {
            return '';
        }
        else {
            return fparts.pop().toLowerCase();
        }
    },
    
    isImage: function() {
        var cls = this.getFileCls();
        return (cls == 'jpg' || cls == 'gif' || cls == 'png' || cls == 'jpeg');
    },
    
    createMouseEvent: function(e, mouseEventType) {
        
        if(document.createEvent)  {
            var evObj = document.createEvent('MouseEvents');
            evObj.initMouseEvent(mouseEventType, true, true, window, e.browserEvent.detail, e.browserEvent.screenX, e.browserEvent.screenY
                    , e.browserEvent.clientX, e.browserEvent.clientY, e.browserEvent.ctrlKey, e.browserEvent.altKey
                    , e.browserEvent.shiftKey, e.browserEvent.metaKey, e.browserEvent.button, e.browserEvent.relatedTarget);
            e.target.dispatchEvent(evObj);
        }    
        // TODO: IE problem on drag over tree nodes (not the whole row tracks onmouseout)
        else if(document.createEventObject) {
            var evObj = document.createEventObject();
            evObj.detail = e.browserEvent.detail;
            evObj.screenX = e.browserEvent.screenX;
            evObj.screenY = e.browserEvent.screenY;
            evObj.clientX = e.browserEvent.clientX;
            evObj.clientY = e.browserEvent.clientY;
            evObj.ctrlKey = e.browserEvent.ctrlKey;
            evObj.altKey = e.browserEvent.altKey;
            evObj.shiftKey = e.browserEvent.shiftKey;
            evObj.metaKey = e.browserEvent.metaKey;
            evObj.button = e.browserEvent.button;
            evObj.relatedTarget = e.browserEvent.relatedTarget;
            e.getTarget('div').fireEvent('on' + mouseEventType ,evObj);
        }
        
    }
};

Ext.preg('ux.browseplugin', Ext.ux.file.BrowsePlugin);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux.file');

/**
 * @namespace   Ext.ux.file
 * @class       Ext.ux.file.Download
 * @extends     Ext.util.Observable
 */
Ext.ux.file.Download = function(config) {
    config = config || {};
    Ext.apply(this, config);
    
    Ext.ux.file.Download.superclass.constructor.call(this);
    
    this.addEvents({
        'success': true,
        'fail': true,
        'abort': true
    });
};

Ext.extend(Ext.ux.file.Download, Ext.util.Observable, {
    url: 'index.php',
    method: 'POST',
    params: null,
    timeout: 1800000, // 30 minutes
    
    /**
     * @private 
     */
    form: null,
    transactionId: null,
    
    /**
     * start download
     */
    start: function() {
        this.form = Ext.getBody().createChild({
            tag:'form',
            method: this.method,
            cls:'x-hidden'
        });

        var con = new Ext.data.Connection({
            // firefox specific problem -> see http://www.extjs.com/forum/archive/index.php/t-44862.html
            //  "It appears that this is because the "load" is completing once the initial download dialog is displayed, 
            //  but the frame is then destroyed before the "save as" dialog is shown."
            //
            // TODO check if we can handle firefox event 'onSaveAsSubmit' (or something like that)
            //
            debugUploads: Ext.isGecko
        });
        
        this.transactionId = con.request({
            isUpload: true,
            form: this.form,
            params: this.params,
            scope: this,
            success: this.onSuccess,
            failure: this.onFailure,
            url: this.url,
            timeout: this.timeout
        });
    },
    
    /**
     * abort download
     */
    abort: function() {
        Ext.Ajax.abort(this.transactionId);
        this.form.remove();
        this.fireEvent('abort', this);
    },
    
    /**
     * @private
     * 
     */
    onSuccess: function() {
        this.form.remove();
        this.fireEvent('success', this);
    },
    
    /**
     * @private
     * 
     */
    onFailure: function() {
        this.form.remove();
        this.fireEvent('fail', this);
    }
    
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux', 'Ext.ux.form');

/**
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.ColorField
 * @extends     Ext.form.TriggerField
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * Provides a color input field with a {@link Ext.menu.ColorMenu} dropdown.
 * @constructor Create a new ColorField
 * @param {Object} config
 * @xtype colorfield
 */
Ext.ux.form.ColorField = Ext.extend(Ext.form.TriggerField, {
    listWidth: 150,
    editable: false,
    
    // private
    initComponent : function(){
        Ext.ux.form.ColorField.superclass.initComponent.call(this);
        
        this.store = new Ext.data.Store({});
        
        this.addEvents(
            /**
             * @event select
             * Fires when a color is selected via the color picker.
             * @param {Ext.form.ColorField} this
             * @param {String} color The color that was selected
             */
            'select'
        );
    },
    
    // private
    onDestroy : function(){
        Ext.destroy(this.menu);
        Ext.ux.form.ColorField.superclass.onDestroy.call(this);
    },
    
    //private
    onTriggerClick : function(){
        if(this.disabled){
            return;
        }
        if(this.menu == null){
            this.menu = new Ext.menu.ColorMenu({
                hideOnClick: false
            });
        }
        this.onFocus();

        this.menu.show(this.el, "tl-bl?");

        this.menuEvents('on');
    },

    setValue : function(color){
        color = color || '#FFFFFF';
        Ext.ux.form.ColorField.superclass.setValue.call(this, color);
        
        this.el.setStyle('background', color);
        this.el.setStyle('color', color);
    },
    
    //private
    menuEvents: function(method){
        this.menu[method]('select', this.onSelect, this);
        this.menu[method]('hide', this.onMenuHide, this);
        this.menu[method]('show', this.onFocus, this);
    },
    
    //private
    onSelect: function(m, d){
        this.setValue('#'+d);
        this.fireEvent('select', this, '#'+d);
        this.menu.hide();
    },
    
    //private
    onMenuHide: function(){
        this.focus(false, 60);
        this.menuEvents('un');
    }
});

Ext.reg('colorfield', Ext.ux.form.ColorField);
/*
 * Tine 2.0
 * 
 * @package     Ext
 * @subpackage  ux
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Ext.ux', 'Ext.ux.form');

/**
 * Class for creating text-fields with an icon in front of the label.
 * <p>Example usage:</p>
 * <pre><code>
 var field =  new Ext.ux.form.IconTextField({
     labelIcon: 'images/oxygen/16x16/actions/mail.png'
     fieldLabel: 'email',
     name: 'email',
 });
 * </code></pre>
 * 
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.IconTextField
 * @extends     Ext.form.TextField
 */
Ext.ux.form.IconTextField = Ext.extend(Ext.form.TextField, {
    /**
     * @cfg {String} LabelIcon icon to be displayed in front of the label
     */
    labelIcon: '',
    /**
     * @private
     */
    initComponent: function(){
         Ext.ux.form.IconTextField.superclass.initComponent.call(this);
         if (this.labelIcon.length > 0){
            this.fieldLabel = '<img src="' + this.labelIcon + '" class="x-ux-form-icontextfield-labelicon">' + this.fieldLabel;
         }
    }
});
Ext.reg('icontextfield', Ext.ux.form.IconTextField);
/*
 * Tine 2.0
 * 
 * @package     Ext
 * @subpackage  ux
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Ext.ux', 'Ext.ux.form');

/**
 * Class for creating equal text-fields multiple times in a form.
 * If a value gets changed in one of the fields, all other will be updated
 * <p>Example usage:</p>
 * <pre><code>
 var dialog =  new Ext.Panel({
     layout: 'form',
     items: [
         {
             fieldLabel: 'First occurrence',
             xtype: 'mirrortextfield',
             name: 'themirrorfiled'
         },{
             fieldLabel: 'Normal field',
             xtype: 'textfield',
             name: 'someotherfield'
         },{
             fieldLabel: 'Second occurrence',
             xtype: 'mirrortextfield',
             name: 'themirrorfiled'
         },
     ]
 });
 * </code></pre>
 * 
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.MirrorTextField
 * @extends     Ext.ux.form.IconTextField
 */
Ext.ux.form.MirrorTextField = Ext.extend(Ext.ux.form.IconTextField, {
    /**
     * @private
     */
    initComponent: function(){
         Ext.ux.form.MirrorTextField.superclass.initComponent.call(this);
         Ext.ux.form.MirrorTextFieldManager.register(this);
    },
    /**
     * @private
     */
    setValue: function(value){
        Ext.ux.form.MirrorTextFieldManager.setAll(this, value);
    },
    /**
     * @private
     */
    onDestroy : function(){
        if(this.rendered){
            Ext.ux.form.MirrorTextFieldManager.unregister(this);
        }
    }
});
Ext.reg('mirrortextfield', Ext.ux.form.MirrorTextField);

/**
 * Helper for Ext.ux.form.MirrorTextField
 * @singleton
 */
Ext.ux.form.MirrorTextFieldManager = function() {
    var MirrorTextFields = {};
    
    function MirrorField(field, newValue, oldValue) {
        var m = MirrorTextFields[field.name];
        for(var i = 0, l = m.length; i < l; i++){
            m[i].setRawValue(newValue);
        }
        return true;
    }
    
    return {
        register: function(field) {
            var m = MirrorTextFields[field.name];
            if(!m){
                m = MirrorTextFields[field.name] = [];
            }
            m.push(field);
            field.on("change", MirrorField);
        },
        
        unregister: function(field) {
            var m = MirrorTextFields[field.name];
            if(m){
                m.remove(field);
                field.un("change", MirrorField);
            }
        },
        
        setAll: function(field, value) {
            var m = MirrorTextFields[field.name];
            if(m){
                MirrorField(field, value);
            }
        }
    };
}();
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux', 'Ext.ux.form');

/**
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.ColumnFormPanel
 * @description
 * Helper Class for creating form panels with a horizontal layout. This class could be directly
 * created using the new keyword or by specifying the xtype: 'columnform'
 * Example usage:</p>
 * <pre><code>
var p = new Ext.ux.form.ColumnFormPanel({
    title: 'Horizontal Form Layout',
    items: [
        [
            {
                columnWidth: .6,
                fieldLabel:'Company Name', 
                name:'org_name'
            },
            {
                columnWidth: .4,
                fieldLabel:'Street', 
                name:'adr_one_street'
            }
        ],
        [
            {
                columnWidth: .7,
                fieldLabel:'Region',
                name:'adr_one_region'
            },
            {
                columnWidth: .3,
                fieldLabel:'Postal Code', 
                name:'adr_one_postalcode'
            }
        ]
    ]
});
</code></pre>
 */
Ext.ux.form.ColumnFormPanel = Ext.extend(Ext.Panel, {

    formDefaults: {
        xtype:'icontextfield',
        anchor: '100%',
        labelSeparator: '',
        columnWidth: .333
    },
    
    layout: 'hfit',
    labelAlign: 'top',
    /**
     * @private
     */
    initComponent: function() {
        var items = [];
            
        // each item is an array with the config of one row
        for (var i=0,j=this.items.length; i<j; i++) {
            
            var initialRowConfig = this.items[i];
            var rowConfig = {
                border: false,
                layout: 'column',
                items: []
            };
            // each row consits n column objects 
            for (var n=0,m=initialRowConfig.length; n<m; n++) {
                var column = initialRowConfig[n];
                var idx = rowConfig.items.push({
                    columnWidth: column.columnWidth ? column.columnWidth : this.formDefaults.columnWidth,
                    layout: 'form',
                    labelAlign: this.labelAlign,
                    defaults: this.formDefaults,
                    bodyStyle: 'padding-right: 5px;',
                    border: false,
                    items: column
                });
                
                if (column.width) {
                    rowConfig.items[idx-1].width = column.width;
                    delete rowConfig.items[idx-1].columnWidth;
                }
            }
            items.push(rowConfig);
        }
        this.items = items;
        
        Ext.ux.form.ColumnFormPanel.superclass.initComponent.call(this);
    }
});

Ext.reg('columnform', Ext.ux.form.ColumnFormPanel);

/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Ext.ux.form.');

/**
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.LayerCombo
 * @extends     Ext.form.TriggerField
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * 
 * trigger field with support for extra layer on trigger click
 */
Ext.ux.form.LayerCombo = function(config) {
    Ext.ux.form.LayerCombo.superclass.constructor.call(this, config);
    
    this.addEvents(
        /**
         * @event beforecollapse
         * Fires before the layer is collapsed by calling the {@link #collapse} method.
         * Return false from an event handler to stop the collapse.
         * @param {LayerCombo} this
         */
        'beforecollapse'
    );
};

Ext.extend(Ext.ux.form.LayerCombo, Ext.form.TriggerField, {
    /**
     * @cfg {Boolean} hideButton Hide standard form buttons 
     */
    hideButtons: false,
    /**
     * @cfg {Object} config for innerForm
     */
    formConfig: null,
    /**
     * @cfg {Boolean} lazyInit <tt>true</tt> to not initialize the list for this combo until the field is focused
     * (defaults to <tt>true</tt>)
     */
    lazyInit : true,
    /**
     * @cfg {String} triggerClass An additional CSS class used to style the trigger button.  The trigger will always
     * get the class <tt>'x-form-trigger'</tt> and <tt>triggerClass</tt> will be <b>appended</b> if specified
     * (defaults to <tt>'x-form-arrow-trigger'</tt> which displays a downward arrow icon).
     */
    triggerClass : 'x-form-arrow-trigger',
    /**
     * @cfg {Boolean/String} shadow <tt>true</tt> or <tt>"sides"</tt> for the default effect, <tt>"frame"</tt> for
     * 4-way shadow, and <tt>"drop"</tt> for bottom-right
     */
    shadow : 'sides',
    /**
     * @cfg {String/Array} layerAlign A valid anchor position value. See <tt>{@link Ext.Element#alignTo}</tt> for details
     * on supported anchor positions and offsets. To specify x/y offsets as well, this value
     * may be specified as an Array of <tt>{@link Ext.Element#alignTo}</tt> method arguments.</p>
     * <pre><code>[ 'tl-bl?', [6,0] ]</code></pre>(defaults to <tt>'tr-br?'</tt>)
     */
    layerAlign : 'tl-bl?',
    /**
     * @cfg {String} layerClass The CSS class to add to the predefined <tt>'x-combo-layer'</tt> class
     * applied the dropdown layer element (defaults to '').
     */
    layerClass : '',
    /**
     * @cfg {Number} minLayerWidth The minimum width of the dropdown layer in pixels (defaults to <tt>70</tt>, will
     * be ignored if <tt>{@link #layerWidth}</tt> has a higher value)
     */
    minLayerWidth : 70,
    /**
     * @cfg {Number} layerWidth The width (used as a parameter to {@link Ext.Element#setWidth}) of the dropdown
     * layer (defaults to the width of the ComboBox field).  See also <tt>{@link #minLayerWidth}</tt>
     */
    /**
     * @cfg {Number} minLayerHeight The minimum height of the dropdown layer in pixels (defaults to <tt>70</tt>, will
     * be ignored if <tt>{@link #layerHeight}</tt> has a higher value)
     */
    minLayerHeight: 70,
    /**
     * @cfg {Number} layerHeight The width (used as a parameter to {@link Ext.Element#setHeight}) of the dropdown
     * layer (defaults to the width of the ComboBox field).  See also <tt>{@link #minLayerHeight}</tt>
     */
    
    /**
     * @property currentValue
     * @type mixed
     */
    currentValue: null,
    
    editable: false,
    
    /**
     * Hides the dropdown layer if it is currently expanded. Fires the {@link #collapse} event on completion.
     */
    collapse: function() {
        if(!this.isExpanded()){
            return;
        }
        if(this.fireEvent('beforecollapse', this) !== false){
            this.layer.hide();
            Ext.getDoc().un('mousewheel', this.collapseIf, this);
            Ext.getDoc().un('mousedown', this.collapseIf, this);
            this.fireEvent('collapse', this);
        }
    },
    
    
    /**
     * @private
     * @param {Ext.EventObject} e
     */
    collapseIf : function(e){
        if(!e.within(this.wrap) && !e.within(this.layer)){
            this.collapse();
        }
    },
    
    /**
     * this fn is here so this filed is detected like a combo
     * @private
     */
    doQuery: Ext.emptyFn,
    
    /**
     * Expands the dropdown layer if it is currently hidden. Fires the {@link #expand} event on completion.
     */
    expand: function() {
        if(this.isExpanded() || !this.hasFocus){
            return;
        }
        
        this.setFormValue(this.currentValue);
        this.layer.alignTo.apply(this.layer, [this.el].concat(this.layerAlign));
        this.layer.show();
        if(Ext.isGecko2){
            this.innerLayer.setOverflow('auto'); // necessary for FF 2.0/Mac
        }
        this.mon(Ext.getDoc(), {
            scope: this,
            mousewheel: this.collapseIf,
            mousedown: this.collapseIf
        });
        this.fireEvent('expand', this);
    },
    
    /**
     * get values from form
     * 
     * @return {mixed}
     */
    getFormValue: function() {
        var formValues = this.getInnerForm().getForm().getValues();
        return formValues;
    },
    
    /**
     * template fn for subclasses to return inner form items
     */
    getItems: function() {
        return [];
    },
    
    /**
     * <p>Returns the element used to house this ComboBox's pop-up layer. Defaults to the document body.</p>
     * A custom implementation may be provided as a configuration option if the floating layer needs to be rendered
     * to a different Element. An example might be rendering the layer inside a Menu so that clicking
     * the layer does not hide the Menu
     */
    getLayerParent : function() {
        return document.body;
    },
    
    /**
     * get buttons
     * 
     * @private
     */
    getButtons: function () {
        if (! this.hideButtons) {
            this.action_ok = new Ext.Action({
                text: _('Ok'),
                scope: this,
                handler: this.onOk,
                iconCls: 'action_saveAndClose'
            });
            
            this.action_cancel = new Ext.Action({
                text: _('Cancel'),
                scope: this,
                handler: this.onCancel,
                iconCls: 'action_cancel'
            });
            
            return [
                this.action_cancel,
                this.action_ok
            ];
        }
                
        return false;
    },
    
    /**
     * returns inner form of the layer
     * 
     */
    getInnerForm: function() {
        if (! this.innerForm) {
            this.innerForm = new Ext.form.FormPanel(Ext.apply({
                //labelWidth: 30,
                height: this.layerHeight || 'auto',
                border: false,
                cls: 'tw-editdialog',
                items: this.getItems(),
                buttonAlign: 'right',
                buttons: this.getButtons()
            }, this.formConfig));
        }
        
        return this.innerForm;
    },
    
    /**
     * Returns the currently selected field value or empty string if no value is set.
     * @return {String} value The selected value
     */
    getValue: function() {
        return this.currentValue;
    },
    
    initComponent: function() {
        Ext.ux.form.LayerCombo.superclass.initComponent.apply(this, arguments);
    },

    /**
     * init the dropdown layer
     * 
     * @private
     */
    initLayer: function() {
        if(!this.layer){
            var cls = 'ux-layercombo-layer',
                layerParent = Ext.getDom(this.getLayerParent() || Ext.getBody()),
                zindex = parseInt(Ext.fly(layerParent).getStyle('z-index') ,10);

            if (this.ownerCt && !zindex){
                this.findParentBy(function(ct){
                    zindex = parseInt(ct.getPositionEl().getStyle('z-index'), 10);
                    return !!zindex;
                });
            }

            this.layer = new Ext.Layer({
                parentEl: layerParent,
                shadow: this.shadow,
                cls: [cls, this.layerClass].join(' '),
                constrain:false,
                zindex: (zindex || 8000) + 5
            });

            var lw = this.layerWidth || Math.max(this.wrap.getWidth(), this.minLayerWidth);
            this.layer.setSize(lw, 0);
            this.assetHeight = 0;
            if(this.syncFont !== false){
                this.layer.setStyle('font-size', this.el.getStyle('font-size'));
            }
            if(this.title){
                this.header = this.layer.createChild({cls:cls+'-hd', html: this.title});
                this.assetHeight += this.header.getHeight();
            }

            this.innerLayer = this.layer.createChild({cls:cls+'-inner'});
            //this.mon(this.innerLayer, 'mouseover', this.onViewOver, this);
            //this.mon(this.innerLayer, 'mousemove', this.onViewMove, this);
            this.innerLayer.setWidth(lw - this.layer.getFrameWidth('lr'));
            
            var innerForm = this.getInnerForm();
            innerForm.render(this.innerLayer);
            
            this.setLayerHeight(this.layerHeight ? this.layerHeight : 'auto');
        }
    },
    
    /**
     * Returns true if the dropdown layer is expanded, else false.
     */
    isExpanded : function(){
        return this.layer && this.layer.isVisible();
    },
    
    /**
     * cancel handler
     */
    onCancel: function() {
        this.setValue(this.currentValue);
        this.collapse();
    },
    
    /**
     * do cleanup
     * 
     * @private
     */
    onDestroy : function(){
       if (this.dqTask){
           this.dqTask.cancel();
           this.dqTask = null;
       }
       Ext.destroy(
           this.resizer,
           this.layer
       );
       Ext.destroyMembers(this, 'hiddenField');
       Ext.form.ComboBox.superclass.onDestroy.call(this);
   },
 
    /**
     * ok handler
     */
    onOk: function() {
        this.collapse();
        var v = this.getFormValue();
        this.setValue(v);
        
        this.fireEvent('select', this, v);
        this.startValue = v;
    },
    
    /**
     * executed onRender
     * 
     * @private
     * 
     * @param {Ext.Container} ct
     * @param {Number} position
     */
    onRender : function(ct, position){
        if(this.hiddenName && !Ext.isDefined(this.submitValue)){
            this.submitValue = false;
        }
        Ext.ux.form.LayerCombo.superclass.onRender.apply(this, arguments);
        if(this.hiddenName){
            this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
                    id: (this.hiddenId||this.hiddenName)}, 'before', true);

        }
        if(Ext.isGecko){
            this.el.dom.setAttribute('autocomplete', 'off');
        }

        if(!this.lazyInit){
            this.initLayer();
        }else{
            this.on('focus', this.initLayer, this, {single: true});
        }
    },
    
    /**
     * Implements the default empty TriggerField.onTriggerClick function
     * 
     * @private
     */
    onTriggerClick : function(){
        if (this.readOnly || this.disabled){
            return;
        }
        if(this.isExpanded()){
            this.collapse();
            this.el.focus();
        } else {
            this.onFocus({});
            this.expand()
        }
    },
    
    /**
     * set layer height
     * 
     * @param {Number} height
     */
    setLayerHeight: function(height) {
        if (! Ext.isNumber(height)) {
            height = this.innerForm.getHeight();
        }
        this.innerLayer.dom.style.height = '';
        var pad = this.layer.getFrameWidth('tb') + (this.resizable ? this.handleHeight : 0) + this.assetHeight;

        this.innerLayer.setHeight(height);
        this.layer.beginUpdate();
        this.layer.setHeight(height+pad);
        this.layer.alignTo.apply(this.layer, [this.el].concat(this.layerAlign));
        this.layer.endUpdate();
    },
    
    /**
     * Sets the specified value into the field.  If the value finds a match, the corresponding record text
     * will be displayed in the field.  If the value does not match the data value of an existing item,
     * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
     * Otherwise the field will be blank (although the value will still be set).
     * 
     * @param {String} value The value to match
     * @return {Ext.form.Field} this
     */
    setValue: function(value) {
        this.currentValue = value;
        return this;
    },
    
    /**
     * sets values to innerForm
     */
    setFormValue: function(value) {
        this.getInnerForm().getForm().setValues(value);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux', 'Ext.ux.form');

/**
 * A ComboBox with a secondary trigger button that clears the contents of the ComboBox
 * 
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.ClearableComboBox
 * @extends     Ext.form.ComboBox
 */
Ext.ux.form.ClearableComboBox = Ext.extend(Ext.form.ComboBox, {     
    /**
     * @cfg {bool} disableClearer
     * disables the clearer
     */
    disableClearer: null,
    
    initComponent : function(){
        Ext.ux.form.ClearableComboBox.superclass.initComponent.call(this);
        this.triggerConfig = {
            tag: 'span', cls: 'x-form-twin-triggers', style: 'padding-right:2px',  // padding needed to prevent IE from clipping 2nd trigger button
            cn: [
                {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger x-form-clear-trigger"},
                {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger"}
            ]
        };
    },

    getTrigger: function (index) {
        return this.triggers[index];
    },

    initTrigger: function () {
        var ts = this.trigger.select('.x-form-trigger', true);
        this.wrap.setStyle('overflow', 'hidden');
        var triggerField = this;
        ts.each(function (t, all, index) {
            t.hide = function () {
                this.dom.style.display = 'none';
                triggerField.el.setWidth(triggerField.wrap.getWidth() - 21);
            };
            t.show = function () {
                this.dom.style.display = '';
                triggerField.el.setWidth(triggerField.wrap.getWidth() - 38);
            };
            var triggerIndex = 'Trigger' + (index + 1);

            if (this['hide' + triggerIndex]) {
                t.dom.style.display = 'none';
            }
            
            t.on("click", this['on' + triggerIndex + 'Click'], this, {preventDefault: true});
            t.addClassOnOver('x-form-trigger-over');
            t.addClassOnClick('x-form-trigger-click');
        }, this);
        
        this.triggers = ts.elements;
        this.triggers[0].hide();
    },
    
    // clear contents of combobox
    onTrigger1Click: function () {
        if (this.disabled) {
           return;
        }
        this.clearValue();
    },
    
    // pass to original combobox trigger handler
    onTrigger2Click: function () {
        this.onTriggerClick();
    },
    
    /**
     * clear value
     */
    clearValue: function () {
        Ext.ux.form.ClearableComboBox.superclass.clearValue.apply(this, arguments);
        this.fireEvent('select', this, this.getRawValue(), this.startValue);
        this.startValue = this.getRawValue();
        if (this.triggers && this.disableClearer !== true) {
            this.triggers[0].hide();
        }
    },
    
    // show clear trigger when item got selected
    onSelect: function (combo, record, index) {
        if (this.triggers && this.disableClearer !== true) {
            this.triggers[0].show();
        }
        Ext.ux.form.ClearableComboBox.superclass.onSelect.call(this, combo, record, index);
        this.startValue = this.getValue();
    },
    
    /**
     * @see Ext.form.ComboBox
     */
    setValue: function (value) {
        Ext.ux.form.ClearableComboBox.superclass.setValue.call(this, value);
        if (value && (this.triggers && this.disableClearer !== true)) {
            this.triggers[0].show();
        }
    },
    
    /**
     * @see Ext.form.ComboBox
     */
    assertValue : function(){
        
        var val = this.getRawValue(),
            rec;

        if (this.valueField && Ext.isDefined(this.value)){
            rec = this.findRecord(this.valueField, this.value);
        }
        if (!rec || rec.get(this.displayField) != val){
            rec = this.findRecord(this.displayField, val);
        }
        
        if (!rec && this.forceSelection){
            if (val.length > 0 && val != this.emptyText){
                this.el.dom.value = Ext.value(this.lastSelectionText, '');
                this.applyEmptyText();
            } else {
                this.clearValue();
            }
        } else {
            if (rec && this.valueField){
                // onSelect may have already set the value and by doing so
                // set the display field properly.  Let's not wipe out the
                // valueField here by just sending the displayField.
                if (this.value == val){
                    return;
                }
                val = rec.get(this.valueField || this.displayField);
            }
            
            this.selectedRecord = rec;
            this.setRawValue(rec ? rec.get(this.displayField) : val);
        }
    }
});
Ext.reg('extuxclearablecombofield', Ext.ux.form.ClearableComboBox);/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Ext.ux.form');

/**
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.ClearableTextField
 * @extends     Ext.form.TriggerField
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * Textfield with clearing trigger
 */
Ext.ux.form.ClearableTextField = Ext.extend(Ext.form.TriggerField, {
    enableKeyEvents: true,
    triggerClass: 'x-form-clear-trigger',
    
    checkTrigger: function() {
        if (this.getValue()) {
            this.el.setWidth(this.wrap.getWidth() - this.trigger.getWidth());
            this.trigger.show();
        } else {
            this.trigger.hide();
            this.el.setWidth(this.wrap.getWidth());
        }
    },
    
    initComponent: function() {
        this.supr().initComponent.call(this);
        this.on('keyup', this.checkTrigger, this);
    },
    
    afterRender: function() {
        this.supr().afterRender.call(this);
        this.checkTrigger();
    },
    
    onTriggerClick: function() {
        var value = this.getValue();
        this.setValue('');
        
        if (value) {
            this.fireEvent('change', this, '', value);
        }
        
        this.checkTrigger();
        this.el.focus();
    },
    
    onDestroy: function() {
        this.un('keyup', this.checkTrigger);
    },
    
    setValue: function(value) {
        var ret = this.supr().setValue.call(this, value);
        this.checkTrigger();
        
        return ret;
    }
});
Ext.reg('extuxclearabletextfield', Ext.ux.form.ClearableTextField);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Ext.ux.form');

/**
 * A ComboBox with a (Ext.data.JsonStore) store that loads its data from a record in this format:
 * {
 *  value:1,
 *  records:[{
 *          totalcount: 2,
 *          results: {id: 0, name: 'name 1'}, {id: 1,name: 'name 2'}
 *      }]
 * }
 * 
 * use it like this:
 * {
 *   xtype:'reccombo',
 *   name: 'software_id',
 *   fieldLabel: this.app.i18n._('Software Version'),
 *   displayField: 'name',
 *   store: new Ext.data.Store({
 *      fields: Tine.Voipmanager.Model.SnomSoftware,
 *      proxy: Tine.Voipmanager.SnomSoftwareBackend,
 *      reader: Tine.Voipmanager.SnomSoftwareBackend.getReader(),
 *      remoteSort: true,
 *      sortInfo: {field: 'name', dir: 'ASC'}
 *   })
 * }
 *
 * @namespace   Ext.ux.form 
 * @class       Ext.ux.form.RecordsComboBox
 * @extends     Ext.form.ComboBox
 */
Ext.ux.form.RecordsComboBox = Ext.extend(Ext.form.ComboBox, {
    /**
     * default config
     */
    triggerAction: 'all',
    editable: false,
    forceSelection: true,
    valueField: 'id',
    
    /**
     * overwrite setValue() to get records
     * 
     * @param value
     */
    setValue: function(value) {
        var val = value;
        // check if object and load options from record
        if(typeof value === 'object' && Object.prototype.toString.call(value) === '[object Object]') {
            if(value['records'] !== undefined) {
                this.mode = 'local';
                this.store.loadData(value['records']);
            }
            val = value['value'];
        }
        Ext.ux.form.RecordsComboBox.superclass.setValue.call(this, val);
    }
});

// register xtype
Ext.reg('reccombo', Ext.ux.form.RecordsComboBox);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

/*global Ext*/

Ext.ns('Ext.ux', 'Ext.ux.form');

/**
 * A combination of datefield and timefield
 * 
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.DateTimeField
 * @extends     Ext.form.Field
 */
Ext.ux.form.DateTimeField = Ext.extend(Ext.form.Field, {
    autoEl: 'div',
    value: '',
    increment: 15,
    timeEditable: true,
    markedInvalid: false,
    
    /**
     * @cfg {Object} config to be applied to date field 
     */
    dateConf: null,
    
    /**
     * @cfg {Object} config to be applied to time field 
     */
    timeConf: null,
    
    /*
     * @cfg {String} Default Time in the form HH:MM, if null current time is used
     */
    defaultTime: null,
    
    initComponent: function () {
        Ext.ux.form.DateTimeField.superclass.initComponent.call(this);
        this.lastValues = [];
        this.on('change', this.onChange, this);
    },
    
    /**
     * Sets the default time when using the first time or after clearing the datefield
     * @param {Ext.form.Field}
     * @param {DateTime} newValue
     * @param {DateTime} oldValue
     */
    onChange: function(f, newValue, oldValue) {

        if(oldValue == '' && newValue) {
            var newDate = newValue.clone(),
                now = new Date(),
                times = (this.defaultTime) ? this.defaultTime.split(':') : [now.getHours(), now.getMinutes()];
                
            
            newDate.setHours(parseInt(times[0]));
            newDate.setMinutes(parseInt(times[1]));
                
            f.setValue(newDate);
        }
    },
    
    clearInvalid: function () {
        this.markedInvalid = false;
        
        if (this.dateField) {
            this.dateField.clearInvalid();
        }
        if (this.timeField) {
            this.timeField.clearInvalid();
        }
    },
    
    clearTime: function () {
        var dateTime = this.getValue();
        if (Ext.isDate(dateTime)) {
            this.setValue(this.getValue().clearTime(true));
        } else {
            this.timeField.setValue(new Date().clearTime());
        }
    },
    
    combineDateTime: function (date, time) {
        date = Ext.isDate(date) ? date : new Date.clearTime();
        
        if (Ext.isDate(time)) {
            date = date.clone();
            date.clearTime();
            date = date.add(Date.HOUR, time.getHours());
            date = date.add(Date.MINUTE, time.getMinutes());
        }
        
        return date;
    },
    
    getName: function () {
        return this.name;
    },
    
    getValue: function () {
        if (! this.dateField) {
            return this.value;
        }
        
        var date = this.dateField.getValue();
        var time = this.timeField.getValue();
        
        // this is odd, why doesn't Ext.form.TimeField a Date datatype?
        if (! Ext.isDate(time)) {
            time = Date.parseDate(time, 'H:i');
        }
        
        if (Ext.isDate(date)) {
            date = this.combineDateTime(date, time);
        }
        
        return date;
    },
    
    markInvalid: function (msg) {
        this.markedInvalid = true;
        
        this.dateField.markInvalid(msg);
        this.timeField.markInvalid(msg);
    },
    
    onRender: function (ct, position) {
        //Ext.ux.form.DateTimeField.superclass.onRender.call(this, ct, position);
        this.el = document.createElement(this.autoEl);
        this.el.id = this.getId();
        this.el = Ext.get(this.el);
        ct.dom.insertBefore(this.el.dom, position);
        this.el.applyStyles('overflow:visible;');
        
        var dateField = (this.allowBlank) ? Ext.ux.form.ClearableDateField : Ext.form.DateField;
        
        this.dateField = new dateField(Ext.apply({
            lazyRender: false,
            renderTo: this.el,
            readOnly: this.readOnly,
            hideTrigger: this.hideTrigger,
            disabled: this.disabled,
            tabIndex: this.tabIndex === -1 ? this.tabIndex : false,
            allowBlank: this.allowBlank,
            value: this.value,
            listeners: {
                scope: this,
                change: this.onDateChange,
                select: this.onDateChange,
                focus: this.onDateFocus,
                blur: this.onDateBlur
            }
        }, this.dateConf || {}));
        
        this.timeField = new Ext.form.TimeField(Ext.apply({
            lazyRender: false,
            increment: this.increment,
            renderTo: this.el,
            readOnly: this.readOnly,
            hideTrigger: this.hideTrigger,
            disabled: this.disabled,
            editable: this.timeEditable,
            tabIndex: this.tabIndex === -1 ? this.tabIndex : false,
            allowBlank: this.allowBlank,
            value: this.value,
            listeners: {
                scope: this,
                change: this.onTimeChange,
                select: this.onTimeChange,
                focus: this.onDateFocus,
                blur: this.onDateBlur
            }
        }, this.timeConf || {}));
        
    },
    
    onDateFocus: function () {
        this.hasFocus = true;
        this.fireEvent('focus', this);
    },
    
    onDateBlur: function () {
        this.hasFocus = false;
        this.fireEvent('blur', this);
    },
    
    onDateChange: function () {
        var newValue = this.getValue();
        this.setValue(newValue);
        this.fireEvent('change', this, newValue, this.lastValues.length > 1 ? this.lastValues[this.lastValues.length - 2] : '');
    },
    
    onResize: function (w, h) {
        Ext.ux.form.DateTimeField.superclass.onResize.apply(this, arguments);
        
        if(this.allowBlank) {
            var korrel = [0.65, 0.35];
        } else {
            var korrel = [0.55, 0.45];
        }
        
        // needed for readonly
        this.el.setHeight(20);
        
        this.el.setStyle({'position': 'relative'});
        
        this.dateField.wrap.setStyle({'position': 'absolute'});
        var dateFieldWidth = Math.abs(w * korrel[0] - 5);
        this.dateField.setWidth(dateFieldWidth);
        this.dateField.wrap.setWidth(dateFieldWidth);
        
        this.timeField.wrap.setStyle({'position': 'absolute'});
        var timeFieldWidth = Math.abs(w * korrel[1]);
        this.timeField.setWidth(timeFieldWidth);
        this.timeField.wrap.setWidth(timeFieldWidth);
        this.timeField.wrap.setLeft(dateFieldWidth + 5);
    },
    
    onTimeChange: function () {
        var newValue = this.getValue();
        this.setValue(newValue);
        this.fireEvent('change', this, newValue, this.lastValues.length > 1 ? this.lastValues[this.lastValues.length - 2] : '');
    },
    
    setDisabled: function (bool, what) {
        if (what !== 'time' && this.dateField) {
            this.dateField.setDisabled(bool);
        }
        
        if (what !== 'date' && this.timeField) {
            this.timeField.setDisabled(bool);
        }
        
        Ext.ux.form.DateTimeField.superclass.setDisabled.call(this, bool);
    },
    
    setReadOnly: function (bool, what) {
        if (what !== 'time' && this.dateField) {
            this.dateField.setReadOnly(bool);
        }
        
        if (what !== 'date' && this.timeField) {
            this.timeField.setReadOnly(bool);
        }
        
        Ext.ux.form.DateTimeField.superclass.setReadOnly.call(this, bool);
    },
    
    setRawValue: Ext.EmptyFn,
    
    setValue: function (value, skipHistory) {
        if (! skipHistory) {
            this.lastValues.push(value);
        }
        
        if (this.dateField && this.timeField) {
            this.dateField.setValue(value);
            this.timeField.setValue(value);
        }
        
        this.value = value;
    },
    
    undo: function () {
        if (this.lastValues.length > 1) {
            this.lastValues.pop();
            this.setValue(this.lastValues[this.lastValues.length - 1], true);
        } else {
            this.reset();
        }
    },
    
    isValid: function (preventMark) {
        return this.dateField.isValid(preventMark) && this.timeField.isValid(preventMark) && ! this.markedInvalid;
    }
});
Ext.reg('datetimefield', Ext.ux.form.DateTimeField);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux', 'Ext.ux.form');

/**
 * A DateField with a secondary trigger button that clears the contents of the DateField
 *
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.ClearableDateField
 * @extends     Ext.form.DateField
 */
Ext.ux.form.ClearableDateField = Ext.extend(Ext.form.DateField, {
    initComponent : function(){
        Ext.ux.form.ClearableDateField.superclass.initComponent.call(this);

        this.triggerConfig = {
            tag:'span', cls:'x-form-twin-triggers',
            cn:[
                {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger x-form-clear-trigger"},            
                {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger x-form-date-trigger"}                            
            ]
        };
    },

    getTrigger : function(index){
        return this.triggers[index];
    },

    initTrigger : function(){
        var ts = this.trigger.select('.x-form-trigger', true);
        this.wrap.setStyle('overflow', 'hidden');
        var triggerField = this;
        ts.each(function(t, all, index){
            t.hide = function(){
                var w = triggerField.wrap.getWidth();
                this.dom.style.display = 'none';
                triggerField.el.setWidth(w-triggerField.trigger.getWidth());
            };
            t.show = function(){
                var w = triggerField.wrap.getWidth();
                this.dom.style.display = '';
                triggerField.el.setWidth(w-triggerField.trigger.getWidth());
            };
            var triggerIndex = 'Trigger'+(index+1);

            if(this['hide'+triggerIndex]){
                t.dom.style.display = 'none';
            }
            t.on("click", this['on'+triggerIndex+'Click'], this, {preventDefault:true});
            t.addClassOnOver('x-form-trigger-over');
            t.addClassOnClick('x-form-trigger-click');
        }, this);
        this.triggers = ts.elements;        
        this.triggers[0].hide();                   
    },
    
    validateValue : function(value) {
        if(value !== this.emptyText && value !== undefined && value.length > '1'){
            if (Ext.isArray(this.triggers) && this.triggers[0] && typeof this.triggers[0].show == 'function') {
                this.triggers[0].show();
            }
        }      
      
        return true;
    },    
    // clear contents of combobox
    onTrigger1Click : function() {
        this.setValue('');
        this.fireEvent('select', this, '' , '');
        this.triggers[0].hide();
    },
    // pass to original combobox trigger handler
    onTrigger2Click : function() {
        this.onTriggerClick();
    },
    
    setValue: function(v) {
        if (Ext.isArray(this.triggers) && this.triggers[0] && typeof this.triggers[0].show == 'function') {
             this.triggers[0][ v ? 'show' : 'hide']();
        }
        
        return Ext.ux.form.ClearableDateField.superclass.setValue.apply(this, arguments);
    }
});
Ext.reg('extuxclearabledatefield', Ext.ux.form.ClearableDateField);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

/*global Ext, Tine*/

Ext.ns('Ext.ux.form');

/**
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.ImageField
 * 
 * <p>A field which displayes a image of the given url and optionally supplies upload
 * button with the feature to display the newly uploaded image on the fly</p>
 * <p>Example usage:</p>
 * <pre><code>
 var formField = new Ext.ux.form.ImageField({
     name: 'jpegimage',
     width: 90,
     height: 90
 });
 * </code></pre>
 */
Ext.ux.form.ImageField = Ext.extend(Ext.form.Field, {
    /**
     * @cfg {bool}
     */
    border: true,
    
    /**
     * @cfg {String}
     */
    defaultImage: 'images/empty_photo_blank.png',
    
    defaultAutoCreate: {tag: 'input', type: 'hidden'},
    
    handleMouseEvents: true,
    
    initComponent: function () {
        this.plugins = this.plugins || [];
        this.scope = this;
        this.handler = this.onFileSelect;
        
        this.browsePlugin = new Ext.ux.file.BrowsePlugin({
            dropElSelector: 'div[class^=x-panel-body]'
        });
        this.plugins.push(this.browsePlugin);

        Tine.widgets.dialog.MultipleEditDialogPlugin.prototype.registerSkipItem(this);
        Ext.ux.form.ImageField.superclass.initComponent.call(this);
        this.imageSrc = this.defaultImage;
        if (this.border === true) {
            this.width = this.width;
            this.height = this.height;
        }
    },
    
    onRender: function (ct, position) {
        Ext.ux.form.ImageField.superclass.onRender.call(this, ct, position);
        
        // the container for the browe button
        this.buttonCt = Ext.DomHelper.insertFirst(ct, '<div>&#160;</div>', true);
        this.buttonCt.applyStyles({
            border: this.border === true ? '1px solid #B5B8C8' : '0'
        });
        this.buttonCt.setSize(this.width, this.height);
        
        this.loadMask = new Ext.LoadMask(this.buttonCt, {msg: _('Loading'), msgCls: 'x-mask-loading'});
        
        // the click to edit text container
        var clickToEditText = _('Click to edit');
        this.textCt = Ext.DomHelper.insertFirst(this.buttonCt, '<div class="x-ux-from-imagefield-text">' + clickToEditText + '</div>', true);
        this.textCt.setSize(this.width, this.height);
        var tm = Ext.util.TextMetrics.createInstance(this.textCt);
        tm.setFixedWidth(this.width);
        this.textCt.setStyle({top: ((this.height - tm.getHeight(clickToEditText)) / 2) + 'px'});
        
        // the image container
        // NOTE: this will atm. always be the default image for the first few miliseconds
        this.imageCt = Ext.DomHelper.insertFirst(this.buttonCt, '<img src="' + this.imageSrc + '"/>' , true);
        this.imageCt.setOpacity(0.2);
        this.imageCt.setStyle({
            position: 'absolute',
            top: '18px'
        });
        
        Ext.apply(this.browsePlugin, {
            buttonCt: this.buttonCt,
            renderTo: this.buttonCt
        });
    },
    
    getValue: function () {
        return Ext.ux.form.ImageField.superclass.getValue.call(this);
    },
    
    setValue: function (value) {
        Ext.ux.form.ImageField.superclass.setValue.call(this, value);
        if (! value || value === this.defaultImage) {
            this.setDefaultImage(this.defaultImage);
        } else {
            if (value instanceof Ext.ux.util.ImageURL || (Ext.isString(value) && value.match(/&/))) {
                this.imageSrc = Ext.ux.util.ImageURL.prototype.parseURL(value);
                this.imageSrc.width = this.width;
                this.imageSrc.height = this.height;
                this.imageSrc.ratiomode = 0;
            }
            else {
                this.setDefaultImage(! Ext.isEmpty(value) ? value : this.defaultImage);
            }
        }
        this.updateImage();
    },
    
    /**
     * @private
     */
    onFileSelect: function (fileSelector) {
        if (! fileSelector.isImage()) {
            Ext.MessageBox.alert(_('Not An Image'), _('Please select an image file (gif/png/jpeg)')).setIcon(Ext.MessageBox.ERROR);
            return;
        }
        
        var files = fileSelector.getFileList();
        var uploader = new Ext.ux.file.Upload({
            file: files[0],
            fileSelector: fileSelector
        });
        
        uploader.on('uploadcomplete', function (uploader, record) {
            console.log(arguments);
            this.imageSrc = new Ext.ux.util.ImageURL({
                id: record.get('tempFile').id,
                width: this.width,
                height: this.height,
                ratiomode: 0
            });
            this.setValue(this.imageSrc);
            
            this.updateImage();
        }, this);
        
        uploader.on('uploadfailure', this.onUploadFail, this);
        
        this.loadMask.show();
        
        var uploadKey = Tine.Tinebase.uploadManager.queueUpload(uploader);
        var fileRecord = Tine.Tinebase.uploadManager.upload(uploadKey);
        
        if (this.ctxMenu) {
            this.ctxMenu.hide();
        }
    },
    /**
     * @private
     */
    onUploadFail: function () {
        Ext.MessageBox.alert(_('Upload Failed'), _('Could not upload image. Please notify your Administrator')).setIcon(Ext.MessageBox.ERROR);
    },
    /**
     * executed on image contextmenu
     * @private
     */
    onContextMenu: function (e, input) {
        e.preventDefault();
        
        var ct = Ext.DomHelper.append(this.buttonCt, '<div>&#160;</div>', true);
        
        var upload = new Ext.menu.Item({
            text: _('Change Image'),
            iconCls: 'action_uploadImage',
            handler: this.onFileSelect,
            scope: this,
            plugins: [new Ext.ux.file.BrowsePlugin({})]
        });
        
        this.ctxMenu = new Ext.menu.Menu({
            items: [upload, {
                text: _('Crop Image'),
                iconCls: 'action_cropImage',
                scope: this,
                disabled: true, //this.imageSrc == this.defaultImage,
                handler: function () {
                    var cropper = new Ext.ux.form.ImageCropper({
                        imageURL: this.imageSrc
                    });
                    
                    var dlg = new Tine.widgets.dialog.EditRecord({
                        handlerScope: this,
                        handlerCancle: this.close,
                        items: cropper
                    });
                    
                    var win = Tine.WindowFactory.getWindow({
                        width: 320,
                        height: 320,
                        title: _('Crop Image'),
                        layout: 'fit',
                        items: dlg
                    });
                }
            }, {
                text: _('Delete Image'),
                iconCls: 'action_delete',
                disabled: this.imageSrc === this.defaultImage,
                scope: this,
                handler: function () {
                    this.setValue('');
                }
                
            }, {
                text: _('Show Original Image'),
                iconCls: 'action_originalImage',
                disabled: this.imageSrc === this.defaultImage,
                scope: this,
                handler: this.downloadImage
                
            }]
        });
        this.ctxMenu.showAt(e.getXY());
    },
    
    downloadImage: function () {
        var url = Ext.apply(this.imageSrc, {
            height: -1,
            width: -1
        }).toString();
        
        var window = Tine.WindowFactory.getWindow({
            url: url,
            name: 'showImage',
            width: 800,
            height: 600
        });
    },
    
    /**
     * Set (new) default (empty) image
     * @param {String} image
     */
    setDefaultImage: function (image) {
        this.defaultImage = image;
        this.imageSrc = this.defaultImage;
        
        var img = Ext.DomHelper.insertAfter(this.imageCt, '<img src="' + this.defaultImage + '"/>' , true);
        
        this.imageCt.remove();
        this.imageCt = img;
        this.imageCt.setOpacity(0.2);
        this.imageCt.setStyle({
            position: 'absolute',
            top: '18px'
        });
        this.textCt.setVisible(true);
        
        // after setting new empty photo set field value to empty string
        Ext.ux.form.ImageField.superclass.setValue.call(this, '');
    },
    
    updateImage: function () {
        // only update when new image differs from current
        if (this.imageCt.dom.src.substr(-1 * this.imageSrc.length) !== this.imageSrc) {
            var ct = this.imageCt.up('div');
            var img = Ext.DomHelper.insertAfter(this.imageCt, '<img src="' + this.imageSrc + '"/>' , true);
            // replace image after load
            img.on('load', function () {
                this.imageCt.remove();
                this.imageCt = img;
                this.textCt.setVisible(this.imageSrc === this.defaultImage);
                this.imageCt.setOpacity(this.imageSrc === this.defaultImage ? 0.2 : 1);
                this.loadMask.hide();
            }, this);
            img.on('error', function () {
                Ext.MessageBox.alert(_('Image Failed'), _('Could not load image. Please notify your Administrator')).setIcon(Ext.MessageBox.ERROR);
                this.loadMask.hide();
            }, this);
        }
    }
});

Ext.ns('Ext.ux.util');

/**
 * this class represents an image URL
 */
Ext.ux.util.ImageURL = function (config) {
    Ext.apply(this, config, {
        url: 'index.php',
        method: 'Tinebase.getImage',
        application: 'Tinebase',
        location: 'tempFile',
        width: 90,
        height: 120,
        ratiomode: 0,
        mtime: new Date().getTime()
    });
};
/**
 * generates an imageurl according to the class members
 * 
 * @return {String}
 */
Ext.ux.util.ImageURL.prototype.toString = function () {
    return this.url + 
        "?method=" + this.method + 
        "&application=" + this.application + 
        "&location=" + this.location + 
        "&id=" + this.id + 
        "&width=" + this.width + 
        "&height=" + this.height + 
        "&ratiomode=" + this.ratiomode + 
        "&mtime=" + this.mtime;
};
/**
 * parses an imageurl
 * 
 * @param  {String} url
 * @return {Ext.ux.util.ImageURL}
 */
Ext.ux.util.ImageURL.prototype.parseURL = function (url) {
    var urlString = url.toString(),
        params = {},
        lparams = urlString.substr(urlString.indexOf('?') + 1).split('&');
        
    for (var i = 0, j = lparams.length; i < j; i += 1) {
        var param = lparams[i].split('=');
        params[param[0]] = Ext.util.Format.htmlEncode(param[1]);
    }
    return new Ext.ux.util.ImageURL(params);
};

/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux.form');

/**
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.ImageCropper
 * @extends     Ext.Component
 */
Ext.ux.form.ImageCropper = function(config) {
    Ext.apply(this, config);

    Ext.ux.form.ImageCropper.superclass.constructor.apply(this, arguments);
    
    this.addEvents(
        /**
         * @event uploadcomplete
         * Fires when the upload was done successfully 
         * @param {String} croped image
         */
         'imagecropped'
    );
};

Ext.extend(Ext.ux.form.ImageCropper, Ext.Component, {
    /**
     * @property {Object} natrual size of image
     */
    imageSize: false,
    
    width: 320,
    height: 320,
    
    initComponent: function() {
        this.imageURL.width = 290;
        this.imageURL.height = 240;
        this.imageURL.ratiomode = 1;

        Ext.ux.form.ImageCropper.superclass.initComponent.call(this);
    },
    onRender: function(ct, position) {
        Ext.ux.form.ImageCropper.superclass.onRender.call(this, ct, position);
        this.wrapEl = Ext.DomHelper.insertFirst(ct, {tag: 'div'}, true);
        //this.maskEl = Ext.DomHelper.insertFirst(this.wrapEl, {tag: 'div', style: 'background-color: #000000'}, true);
        
        // bg image
        this.bgImageEl = Ext.DomHelper.insertFirst(this.wrapEl, {tag: 'img', id: 'yui_img', src: this.imageURL}, true);
        this.bgImageEl.setOpacity(0.5);
        
        // yui cropper is very fast. 
        // cause of the window usage the yui crop mask does not work, so we use the mask obove
        /*
        var Dom = YAHOO.util.Dom, 
        Event = YAHOO.util.Event;
    
        var crop = new YAHOO.widget.ImageCropper('yui_img');
        */
        
         // Ext only implementation is very slow!
         // the bad news is that the resizeable dosn't fire events while resizeing 
         // and that the dd onDrag looses scope
         this.fgImageEl = Ext.DomHelper.insertFirst(this.wrapEl, {tag: 'div', style: {
            width              : '100px',
            height             : '100px',
            position           : 'absolute',
            top                : '30px',
            left               : '30px',
            'background-image' : 'url(' + this.imageURL + ')',
            'background-repeat': 'no-repeat'
        }}, true);
        
        this.resizeable = new Ext.Resizable(this.fgImageEl, {
            wrap:true,
            pinned:true,
            handles: 's e se',
            draggable:true,
            dynamic:true
        });
        this.resizeable.dd.fgImageEl = this.fgImageEl;
        this.resizeable.dd.bgImageEl = this.bgImageEl;
        
        this.resizeable.dd.onDrag = this.syncImageEls;
        
        // fix opacity, which might be broken by the inherited window properties
        var rhs = this.resizeable.getEl().query('div.x-resizable-handle');
        for (var i=0, j=rhs.length; i<j; i++) {
            Ext.get(rhs[i]).setOpacity(1);
        }
        this.syncImageEls();
    },
    /**
     * sync fg image with bg image
     */
    syncImageEls: function() {
        var dx = this.bgImageEl.getX() - this.fgImageEl.getX();
        var dy = this.bgImageEl.getY() - this.fgImageEl.getY();
        this.fgImageEl.setStyle('background-position', dx + 'px ' + dy + 'px');
    }
});
/**
 * Copyright (c) 2008, Steven Chim
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 *     * The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
  * Ext.ux.form.Spinner Class
  *
  * @author  Steven Chim
  * @version Spinner.js 2008-08-27 v0.35
  *
  * @class Ext.ux.form.Spinner
  * @extends Ext.form.TriggerField
  */

Ext.ns("Ext.ux.form");

/**
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.Spinner
 * @extends     Ext.form.TriggerField
 */
Ext.ux.form.Spinner = function(config){
    Ext.ux.form.Spinner.superclass.constructor.call(this, config);
    this.addEvents({
        'spin' : true,
        'spinup' : true,
        'spindown' : true
    });
};

Ext.extend(Ext.ux.form.Spinner, Ext.form.TriggerField, {
    triggerClass : 'x-form-spinner-trigger',
    splitterClass : 'x-form-spinner-splitter',

    alternateKey : Ext.EventObject.shiftKey,
    strategy : undefined,

    //private
    onRender : function(ct, position){
        Ext.ux.form.Spinner.superclass.onRender.call(this, ct, position);

        this.splitter = this.wrap.createChild({tag:'div', cls:this.splitterClass, style:'width:13px; height:2px;'});
        this.splitter.show().setRight( (Ext.isIE) ? 1 : 2 );
        this.splitter.show().setTop(8);

        this.proxy = this.trigger.createProxy('', this.splitter, true);
        this.proxy.addClass("x-form-spinner-proxy");
        this.proxy.setStyle('left','0px');  
        this.proxy.setSize(14, 1);
        this.proxy.hide();
        this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {dragElId: this.proxy.id});

        this.initSpinner();
    },

    //private
    initTrigger : function(){
        this.trigger.addClassOnOver('x-form-trigger-over');
        this.trigger.addClassOnClick('x-form-trigger-click');
    },

    //private
    initSpinner : function(){
        this.keyNav = new Ext.KeyNav(this.el, {
            "up" : function(e){
                e.preventDefault();
                this.onSpinUp();
            },

            "down" : function(e){
                e.preventDefault();
                this.onSpinDown();
            },

            "pageUp" : function(e){
                e.preventDefault();
                this.onSpinUpAlternate();
            },

            "pageDown" : function(e){
                e.preventDefault();
                this.onSpinDownAlternate();
            },

            scope : this
        });

        this.repeater = new Ext.util.ClickRepeater(this.trigger);
        this.repeater.on("click", this.onTriggerClick, this, {preventDefault:true});
        this.trigger.on("mouseover", this.onMouseOver, this, {preventDefault:true});
        this.trigger.on("mouseout",  this.onMouseOut,  this, {preventDefault:true});
        this.trigger.on("mousemove", this.onMouseMove, this, {preventDefault:true});
        this.trigger.on("mousedown", this.onMouseDown, this, {preventDefault:true});
        this.trigger.on("mouseup",   this.onMouseUp,   this, {preventDefault:true});
        this.wrap.on("mousewheel",   this.handleMouseWheel, this);

        this.dd.setXConstraint(0, 0, 10);
        this.dd.setYConstraint(1500, 1500, 10);
        this.dd.endDrag = this.endDrag.createDelegate(this);
        this.dd.startDrag = this.startDrag.createDelegate(this);
        this.dd.onDrag = this.onDrag.createDelegate(this);

        /*
        jsakalos suggestion
        http://extjs.com/forum/showthread.php?p=121850#post121850 */
        if('object' == typeof this.strategy && this.strategy.xtype) {
            switch(this.strategy.xtype) {
                case 'number':
                    this.strategy = new Ext.ux.form.Spinner.NumberStrategy(this.strategy);
                    break;

                case 'date':
                    this.strategy = new Ext.ux.form.Spinner.DateStrategy(this.strategy);
                    break;

                case 'time':
                    this.strategy = new Ext.ux.form.Spinner.TimeStrategy(this.strategy);
                    break;

                default:
                    delete(this.strategy);
                    break;
            }
            delete(this.strategy.xtype);
        }

        if(this.strategy == undefined){
            this.strategy = new Ext.ux.form.Spinner.NumberStrategy();
        }
    },

    //private
    onMouseOver : function(){
        if(this.disabled){
            return;
        }
        var middle = this.getMiddle();
        this.__tmphcls = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';
        this.trigger.addClass(this.__tmphcls);
    },

    //private
    onMouseOut : function(){
        this.trigger.removeClass(this.__tmphcls);
    },

    //private
    onMouseMove : function(){
        if(this.disabled){
            return;
        }
        var middle = this.getMiddle();
        if( ((Ext.EventObject.getPageY() > middle) && this.__tmphcls == "x-form-spinner-overup") ||
            ((Ext.EventObject.getPageY() < middle) && this.__tmphcls == "x-form-spinner-overdown")){
        }
    },

    //private
    onMouseDown : function(){
        if(this.disabled){
            return;
        }
        var middle = this.getMiddle();
        this.__tmpccls = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';
        this.trigger.addClass(this.__tmpccls);
    },

    //private
    onMouseUp : function(){
        this.trigger.removeClass(this.__tmpccls);
    },

    //private
    onTriggerClick : function(){
        if(this.disabled || this.getEl().dom.readOnly){
            return;
        }
        var middle = this.getMiddle();
        var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';
        this['onSpin'+ud]();
    },

    //private
    getMiddle : function(){
        var t = this.trigger.getTop();
        var h = this.trigger.getHeight();
        var middle = t + (h/2);
        return middle;
    },
    
    //private
    //checks if control is allowed to spin
    isSpinnable : function(){
        if(this.disabled || this.getEl().dom.readOnly){
            Ext.EventObject.preventDefault();    //prevent scrolling when disabled/readonly
            return false;
        }
        return true;
    },

    handleMouseWheel : function(e){
        //disable scrolling when not focused
        if(this.wrap.hasClass('x-trigger-wrap-focus') == false){
            return;
        }

        var delta = e.getWheelDelta();
        if(delta > 0){
            this.onSpinUp();
            e.stopEvent();
        } else if(delta < 0){
            this.onSpinDown();
            e.stopEvent();
        }
    },

    //private
    startDrag : function(){
        this.proxy.show();
        this._previousY = Ext.fly(this.dd.getDragEl()).getTop();
    },

    //private
    endDrag : function(){
        this.proxy.hide();
    },

    //private
    onDrag : function(){
        if(this.disabled){
            return;
        }
        var y = Ext.fly(this.dd.getDragEl()).getTop();
        var ud = '';

        if(this._previousY > y){ud = 'Up';}         //up
        if(this._previousY < y){ud = 'Down';}       //down

        if(ud != ''){
            this['onSpin'+ud]();
        }

        this._previousY = y;
    },

    //private
    onSpinUp : function(){
        if(this.isSpinnable() == false) {
            return;
        }
        if(Ext.EventObject.shiftKey == true){
            this.onSpinUpAlternate();
            return;
        }else{
            this.strategy.onSpinUp(this);
        }
        this.fireEvent("spin", this);
        this.fireEvent("spinup", this);
    },

    //private
    onSpinDown : function(){
        if(this.isSpinnable() == false) {
            return;
        }
        if(Ext.EventObject.shiftKey == true){
            this.onSpinDownAlternate();
            return;
        }else{
            this.strategy.onSpinDown(this);
        }
        this.fireEvent("spin", this);
        this.fireEvent("spindown", this);
    },

    //private
    onSpinUpAlternate : function(){
        if(this.isSpinnable() == false) {
            return;
        }
        this.strategy.onSpinUpAlternate(this);
        this.fireEvent("spin", this);
        this.fireEvent("spinup", this);
    },

    //private
    onSpinDownAlternate : function(){
        if(this.isSpinnable() == false) {
            return;
        }
        this.strategy.onSpinDownAlternate(this);
        this.fireEvent("spin", this);
        this.fireEvent("spindown", this);
    }

});

Ext.reg('uxspinner', Ext.ux.form.Spinner);
/**
 * Copyright (c) 2008, Steven Chim
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 *     * The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * Abstract Strategy
 *
 * @namespace   Ext.ux.form.Spinner
 * @class       Ext.ux.form.Spinner.Strategy
 * @extends     Ext.util.Observable
 */
Ext.ux.form.Spinner.Strategy = function(config){
    Ext.apply(this, config);
};

Ext.extend(Ext.ux.form.Spinner.Strategy, Ext.util.Observable, {
    defaultValue : 0,
    minValue : undefined,
    maxValue : undefined,
    incrementValue : 1,
    alternateIncrementValue : 5,
    validationTask : new Ext.util.DelayedTask(),
    
    onSpinUp : function(field){
        this.spin(field, false, false);
    },

    onSpinDown : function(field){
        this.spin(field, true, false);
    },

    onSpinUpAlternate : function(field){
        this.spin(field, false, true);
    },

    onSpinDownAlternate : function(field){
        this.spin(field, true, true);
    },

    spin : function(field, down, alternate){
        this.validationTask.delay(500, function(){field.validate();});
        //extend
    },

    fixBoundries : function(value){
        return value;
        //overwrite
    }
    
});

/**
 * Concrete Strategy: Numbers
 * 
 * @namespace   Ext.ux.form.Spinner
 * @class       Ext.ux.form.Spinner.NumberStrategy
 * @extends     Ext.ux.form.Spinner.Strategy
 */
Ext.ux.form.Spinner.NumberStrategy = function(config){
    Ext.ux.form.Spinner.NumberStrategy.superclass.constructor.call(this, config);
};

Ext.extend(Ext.ux.form.Spinner.NumberStrategy, Ext.ux.form.Spinner.Strategy, {

    allowDecimals : true,
    decimalPrecision : 2,
    
    spin : function(field, down, alternate){
        Ext.ux.form.Spinner.NumberStrategy.superclass.spin.call(this, field, down, alternate);

        var v = parseFloat(field.getValue());
        var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;

        (down == true) ? v -= incr : v += incr ;
        v = (isNaN(v)) ? this.defaultValue : v;
        v = this.fixBoundries(v);
        field.setRawValue(v);
    },

    fixBoundries : function(value){
        var v = value;

        if(this.minValue != undefined && v < this.minValue){
            v = this.minValue;
        }
        if(this.maxValue != undefined && v > this.maxValue){
            v = this.maxValue;
        }

        return this.fixPrecision(v);
    },
    
    // private
    fixPrecision : function(value){
        var nan = isNaN(value);
        if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
            return nan ? '' : value;
        }
        return parseFloat(parseFloat(value).toFixed(this.decimalPrecision));
    }
});


/**
 * Concrete Strategy: Date
 *
 * @namespace   Ext.ux.form.Spinner
 * @class       Ext.ux.form.Spinner.DateStrategy
 * @extends     Ext.ux.form.Spinner.Strategy
 */
Ext.ux.form.Spinner.DateStrategy = function(config){
    Ext.ux.form.Spinner.DateStrategy.superclass.constructor.call(this, config);
};

Ext.extend(Ext.ux.form.Spinner.DateStrategy, Ext.ux.form.Spinner.Strategy, {
    defaultValue : new Date(),
    format : "Y-m-d",
    incrementValue : 1,
    incrementConstant : Date.DAY,
    alternateIncrementValue : 1,
    alternateIncrementConstant : Date.MONTH,

    spin : function(field, down, alternate){
        Ext.ux.form.Spinner.DateStrategy.superclass.spin.call(this, field, down, alternate);

        var v = field.getRawValue();
        
        v = Date.parseDate(v, this.format);
        var dir = (down == true) ? -1 : 1 ;
        var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;
        var dtconst = (alternate == true) ? this.alternateIncrementConstant : this.incrementConstant;

        if(typeof this.defaultValue == 'string'){
            this.defaultValue = Date.parseDate(this.defaultValue, this.format);
        }

        v = (v) ? v.add(dtconst, dir*incr) : this.defaultValue;

        v = this.fixBoundries(v);
        field.setRawValue(Ext.util.Format.date(v,this.format));
    },
    
    //private
    fixBoundries : function(date){
        var dt = date;
        var min = (typeof this.minValue == 'string') ? Date.parseDate(this.minValue, this.format) : this.minValue ;
        var max = (typeof this.maxValue == 'string') ? Date.parseDate(this.maxValue, this.format) : this.maxValue ;

        if(this.minValue != undefined && dt < min){
            dt = min;
        }
        if(this.maxValue != undefined && dt > max){
            dt = max;
        }

        return dt;
    }

});


/**
 * Concrete Strategy: Time
 *
 * @namespace   Ext.ux.form.Spinner
 * @class       Ext.ux.form.Spinner.TimeStrategy
 * @extends     Ext.ux.form.Spinner.DateStrategy
 */
Ext.ux.form.Spinner.TimeStrategy = function(config){
    Ext.ux.form.Spinner.TimeStrategy.superclass.constructor.call(this, config);
};

Ext.extend(Ext.ux.form.Spinner.TimeStrategy, Ext.ux.form.Spinner.DateStrategy, {
    format : "H:i",
    incrementValue : 1,
    incrementConstant : Date.MINUTE,
    alternateIncrementValue : 1,
    alternateIncrementConstant : Date.HOUR
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Thomas Wadewitz <t.wadewitz@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * @todo        switch lock and trigger icons (because only the trigger icon has a round upper right corner)
 */
 
Ext.ns('Ext.ux', 'Ext.ux.form');

/**
 * Generic widget for a twin trigger combo field
 *
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.LockCombo
 * @extends     Ext.form.ComboBox
 */
Ext.ux.form.LockCombo = Ext.extend(Ext.form.ComboBox, {
    /**
     * @cfg {String} paramName
     */
    paramName : 'query',
    /**
     * @cfg {Bool} selectOnFocus
     */
    selectOnFocus : true,
    /**
     * @cfg {String} emptyText
     */
    emptyText: 'select entry...',
    
    hiddenFieldId: '',
    
    hiddenFieldData: '',
    
    validationEvent:false,
    validateOnBlur:false,
    trigger1Class:'x-form-trigger',
    trigger2ClassLocked:'x-form-locked-trigger',
    trigger2ClassUnlocked:'x-form-unlocked-trigger',
    hideTrigger1:false,
    width:180,
    hasSearch : false,
    /**
     * @private
     */
    initComponent : function(){
        Ext.ux.form.LockCombo.superclass.initComponent.call(this);

        if(!this.hiddenFieldData) {
            this.hiddenFieldData = '1';
        }

        this.triggerConfig = {
            tag:'span', cls:'x-form-twin-triggers', cn:[
            {tag: "img", id:'trigger1', src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class},
            {tag: "img", id:'trigger2', src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " }
        ]};
    },

    getTrigger : function(index){
        return this.triggers[index];
    },

    initTrigger : function(){

        var ts = this.trigger.select('.x-form-trigger', true);
        this.wrap.setStyle('overflow', 'hidden');
        var triggerField = this;
        ts.each(function(t, all, index){
            t.hide = function(){
                var w = triggerField.wrap.getWidth();
                this.dom.style.display = 'none';
                triggerField.el.setWidth(w-triggerField.trigger.getWidth());
            };
            t.show = function(){
                var w = triggerField.wrap.getWidth();
                this.dom.style.display = '';
                triggerField.el.setWidth(w-triggerField.trigger.getWidth());
            };
            var triggerIndex = 'Trigger'+(index+1);

            if(this['hide'+triggerIndex]){
                t.dom.style.display = 'none';
            }
            t.on("click", this['on'+triggerIndex+'Click'], this, {preventDefault:true});
    
            if(t.id == 'trigger2') {
                if(this.hiddenFieldData == '0') {
                    var _cssClass = this.trigger2ClassLocked.toString();
                    t.addClass(_cssClass);
                }
                if(this.hiddenFieldData == '1' || !this.hiddenFieldData) {
                    var _cssClass = this.trigger2ClassUnlocked.toString();
                    t.addClass(_cssClass);
                }                
            }
    
            t.addClassOnOver('x-form-trigger-over');
            t.addClassOnClick('x-form-trigger-click');
        }, this);
        this.triggers = ts.elements;
    },
    

    onRender:function(ct, position) {
        Ext.ux.form.LockCombo.superclass.onRender.call(this, ct, position); // render the Ext.Button
        this.hiddenBox = ct.parent().createChild({tag:'input', type:'hidden', name: this.hiddenFieldId, id: this.hiddenFieldId, value: this.hiddenFieldData });
        Ext.ComponentMgr.register(this.hiddenBox);
    },


    onTrigger1Click: function(){
        if(this.disabled){
            return;
        }
        if(this.isExpanded()){
            this.collapse();
            this.el.focus();
        }else {
            this.onFocus({});
            if(this.triggerAction == 'all') {
                this.doQuery(this.allQuery, true);
            } else {
                this.doQuery(this.getRawValue());
            }
            this.el.focus();
        }
    },
    
    onTrigger2Click : function(){

        var _currentValue = Ext.getCmp(this.hiddenFieldId).getValue();
        
        var ts = this.trigger.select('.x-form-trigger', true);


        if (_currentValue == '0') {
            Ext.getCmp(this.hiddenFieldId).dom.value = '1';
     
            var _cssClass = this.trigger2ClassUnlocked.toString();
            ts.each(function(t, all, index){
                if (t.id == 'trigger2') {
                    t.dom.className = "x-form-trigger " + _cssClass;
                }
            });
        }
        else  {
            Ext.getCmp(this.hiddenFieldId).dom.value = '0';

            var _cssClass = this.trigger2ClassLocked.toString();
            ts.each(function(t, all, index){
                if (t.id == 'trigger2') {
                    t.dom.className = "x-form-trigger " + _cssClass;
                }
            });
        }
    }    
});
Ext.reg('lockCombo', Ext.ux.form.LockCombo);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux', 'Ext.ux.form');

/**
 * Generic widget for a twin trigger textfield
 *
 * @namespace   Ext.ux.form
 * @class       Ext.ux.form.LockTextfield
 * @extends     Ext.form.TriggerField
 */
Ext.ux.form.LockTextfield = Ext.extend(Ext.form.TriggerField, {
    hiddenFieldId: '',
    hiddenFieldData: '',
    
    triggerClassLocked: 'x-form-locked-trigger',
    triggerClassUnlocked: 'x-form-unlocked-trigger',
    
    /**
     * @private
     */
    initComponent: function() {
        this.triggerClass = (this.hiddenFieldData == '1') 
            ? this.triggerClassUnlocked 
            : this.triggerClassLocked;

        Ext.ux.form.LockTextfield.superclass.initComponent.call(this);
    },
    
    onRender:function(ct, position) {
        Ext.ux.form.LockTextfield.superclass.onRender.call(this, ct, position); // render the textfield
        this.hiddenBox = ct.parent().createChild({tag:'input', type:'hidden', name: this.hiddenFieldId, id: this.hiddenFieldId, value: this.hiddenFieldData });
        Ext.ComponentMgr.register(this.hiddenBox);
    },
    
    onTriggerClick: function() {
        //this.hiddenFieldData = (this.hiddenFieldData == '1') ? '0' : '1';
        //this.triggerClass = (this.hiddenFieldData == '1') ? 'x-form-unlocked-trigger' : 'x-form-locked-trigger';

        var _currentValue = Ext.getCmp(this.hiddenFieldId).getValue();

        if (_currentValue == '0') {
            Ext.getCmp(this.hiddenFieldId).dom.value = '1';
            this.trigger.removeClass(this.triggerClassLocked);
            this.trigger.addClass(this.triggerClassUnlocked);
        } else {
            Ext.getCmp(this.hiddenFieldId).dom.value = '0';
            this.trigger.removeClass(this.triggerClassUnlocked);
            this.trigger.addClass(this.triggerClassLocked);
        }
    }
});
Ext.reg('lockTextfield', Ext.ux.form.LockTextfield);
/*!
 * MIT license
 */
/**
 * @author Shea Frederick - http://www.vinylfox.com
 * @class Ext.ux.form.HtmlEditor.MidasCommand
 * @extends Ext.util.Observable
 * <p>A base plugin for extending to create standard Midas command buttons.
 * googlecode project page: http://code.google.com/p/ext-ux-htmleditor-plugins/
 * </p>
 */
Ext.ns('Ext.ux.form.HtmlEditor');

/**
 * @namespace   Ext.ux.form.HtmlEditor
 * @class       Ext.ux.form.HtmlEditor.MidasCommand
 * @extends     Ext.util.Observable
 */
Ext.ux.form.HtmlEditor.MidasCommand = Ext.extend(Ext.util.Observable, {
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.btns = [];
        this.cmp.on('render', this.onRender, this);
        this.cmp.on('initialize', this.onInit, this, {
            delay: 100,
            single: true
        });
    },
    // private
    onInit: function(){
        Ext.EventManager.on(this.cmp.getDoc(), {
            'mousedown': this.onEditorEvent,
            'dblclick': this.onEditorEvent,
            'click': this.onEditorEvent,
            'keyup': this.onEditorEvent,
            buffer: 100,
            scope: this
        });
    },
    // private
    onRender: function(){
        var midasCmdButton, tb = this.cmp.getToolbar(), btn;
        Ext.each(this.midasBtns, function(b){
            if (Ext.isObject(b)) {
                midasCmdButton = {
                    iconCls: 'x-edit-' + b.cmd,
                    handler: function(){
                        this.cmp.relayCmd(b.cmd);
                    },
                    scope: this,
                    tooltip: b.tooltip ||
                    {
                        title: b.title
                    },
                    overflowText: b.overflowText || b.title
                };
            } else {
                midasCmdButton = new Ext.Toolbar.Separator();
            }
            btn = tb.addButton(midasCmdButton);
            if (b.enableOnSelection) {
                btn.disable();
            }
            this.btns.push(btn);
        }, this);
    },
    // private
    onEditorEvent: function(){
        var doc = this.cmp.getDoc();
        Ext.each(this.btns, function(b, i){
            if (this.midasBtns[i].enableOnSelection || this.midasBtns[i].disableOnSelection) {
                if (doc.getSelection) {
                    if ((this.midasBtns[i].enableOnSelection && doc.getSelection() !== '') || (this.midasBtns[i].disableOnSelection && doc.getSelection() === '')) {
                        b.enable();
                    } else {
                        b.disable();
                    }
                } else if (doc.selection) {
                    if ((this.midasBtns[i].enableOnSelection && doc.selection.createRange().text !== '') || (this.midasBtns[i].disableOnSelection && doc.selection.createRange().text === '')) {
                        b.enable();
                    } else {
                        b.disable();
                    }
                }
            }
            if (this.midasBtns[i].monitorCmdState) {
                b.toggle(doc.queryCommandState(this.midasBtns[i].cmd));
            }
        }, this);
    }
});
/**
 * @namespace   Ext.ux.form.HtmlEditor
 * @class       Ext.ux.form.HtmlEditor.Divider
 * @extends     Ext.util.Observable
 * @author      Shea Frederick - http://www.vinylfox.com
 * 
 * <p>A plugin that creates a divider on the HtmlEditor. Used for separating additional buttons.</p>
 */
Ext.ux.form.HtmlEditor.Divider = Ext.extend(Ext.util.Observable, {
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        this.cmp.getToolbar().addButton([new Ext.Toolbar.Separator()]);
    }
});
/**
 * @namespace   Ext.ux.form.HtmlEditor
 * @class       Ext.ux.form.HtmlEditor.IndentOutdent
 * @extends     Ext.ux.form.HtmlEditor.MidasCommand
 * @author      Shea Frederick - http://www.vinylfox.com
 * 
 * <p>A plugin that creates two buttons on the HtmlEditor for indenting and outdenting of selected text.</p>
 */
Ext.ux.form.HtmlEditor.IndentOutdent = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
    // private
    midasBtns: ['|', {
        cmd: 'outdent',
        tooltip: {
            title: 'Outdent Text'
        },
        overflowText: 'Outdent Text'
    }, {
        cmd: 'indent',
        tooltip: {
            title: 'Indent Text'
        },
        overflowText: 'Indent Text'
    }]
});
/**
 * @namespace   Ext.ux.form.HtmlEditor
 * @class       Ext.ux.form.HtmlEditor.RemoveFormat
 * @extends     Ext.ux.form.HtmlEditor.MidasCommand
 * @author      Shea Frederick - http://www.vinylfox.com
 * 
 * <p>A plugin that creates a button on the HtmlEditor that will remove all formatting on selected text.</p>
 */
Ext.ux.form.HtmlEditor.RemoveFormat = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
    midasBtns: ['|', {
        enableOnSelection: true,
        cmd: 'removeFormat',
        tooltip: {
            title: 'Remove Formatting'
        },
        overflowText: 'Remove Formatting'
    }]
});
/**
 * @namespace   Ext.ux.form.HtmlEditor
 * @class       Ext.ux.form.HtmlEditor.SubSuperScript
 * @extends     Ext.ux.form.HtmlEditor.MidasCommand
 * @author      Shea Frederick - http://www.vinylfox.com
 * 
 * <p>A plugin that creates two buttons on the HtmlEditor for superscript and subscripting of selected text.</p>
 */
Ext.ux.form.HtmlEditor.SubSuperScript = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
    // private
    midasBtns: ['|', {
        enableOnSelection: true,
        cmd: 'subscript',
        tooltip: {
            title: 'Subscript'
        },
        overflowText: 'Subscript'
    }, {
        enableOnSelection: true,
        cmd: 'superscript',
        tooltip: {
            title: 'Superscript'
        },
        overflowText: 'Superscript'
    }]
});
/**
 * @namespace   Ext.ux.form.HtmlEditor
 * @class       Ext.ux.form.HtmlEditor.SpecialCharacters
 * @extends     Ext.util.Observable
 * @author      Shea Frederick - http://www.vinylfox.com
 * 
 * <p>A plugin that creates a button on the HtmlEditor for inserting special characters.</p>
 */
Ext.ux.form.HtmlEditor.SpecialCharacters = Ext.extend(Ext.util.Observable, {
    /**
     * @cfg {Array} specialChars
     * An array of additional characters to display for user selection.  Uses numeric portion of the ASCII HTML Character Code only. For example, to use the Copyright symbol, which is &#169; we would just specify <tt>169</tt> (ie: <tt>specialChars:[169]</tt>).
     */
    specialChars: [],
    /**
     * @cfg {Array} charRange
     * Two numbers specifying a range of ASCII HTML Characters to display for user selection. Defaults to <tt>[160, 256]</tt>.
     */
    charRange: [160, 256],
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        var cmp = this.cmp;
        var btn = this.cmp.getToolbar().addButton({
            iconCls: 'x-edit-char',
            handler: function(){
                if (this.specialChars.length) {
                    Ext.each(this.specialChars, function(c, i){
                        this.specialChars[i] = ['&#' + c + ';'];
                    }, this);
                }
                for (i = this.charRange[0]; i < this.charRange[1]; i++) {
                    this.specialChars.push(['&#' + i + ';']);
                }
                var charStore = new Ext.data.ArrayStore({
                    fields: ['char'],
                    data: this.specialChars
                });
                this.charWindow = new Ext.Window({
                    title: 'Insert Special Character',
                    width: 436,
                    autoHeight: true,
                    layout: 'fit',
                    items: [{
                        xtype: 'dataview',
                        store: charStore,
                        ref: '../charView',
                        autoHeight: true,
                        multiSelect: true,
                        tpl: new Ext.XTemplate('<tpl for="."><div class="char-item">{char}</div></tpl><div class="x-clear"></div>'),
                        overClass: 'char-over',
                        itemSelector: 'div.char-item',
                        listeners: {
                            dblclick: function(t, i, n, e){
                                this.insertChar(t.getStore().getAt(i).get('char'));
                                this.charWindow.close();
                            },
                            scope: this
                        }
                    }],
                    buttons: [{
                        text: 'Insert',
                        handler: function(){
                            Ext.each(this.charWindow.charView.getSelectedRecords(), function(rec){
                                var c = rec.get('char');
                                this.insertChar(c);
                            }, this);
                            this.charWindow.close();
                        },
                        scope: this
                    }, {
                        text: 'Cancel',
                        handler: function(){
                            this.charWindow.close();
                        },
                        scope: this
                    }]
                });
                this.charWindow.show();
            },
            scope: this,
            tooltip: {
                title: 'Insert Special Character'
            },
            overflowText: 'Special Characters'
        });
    },
    /**
     * Insert a single special character into the document.
     * @param c String The special character to insert (not just the numeric code, but the entire ASCII HTML entity).
     */
    insertChar: function(c){
        if (c) {
            this.cmp.insertAtCursor(c);
        }
    }
});
/**
 * @namespace   Ext.ux.form.HtmlEditor
 * @class       Ext.ux.form.HtmlEditor.Table
 * @extends     Ext.util.Observable
 * @author      Shea Frederick - http://www.vinylfox.com
 * 
 * <p>A plugin that creates a button on the HtmlEditor for making simple tables.</p>
 */
Ext.ux.form.HtmlEditor.Table = Ext.extend(Ext.util.Observable, {
    // private
    cmd: 'table',
    /**
     * @cfg {Array} tableBorderOptions
     * A nested array of value/display options to present to the user for table border style. Defaults to a simple list of 5 varrying border types.
     */
    tableBorderOptions: [['none', 'None'], ['1px solid #000', 'Sold Thin'], ['2px solid #000', 'Solid Thick'], ['1px dashed #000', 'Dashed'], ['1px dotted #000', 'Dotted']],
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        var cmp = this.cmp;
        var btn = this.cmp.getToolbar().addButton({
            iconCls: 'x-edit-table',
            handler: function(){
                if (!this.tableWindow){
                    this.tableWindow = new Ext.Window({
                        title: 'Insert Table',
                        closeAction: 'hide',
                        items: [{
                            itemId: 'insert-table',
                            xtype: 'form',
                            border: false,
                            plain: true,
                            bodyStyle: 'padding: 10px;',
                            labelWidth: 60,
                            labelAlign: 'right',
                            items: [{
                                xtype: 'numberfield',
                                allowBlank: false,
                                allowDecimals: false,
                                fieldLabel: 'Rows',
                                name: 'row',
                                width: 60
                            }, {
                                xtype: 'numberfield',
                                allowBlank: false,
                                allowDecimals: false,
                                fieldLabel: 'Columns',
                                name: 'col',
                                width: 60
                            }, {
                                xtype: 'combo',
                                fieldLabel: 'Border',
                                name: 'border',
                                forceSelection: true,
                                mode: 'local',
                                store: new Ext.data.ArrayStore({
                                    autoDestroy: true,
                                    fields: ['spec', 'val'],
                                    data: this.tableBorderOptions
                                }),
                                triggerAction: 'all',
                                value: 'none',
                                displayField: 'val',
                                valueField: 'spec',
                                width: 90
                            }]
                        }],
                        buttons: [{
                            text: 'Insert',
                            handler: function(){
                                var frm = this.tableWindow.getComponent('insert-table').getForm();
                                if (frm.isValid()) {
                                    var border = frm.findField('border').getValue();
                                    var rowcol = [frm.findField('row').getValue(), frm.findField('col').getValue()];
                                    if (rowcol.length == 2 && rowcol[0] > 0 && rowcol[0] < 10 && rowcol[1] > 0 && rowcol[1] < 10) {
                                        var html = "<table>";
                                        for (var row = 0; row < rowcol[0]; row++) {
                                            html += "<tr>";
                                            for (var col = 0; col < rowcol[1]; col++) {
                                                html += "<td width='20%' style='border: " + border + ";'>" + row + "-" + col + "</td>";
                                            }
                                            html += "</tr>";
                                        }
                                        html += "</table>";
                                        this.cmp.insertAtCursor(html);
                                    }
                                    this.tableWindow.hide();
                                }else{
                                    if (!frm.findField('row').isValid()){
                                        frm.findField('row').getEl().frame();
                                    }else if (!frm.findField('col').isValid()){
                                        frm.findField('col').getEl().frame();
                                    }
                                }
                            },
                            scope: this
                        }, {
                            text: 'Cancel',
                            handler: function(){
                                this.tableWindow.hide();
                            },
                            scope: this
                        }]
                    });
                
                }else{
                    this.tableWindow.getEl().frame();
                }
                this.tableWindow.show();
            },
            scope: this,
            tooltip: {
                title: 'Insert Table'
            },
            overflowText: 'Table'
        });
    }
});
/**
 * @namespace   Ext.ux.form.HtmlEditor
 * @class       Ext.ux.form.HtmlEditor.Word
 * @extends     Ext.util.Observable
 * @author      Shea Frederick - http://www.vinylfox.com
 * 
 * <p>A plugin that creates a button on the HtmlEditor for pasting text from Word without all the jibberish html.</p>
 */
Ext.ux.form.HtmlEditor.Word = Ext.extend(Ext.util.Observable, {
    curLength: 0,
    lastLength: 0,
    lastValue: '',
    wordPasteEnabled: true,
    // private
    init: function(cmp){
        
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
        this.cmp.on('initialize', this.onInit, this, {delay:100, single: true});
        
    },
    // private
    onInit: function(){
        
        Ext.EventManager.on(this.cmp.getDoc(), {
            'keyup': this.checkIfPaste,
            scope: this
        });
        this.lastValue = this.cmp.getValue();
        this.curLength = this.lastValue.length;
        this.lastLength = this.lastValue.length;
        
    },
    // private
    checkIfPaste: function(e){
        
        var diffAt = 0;
        this.curLength = this.cmp.getValue().length;
        
        if (e.V == e.getKey() && e.ctrlKey && this.wordPasteEnabled){
            
            this.cmp.suspendEvents();
            
            diffAt = this.findValueDiffAt(this.cmp.getValue());
            var parts = [
                this.cmp.getValue().substr(0, diffAt),
                this.fixWordPaste(this.cmp.getValue().substr(diffAt, (this.curLength - this.lastLength))),
                this.cmp.getValue().substr((this.curLength - this.lastLength)+diffAt, this.curLength)
            ];
            this.cmp.setValue(parts.join(''));
            
            this.cmp.resumeEvents();
        }
        
        this.lastLength = this.cmp.getValue().length;
        this.lastValue = this.cmp.getValue();
        
    },
    // private
    findValueDiffAt: function(val){
        
        for (i=0;i<this.curLength;i++){
            if (this.lastValue[i] != val[i]){
                return i;            
            }
        }
        
    },
    /**
     * Cleans up the jubberish html from Word pasted text.
     * @param wordPaste String The text that needs to be cleansed of Word jibberish html.
     * @return {String} The passed in text with all Word jibberish html removed.
     */
    fixWordPaste: function(wordPaste) {
        
        var removals = [/&nbsp;/ig, /[\r\n]/g, /<(xml|style)[^>]*>.*?<\/\1>/ig, /<\/?(meta|object|span)[^>]*>/ig,
            /<\/?[A-Z0-9]*:[A-Z]*[^>]*>/ig, /(lang|class|type|href|name|title|id|clear)=\"[^\"]*\"/ig, /style=(\'\'|\"\")/ig, /<![\[-].*?-*>/g, 
            /MsoNormal/g, /<\\?\?xml[^>]*>/g, /<\/?o:p[^>]*>/g, /<\/?v:[^>]*>/g, /<\/?o:[^>]*>/g, /<\/?st1:[^>]*>/g, /&nbsp;/g, 
            /<\/?SPAN[^>]*>/g, /<\/?FONT[^>]*>/g, /<\/?STRONG[^>]*>/g, /<\/?H1[^>]*>/g, /<\/?H2[^>]*>/g, /<\/?H3[^>]*>/g, /<\/?H4[^>]*>/g, 
            /<\/?H5[^>]*>/g, /<\/?H6[^>]*>/g, /<\/?P[^>]*><\/P>/g, /<!--(.*)-->/g, /<!--(.*)>/g, /<!(.*)-->/g, /<\\?\?xml[^>]*>/g, 
            /<\/?o:p[^>]*>/g, /<\/?v:[^>]*>/g, /<\/?o:[^>]*>/g, /<\/?st1:[^>]*>/g, /style=\"[^\"]*\"/g, /style=\'[^\"]*\'/g, /lang=\"[^\"]*\"/g, 
            /lang=\'[^\"]*\'/g, /class=\"[^\"]*\"/g, /class=\'[^\"]*\'/g, /type=\"[^\"]*\"/g, /type=\'[^\"]*\'/g, /href=\'#[^\"]*\'/g, 
            /href=\"#[^\"]*\"/g, /name=\"[^\"]*\"/g, /name=\'[^\"]*\'/g, / clear=\"all\"/g, /id=\"[^\"]*\"/g, /title=\"[^\"]*\"/g, 
            /<span[^>]*>/g, /<\/?span[^>]*>/g, /class=/g];
                    
        Ext.each(removals, function(s){
            wordPaste = wordPaste.replace(s, "");
        });
        
        // keep the divs in paragraphs
        wordPaste = wordPaste.replace(/<div[^>]*>/g, "<p>");
        wordPaste = wordPaste.replace(/<\/?div[^>]*>/g, "</p>");
        return wordPaste;
        
    },
    // private
    onRender: function() {
        
        this.cmp.getToolbar().add({
            iconCls: 'x-edit-wordpaste',
            pressed: true,
            handler: function(t){
                t.toggle(!t.pressed);
                this.wordPasteEnabled = !this.wordPasteEnabled;
            },
            scope: this,
            tooltip: {
                text: 'Cleanse text pasted from Word or other Rich Text applications'
            }
        });
        
    }
});
/**
 * @namespace   Ext.ux.form.HtmlEditor
 * @class       Ext.ux.form.HtmlEditor.HR
 * @extends     Ext.util.Observable
 * @author      Shea Frederick - http://www.vinylfox.com
 * 
 * <p>A plugin that creates a button on the HtmlEditor for inserting a horizontal rule.</p>
 */
Ext.ux.form.HtmlEditor.HR = Ext.extend(Ext.util.Observable, {
    // private
    cmd: 'hr',
    // private
    init: function(cmp){
        this.cmp = cmp;
        this.cmp.on('render', this.onRender, this);
    },
    // private
    onRender: function(){
        var cmp = this.cmp;
        var btn = this.cmp.getToolbar().addButton({
            iconCls: 'x-edit-hr',
            handler: function(){
                if (!this.hrWindow){
                    this.hrWindow = new Ext.Window({
                        title: 'Insert Rule',
                        closeAction: 'hide',
                        items: [{
                            itemId: 'insert-hr',
                            xtype: 'form',
                            border: false,
                            plain: true,
                            bodyStyle: 'padding: 10px;',
                            labelWidth: 60,
                            labelAlign: 'right',
                            items: [{
                                xtype: 'label',
                                html: 'Enter the width of the Rule in percentage<br/> followed by the % sign at the end, or to<br/> set a fixed width omit the % symbol.<br/>&nbsp;'
                            }, {
                                xtype: 'textfield',
                                maskRe: /[0-9]|%/,
                                regex: /^[1-9][0-9%]{1,3}/,
                                fieldLabel: 'Width',
                                name: 'hrwidth',
                                width: 60,
                                 listeners: {
                                    specialkey: function(f, e){
                                        if ((e.getKey() == e.ENTER || e.getKey() == e.RETURN) && f.isValid()) {
                                            this.doInsertHR();
                                        }else{
                                            f.getEl().frame();
                                        }
                                    },
                                    scope: this
                                }
                            }]
                        }],
                        buttons: [{
                            text: 'Insert',
                            handler: function(){
                                var frm = this.hrWindow.getComponent('insert-hr').getForm();
                                if (frm.isValid()){
                                    this.doInsertHR();
                                }else{
                                    frm.findField('hrwidth').getEl().frame();
                                }
                            },
                            scope: this
                        }, {
                            text: 'Cancel',
                            handler: function(){
                                this.hrWindow.hide();
                            },
                            scope: this
                        }]
                    });
                }else{
                    this.hrWindow.getEl().frame();
                }
                this.hrWindow.show();
            },
            scope: this,
            tooltip: {
                title: 'Insert Horizontal Rule'
            },
            overflowText: 'Horizontal Rule'
        });
    },
    // private
    doInsertHR: function(){
        var frm = this.hrWindow.getComponent('insert-hr').getForm();
        if (frm.isValid()) {
            var hrwidth = frm.findField('hrwidth').getValue();
            if (hrwidth) {
                this.insertHR(hrwidth);
            } else {
                this.insertHR('100%');
            }
            frm.reset();
            this.hrWindow.hide();
        }
    },
    /**
     * Insert a horizontal rule into the document.
     * @param w String The width of the horizontal rule as the <tt>width</tt> attribute of the HR tag expects. ie: '100%' or '400' (pixels).
     */
    insertHR: function(w){
        this.cmp.insertAtCursor('<hr width="' + w + '">');
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux', 'Ext.ux.layout');

/**
 * @namespace   Ext.ux.layout
 * @class       Ext.ux.layout.HorizontalFitLayout
 * @extends     Ext.layout.ContainerLayout
 * @description
 * <p>This is a base class for layouts that contain a single item that automatically expands horizontally to fill 
 * the horizontal dimenson of the layout's container.  This class is intended to be extended or created via the 
 * layout:'hfit' {@link Ext.Container#layout} config, and should generally not need to be created directly via 
 * the new keyword.</p>
 * <p>To fit a panel to a container horizontally using Horizontal FitLayout, simply set layout:'hfit' on the container 
 * and add a multiple panel to it.</p>
 * <p>Example usage:</p>
 * <pre><code>
var p = new Ext.Panel({
    title: 'Horizontal Fit Layout',
    layout:'hfit',
    items: [{
        title: 'Inner Panel One',
        html: '&lt;p&gt;This is the firsts inner panel content&lt;/p&gt;',
        border: false
    },{
        title: 'Inner Panel Two',
        html: '&lt;p&gt;This is the seconds inner panel content&lt;/p&gt;',
        border: false
    }]
});
</code></pre>
 */
Ext.ux.layout.HorizontalFitLayout = Ext.extend(Ext.layout.ContainerLayout, {
    /**
     * @cfg {bool} containsScrollbar
     */
    containsScrollbar: false,
    /**
     * @private
     */
    monitorResize:true,

    /**
     * @private
     */
    onLayout : function(ct, target){
        Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target);
        if(!this.container.collapsed){
            var size = target.getStyleSize();
            size.width = ct.containsScrollbar ? size.width-16 : size.width;
            
            ct.items.each(function(item){
                this.setItemSize(item,  size);
            }, this);
        }
    },
    /**
     * @private
     */
    setItemSize : function(item, size){
        if(item && size.height > 0){ // display none?
            item.setWidth(size.width);
        }
    }
});
Ext.Container.LAYOUTS['hfit'] = Ext.ux.layout.HorizontalFitLayout;
/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */

Ext.ns('Ext.ux.layout');

/**
 * @namespace   Ext.ux.layout
 * @class       Ext.ux.layout.CenterLayout
 * @extends     Ext.layout.FitLayout
 * 
 * <p>This is a very simple layout style used to center contents within a container.  This layout works within
 * nested containers and can also be used as expected as a Viewport layout to center the page layout.</p>
 * <p>As a subclass of FitLayout, CenterLayout expects to have a single child panel of the container that uses 
 * the layout.  The layout does not require any config options, although the child panel contained within the
 * layout must provide a fixed or percentage width.  The child panel's height will fit to the container by
 * default, but you can specify <tt>autoHeight:true</tt> to allow it to autosize based on its content height.  
 * Example usage:</p> 
 * <pre><code>
// The content panel is centered in the container
var p = new Ext.Panel({
    title: 'Center Layout',
    layout: 'ux.center',
    items: [{
        title: 'Centered Content',
        width: '75%',
        html: 'Some content'
    }]
});

// If you leave the title blank and specify no border
// you'll create a non-visual, structural panel just
// for centering the contents in the main container.
var p = new Ext.Panel({
    layout: 'ux.center',
    border: false,
    items: [{
        title: 'Centered Content',
        width: 300,
        autoHeight: true,
        html: 'Some content'
    }]
});
</code></pre>
 */
Ext.ux.layout.CenterLayout = Ext.extend(Ext.layout.FitLayout, {
    // private
    setItemSize : function(item, size){
        this.container.addClass('ux-layout-center');
        item.addClass('ux-layout-center-item');
        if(item && size.height > 0){
            if(item.width){
                size.width = item.width;
            }
            item.setSize(size);
        }
    }
});
Ext.Container.LAYOUTS['ux.center'] = Ext.ux.layout.CenterLayout;
/*
 * Ext JS Library 3.0.0
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
// We are adding these custom layouts to a namespace that does not
// exist by default in Ext, so we have to add the namespace first:
Ext.ns('Ext.ux.layout');

/**
 * @namespace   Ext.ux.layout
 * @class       Ext.ux.layout.RowLayout
 * @extends     Ext.layout.ContainerLayout
 * 
 * <p>This is the layout style of choice for creating structural layouts in a multi-row format where the height of
 * each row can be specified as a percentage or fixed height.  Row widths can also be fixed, percentage or auto.
 * This class is intended to be extended or created via the layout:'ux.row' {@link Ext.Container#layout} config,
 * and should generally not need to be created directly via the new keyword.</p>
 * <p>RowLayout does not have any direct config options (other than inherited ones), but it does support a
 * specific config property of <b><tt>rowHeight</tt></b> that can be included in the config of any panel added to it.  The
 * layout will use the rowHeight (if present) or height of each panel during layout to determine how to size each panel.
 * If height or rowHeight is not specified for a given panel, its height will default to the panel's height (or auto).</p>
 * <p>The height property is always evaluated as pixels, and must be a number greater than or equal to 1.
 * The rowHeight property is always evaluated as a percentage, and must be a decimal value greater than 0 and
 * less than 1 (e.g., .25).</p>
 * <p>The basic rules for specifying row heights are pretty simple.  The logic makes two passes through the
 * set of contained panels.  During the first layout pass, all panels that either have a fixed height or none
 * specified (auto) are skipped, but their heights are subtracted from the overall container height.  During the second
 * pass, all panels with rowHeights are assigned pixel heights in proportion to their percentages based on
 * the total <b>remaining</b> container height.  In other words, percentage height panels are designed to fill the space
 * left over by all the fixed-height and/or auto-height panels.  Because of this, while you can specify any number of rows
 * with different percentages, the rowHeights must always add up to 1 (or 100%) when added together, otherwise your
 * layout may not render as expected.  Example usage:</p>
 * <pre><code>
// All rows are percentages -- they must add up to 1
var p = new Ext.Panel({
    title: 'Row Layout - Percentage Only',
    layout:'ux.row',
    items: [{
        title: 'Row 1',
        rowHeight: .25
    },{
        title: 'Row 2',
        rowHeight: .6
    },{
        title: 'Row 3',
        rowHeight: .15
    }]
});

// Mix of height and rowHeight -- all rowHeight values must add
// up to 1. The first row will take up exactly 120px, and the last two
// rows will fill the remaining container height.
var p = new Ext.Panel({
    title: 'Row Layout - Mixed',
    layout:'ux.row',
    items: [{
        title: 'Row 1',
        height: 120,
        // standard panel widths are still supported too:
        width: '50%' // or 200
    },{
        title: 'Row 2',
        rowHeight: .8,
        width: 300
    },{
        title: 'Row 3',
        rowHeight: .2
    }]
});
</code></pre>
 */
Ext.ux.layout.RowLayout = Ext.extend(Ext.layout.ContainerLayout, {
    // private
    monitorResize:true,

    // private
    isValidParent : function(c, target){
        return c.getEl().dom.parentNode == this.innerCt.dom;
    },

    // private
    onLayout : function(ct, target){
        var rs = ct.items.items, len = rs.length, r, i;

        if(!this.innerCt){
            target.addClass('ux-row-layout-ct');
            this.innerCt = target.createChild({cls:'x-row-inner'});
        }
        this.renderAll(ct, this.innerCt);

        var size = target.getViewSize();

        if(size.width < 1 && size.height < 1){ // display none?
            return;
        }

        var h = size.height - target.getPadding('tb'),
            ph = h;

        this.innerCt.setSize({height:h});

        // some rows can be percentages while others are fixed
        // so we need to make 2 passes

        for(i = 0; i < len; i++){
            r = rs[i];
            if(!r.rowHeight){
                ph -= (r.getSize().height + r.getEl().getMargins('tb'));
            }
        }

        ph = ph < 0 ? 0 : ph;

        for(i = 0; i < len; i++){
            r = rs[i];
            if(r.rowHeight){
                r.setSize({height: Math.floor(r.rowHeight*ph) - r.getEl().getMargins('tb')});
            }
        }
    }

    /**
     * @property activeItem
     * @hide
     */
});

Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
/**
 * Tine 2.0
 * 
 * @package     Ext
 * @subpackage  ux
 * @license     BSD
 * @author      Shea Frederick
 * @copyright   Copyright (c) 2008 Shea Frederick
 *
 * based on public domain code from Shea Frederick
 * "Free as a bird. Use it anywhere or any way you like."
 * 
 * http://extjs.com/blog/2008/07/01/integrating-google-maps-api-with-extjs/
 */
Ext.ns('Ext.ux');
 
/**
 * @namespace   Ext.ux
 * @class       Ext.ux.GMapPanel
 * @extends     Ext.Panel
 */
Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {
    initComponent : function(){
        
        var defConfig = {
            plain: true,
            zoomLevel: 3,
            yaw: 180,
            pitch: 0,
            zoom: 0,
            gmapType: 'map',
            border: false
        };
        
        Ext.applyIf(this,defConfig);
        
        Ext.ux.GMapPanel.superclass.initComponent.call(this);

    },
    afterRender : function(){
        
        var wh = this.ownerCt.getSize();
        Ext.applyIf(this, wh);
        
        Ext.ux.GMapPanel.superclass.afterRender.call(this);
        
        if (this.gmapType === 'map'){
            this.gmap = new GMap2(this.body.dom);
        }
        
        if (this.gmapType === 'panorama'){
            this.gmap = new GStreetviewPanorama(this.body.dom);
        }
        
        if (typeof this.addControl === 'object' && this.gmapType === 'map') {
            this.gmap.addControl(this.addControl);
        }
        
        if (typeof this.setCenter === 'object') {
            if (typeof this.setCenter.geoCodeAddr === 'string'){
                this.geoCodeLookup(this.setCenter.geoCodeAddr);
            }else{
                if (this.gmapType === 'map'){
                    var point = new GLatLng(this.setCenter.lat,this.setCenter['long']);
                    this.gmap.setCenter(point, this.zoomLevel);
                }
                if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){
                    this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear);
                }
            }
            if (this.gmapType === 'panorama'){
                this.gmap.setLocationAndPOV(new GLatLng(this.setCenter.lat,this.setCenter['long']), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoom});
            }
        }
        
        var dt = new Ext.util.DelayedTask();
        dt.delay(300, function(){
            this.addMarkers(this.markers);
        }, this);

    },
    onResize : function(w, h){

        if (typeof this.gmap == 'object') {
            this.gmap.checkResize();
        }
        
        Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);

    },
    setSize : function(width, height, animate){
        
        if (typeof this.gmap == 'object') {
            this.gmap.checkResize();
        }
        
        Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);
        
    },
    getMap: function(){
        
        return this.gmap;
        
    },
    addMarkers: function(markers) {
        
        if (Ext.isArray(markers)){
            for (var i = 0; i < markers.length; i++) {
                var mkr_point = new GLatLng(markers[i].lat,markers[i]['long']);
                this.addMarker(mkr_point,markers[i].marker,false,markers[i].setCenter);
            }
        }
        
    },
    addMarker: function(point, marker, clear, center){
        
        Ext.applyIf(marker,G_DEFAULT_ICON);

        if (clear === true){
            this.gmap.clearOverlays();
        }
        if (center === true) {
            this.gmap.setCenter(point, this.zoomLevel);
        }
        
        var mark = new GMarker(point,marker);
        this.gmap.addOverlay(mark);

    },
    geoCodeLookup : function(addr) {
        
        this.geocoder = new GClientGeocoder();
        this.geocoder.getLocations(addr, this.addAddressToMap.createDelegate(this));
        
    },
    addAddressToMap : function(response) {
        
        if (!response || response.Status.code != 200) {
            Ext.MessageBox.alert('Error', 'Code '+response.Status.code+' Error Returned');
        } else {
            place = response.Placemark[0];
            addressinfo = place.AddressDetails;
            accuracy = addressinfo.Accuracy;
            if (accuracy === 0) {
                Ext.MessageBox.alert('Unable to Locate Address', 'Unable to Locate the Address you provided');
            }else{
                if (accuracy < 7) {
                    Ext.MessageBox.alert('Address Accuracy', 'The address provided has a low accuracy.<br><br>Level '+accuracy+' Accuracy (8 = Exact Match, 1 = Vague Match)');
                }else{
                    point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
                    if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){
                        this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear,true);
                    }
                }
            }
        }
        
    }
 
});

Ext.reg('gmappanel',Ext.ux.GMapPanel);
/**
 * Ext.ux
 * 
 * @package     Ext.ux
 */

Ext.ns('Ext.ux');


Date.prototype.getFirstDateOfWeek = function(){
    var value=this.clearTime();
    var semana=this.getWeekOfYear();
    while(semana == value.getWeekOfYear()) {
        value=value.add(Date.DAY,-1);
    }
    value=value.add(Date.DAY,1);
    return value;
};

/**
 * @namespace   Ext.ux
 * @class       Ext.ux.DatePickerRange
 * @extends     Ext.DatePicker
 */
Ext.ux.DatePickerRange = Ext.extend(Ext.DatePicker, {
    selectionMode:'month',
    setSelectionMode:function(mode){
        this.selectionMode=mode;
        this.setValue(this.value);
    },
    getSelectionMode:function(){
        return this.selectionMode();
    },

    //private
    update : function(date){
        var vd = this.activeDate;
        this.activeDate = date;
        if(vd && this.el){
            var t = date.getTime();
            if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
                this.cells.removeClass("x-date-selected");
                this.cells.each(function(c){
                   if(this.isSelected(  c.dom.firstChild.dateValue  )){
                       c.addClass("x-date-selected");
                   }
                },this);
                return;
            }
        }
        var days = date.getDaysInMonth();
        var firstOfMonth = date.getFirstDateOfMonth();
        var startingPos = firstOfMonth.getDay()-this.startDay;

        if(startingPos <= this.startDay){
            startingPos += 7;
        }

        var pm = date.add("mo", -1);
        var prevStart = pm.getDaysInMonth()-startingPos;

        var cells = this.cells.elements;
        var textEls = this.textNodes;
        days += startingPos;

        
        var day = 86400000;
        var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
        var today = new Date().clearTime().getTime();
        var sel = date.clearTime().getTime();
        var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
        var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
        var ddMatch = this.disabledDatesRE;
        var ddText = this.disabledDatesText;
        var ddays = this.disabledDays ? this.disabledDays.join("") : false;
        var ddaysText = this.disabledDaysText;
        var format = this.format;



        var setCellClass = function(cal, cell){
            cell.title = "";
            var t = d.getTime();
            cell.firstChild.dateValue = t;
            if(t == today){
                cell.className += " x-date-today";
                cell.title = cal.todayText;
            }
            if(cal.isSelected(cell.firstChild.dateValue)){
                cell.className += " x-date-selected";
            }
            
            if(t < min) {
                cell.className = " x-date-disabled";
                cell.title = cal.minText;
                return;
            }
            if(t > max) {
                cell.className = " x-date-disabled";
                cell.title = cal.maxText;
                return;
            }
            if(ddays){
                if(ddays.indexOf(d.getDay()) != -1){
                    cell.title = ddaysText;
                    cell.className = " x-date-disabled";
                }
            }
            if(ddMatch && format){
                var fvalue = d.dateFormat(format);
                if(ddMatch.test(fvalue)){
                    cell.title = ddText.replace("%0", fvalue);
                    cell.className = " x-date-disabled";
                }
            }
        };

        var i = 0;
        for(; i < startingPos; i++) {
            textEls[i].innerHTML = (++prevStart);
            d.setDate(d.getDate()+1);
            cells[i].className = "x-date-prevday";
            setCellClass(this, cells[i]);
        }

        for(; i < days; i++){
            intDay = i - startingPos + 1;
            textEls[i].innerHTML = (intDay);
            d.setDate(d.getDate()+1);
            cells[i].className = "x-date-active";
            setCellClass(this, cells[i]);
        }
        var extraDays = 0;
        for(; i < 42; i++) {
             textEls[i].innerHTML = (++extraDays);
             d.setDate(d.getDate()+1);
             cells[i].className = "x-date-nextday";
             setCellClass(this, cells[i]);
        }

        this.mbtn.setText(this.monthNames[date.getMonth()] + " " + date.getFullYear());

        if(!this.internalRender){
            var main = this.el.dom.firstChild;
            var w = main.offsetWidth;
            this.el.setWidth(w + this.el.getBorderWidth("lr"));
            Ext.fly(main).setWidth(w);
            this.internalRender = true;
            
            if(Ext.isOpera && !this.secondPass){
                main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px";
                this.secondPass = true;
                this.update.defer(10, this, [date]);
            }
        }
    },
    isSelected:function(date){
        date=new Date(date);
        switch(this.selectionMode) {
            case 'day':
               return date.clearTime().getTime() == this.value.clearTime().getTime();
               break;
            case 'month':
               return date.getFirstDateOfMonth().clearTime().getTime ()==this.value.getFirstDateOfMonth().clearTime().getTime ();
               break;
            case 'week':
               return date.getFirstDateOfWeek().clearTime().getTime ()==this.value.getFirstDateOfWeek().clearTime().getTime ();
               break;
            default:
               throw 'Illegal selection mode';
               break;
        }        
    }
        
    
});

Ext.reg('datepickerrange', Ext.ux.DatePickerRange);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux', 'Ext.ux.tree');

/**
 * @namespace   Ext.ux.tree
 * @class       Ext.ux.tree.CheckboxSelectionModel
 * @extends     Ext.util.Observable
 * 
 * Checkbox multi selection for a TreePanel.
 */
Ext.ux.tree.CheckboxSelectionModel = function(config){
   this.addEvents(
       /**
        * @event beforeselect
        * Fires before the selected node changes, return false to cancel the change
        * @param {DefaultSelectionModel} this
        * @param {TreeNode} node the node to select
        */
       "beforeselect",
       /**
        * @event selectionchange
        * Fires when the selected nodes change
        * @param {MultiSelectionModel} this
        * @param {Array} nodes Array of the selected nodes
        */
       "selectionchange"
   );
    Ext.apply(this, config);
    Ext.ux.tree.CheckboxSelectionModel.superclass.constructor.call(this);
};

Ext.extend(Ext.ux.tree.CheckboxSelectionModel, Ext.util.Observable, {
    /**
     * @cfg {Bool} activateLeafNodesOnly
     * true to only activate leaf nodes
     */
    activateLeafNodesOnly : false,
    
    /**
     * @cfg {bool} optimizeSelection
     * true to optimize selection
     */
    optimizeSelection: false,
    
    /**
     * @type Ext.tree.TreeNode
     * currently active node
     */
    activeNode: null,
    
    /**
     * @cfg {Boolean} defaultValue
     * default check value
     */
    defaultValue: false,
    
    /**
     * activate given node
     * 
     * @param {Ext.tree.TreeNode} node
     * @return {Boolean}
     */
    activate: function(node) {
        if (! node) {
            return;
        }
        
        if (this.activateLeafNodesOnly && ! node.isLeaf()) {
            return false;
        }
        
        if (this.activeNode) {
            this.activeNode.ui.onSelectedChange(false);
        }
        
        this.activeNode = node;
        node.ui.onSelectedChange(true);
    },
        
    /**
     * Clear all selections
     */
    clearSelections : function(suppressEvent) {
        this.suspendEvents(false);
        
        var sn = this.tree.getChecked();
        if(sn.length > 0){
            for(var i = 0, len = sn.length; i < len; i++){
                sn[i].ui.toggleCheck(false);
            }
        }
        
        this.resumeEvents();
    },
    
    /**
     * get active node
     * @return {Ext.tree.TreeNode}
     */
    getActiveNode: function() {
        return this.activeNode;
    },
    
    /**
     * Returns an array of the selected nodes
     * @return {Array}
     */
    getSelectedNodes: function() {
        return this.tree.getChecked();
    },
    
    init : function(tree){
        this.tree = tree;
        tree.getTreeEl().on("keydown", this.onKeyDown, this);
        tree.on("click", this.onNodeClick, this);
        tree.on("beforeappend", this.onBeforeAppend, this);
        tree.on("checkchange", this.onCheckChange, this);
        
    },
    
    /**
     * Returns true if the node is selected
     * @param {Ext.tree.TreeNode} node The node to check
     * @return {Boolean}
     */
    isSelected : function(node){
        if (node && node.ui) {
            return node.ui.isChecked();
        }
    },
    
    onBeforeAppend: function(tree, parent, node) {
        node.attributes.checked = this.defaultValue;
    },
    
    onCheckChange: function(node, checked) {
        if (checked) {
            this.activate(node);
            
            if (this.optimizeSelection) {
                this.optimize(node);
            }
            
        } else {
            
        }
        this.fireEvent("selectionchange", this, this.tree.getChecked());
    },
    
    onNodeClick : function(node){
        this.select(node);
        this.activate(node);
    },
    
    /**
     * Select a node.
     * @param {Ext.tree.TreeNode} node The node to select
     * @return {Text.tree.TreeNode} The selected node
     */
    select : function(node, e, keepExisting){
        if (! node.ui.isChecked() && this.fireEvent('beforeselect', this, node) !== false) {
            node.ui.toggleCheck(true);
        }
        return node;
    },
    
    /**
     * Deselect a node.
     * @param {Ext.tree.TreeNode} node The node to unselect
     */
    unselect : function(node){
        node.ui.toggleCheck(false);
    },
    
    /**
     * optimizes the selection
     */
    optimize: function(node) {
        this.suspendEvents();
            
        this.unselectChildNodes(node);
        
        // recursivly unselect parent nodes
        while(node = node.parentNode) {
            if (this.isSelected(node)) {
                node.unselect();
            }
        }
        
        this.resumeEvents();
    },
    
    /**
     * unselect child nodes of given node
     * 
     * @param {Ext.tree.TreeNode} node
     */
    unselectChildNodes: function(node) {
        if (node.isExpandable() && node.isExpanded()) {
            for (var i=0; i<node.childNodes.length; i++) {
                if (node.childNodes[i].isExpandable()) {
                    this.unselectChildNodes(node.childNodes[i]);
                }
                node.childNodes[i].unselect();
            }
        }
    },
    
    onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,

    selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,

    selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious
});
/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
Ext.ns('Ext.ux.display');

/**
 * @class       Ext.ux.display.DisplayPanel
 * @namespace   Ext.ux.display
 * @extends     Ext.form.FormPanel
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * 
 * <b>Panel for displaying information on record basis.</b>
 */
Ext.ux.display.DisplayPanel = Ext.extend(Ext.form.FormPanel, {
    cls : 'x-ux-display',
    
    layout: 'ux.display',
    
    loadRecord: function(record) {
        return this.getForm().loadRecord(record);
    },
    
    onRender: function() {
        this.supr().onRender.apply(this, arguments);
    }
});

Ext.reg('ux.displaypanel', Ext.ux.display.DisplayPanel);
/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
Ext.ns('Ext.ux.display');

/**
 * @class       Ext.ux.display.DisplayField
 * @namespace   Ext.ux.display
 * @extends     Ext.form.DisplayField
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * 
 * <b>Field for displaying information in a displaypanel</b>
 */
Ext.ux.display.DisplayField = Ext.extend(Ext.form.DisplayField, {
    htmlEncode: true,
    nl2br: false,
    
    renderer: function(v) {
        return v;
    },
    
    setRawValue : function(value) {
        var v = this.renderer(value);
        
        if(this.htmlEncode){
            v = Ext.util.Format.htmlEncode(v);
        }
        
        if (this.nl2br) {
            v = Ext.util.Format.nl2br(v);
        }
        
        return this.rendered ? (this.el.dom.innerHTML = (Ext.isEmpty(v) ? '' : v)) : (this.value = v);
    }

});

Ext.reg('ux.displayfield', Ext.ux.display.DisplayField);


/**
 * @class Ext.ux.display.DisplayTextArea
 * @namespace Ext.ux.display
 * @extends Ext.form.TextArea
 * @author Cornelius Weiss <c.weiss@metaways.de>
 * 
 * <b>Textarea for displaying a text in a displaypanel</b>
 */
Ext.ux.display.DisplayTextArea = Ext.extend(Ext.form.TextArea, {
    readOnly: true,
    cls: 'x-ux-display-textarea'
});

Ext.reg('ux.displaytextarea', Ext.ux.display.DisplayTextArea);
/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
Ext.ns('Ext.ux.layout');

/**
 * @namespace   Ext.ux.layout
 * @class       Ext.ux.layout.DisplayLayout
 * @extends     Ext.layout.FormLayout
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * 
 * <b>Layout for displaying information in a displaypanel</b>
 */
Ext.ux.layout.DisplayLayout = Ext.extend(Ext.layout.FormLayout, {
    /**
     * @cfg {String} background
     * one of {solid|border|none}
     */
    background: 'none',
    
    onLayout : function(ct, target) {
        Ext.ux.layout.DisplayLayout.superclass.onLayout.apply(this, arguments);
        
        target.addClass('x-ux-display-background-' + this.background);
        
        if (this.declaration && ! this.declEl) {
            this.declEl = target.createChild({dom: 'div', html: this.declaration, 'class': 'x-ux-display-declaration x-ux-display-background-declaration'});
        }
    },
    
    destroy: function() {
        if (this.declEl) {
            this.declEl.remove();
        }
        
        Ext.ux.layout.DisplayLayout.superclass.destroy.call(this);
    }
});

Ext.Container.LAYOUTS['ux.display'] = Ext.ux.layout.DisplayLayout;
   
Ext.ns('Ext.ux');

Ext.ux.MessageBox = function(){
    var msgCt;

    function createBox(t, s){
        return ['<div class="x-ux-messagebox-msg">',
                '<div class="x-box-tl"><div class="x-box-tr"><div class="x-box-tc"></div></div></div>',
                '<div class="x-box-ml"><div class="x-box-mr"><div class="x-box-mc"><h3>', t, '</h3>', s, '</div></div></div>',
                '<div class="x-box-bl"><div class="x-box-br"><div class="x-box-bc"></div></div></div>',
                '</div>'].join('');
    }
    return {
        msg : function(title, format){
            if(!msgCt){
                msgCt = Ext.DomHelper.insertFirst(document.body, {id:'x-ux-messagebox-msg-div'}, true);
            }
            msgCt.alignTo(document, 't-t');
            var s = String.format.apply(String, Array.prototype.slice.call(arguments, 1));
            var m = Ext.DomHelper.append(msgCt, {html:createBox(title, s)}, true);
            m.slideIn('t').pause(2).ghost("t", {remove:true});
        }
    };
}();
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Ext.ux');


/**
 * @namespace   Ext.ux
 * @class       Ext.ux.TabPanelSortPlugin
 * 
 * @constructor
 * @param {Object} config Configurations options
 */
Ext.ux.TabPanelSortPlugin = function(config) {
    Ext.apply(this, config);
};

Ext.ux.TabPanelSortPlugin.prototype = {
    
    /**
     * @cfg {Object} dragZoneConfig
     */
    dragZoneConfig: null,
    
    /**
     * @cfg {Object} dropZoneConfig
     */
    dropZoneConfig: null,
    
    /**
     * tabpanel
     * @type object
     */
    tabpanel: null,
    
    /**
     * current position
     * @type Number
     */
    pos: null,
    
    /**
     * init this plugin
     * 
     * @param {Ext.Component} cmp
     */
    init: function(cmp){
        this.tabpanel = cmp;
        
        this.tabpanel.addEvents(
            /**
             * @event tabsort
             * Fired when tabs where resorted
             * @param {Ext.TabPanel}
             */
            'tabsort'
        );
        
        this.tabpanel.on('render', this.onRender, this);
    },
    
    
    /**
     * onRender define dragZone and dropZone
     * 
     * @param {Ext.TabPanel} tabpanel
     */
    onRender: function(tabpanel) {
        this.dragZone = new Ext.dd.DragZone(tabpanel.header, Ext.apply({
            getDragData: this.getDragData.createDelegate(this),
            getRepairXY: this.getRepairXY.createDelegate(this)
        }, this.dragZoneConfig || {}));
        
        this.dropZone = new Ext.dd.DropZone(tabpanel.header, Ext.apply({
            onNodeOver : this.onNodeOver.createDelegate(this),
            onNodeDrop : this.onNodeDrop.createDelegate(this),
            getTargetFromEvent: this.getTargetFromEvent.createDelegate(this)
        }, this.dropZoneConfig || {}));
    },
    
    /**
     * gets static ddel
     * 
     * @return {Ext.Element}
     */
    getDDEl: function() {
        if (! this.ddel) {
            this.ddel = Ext.get(document.createElement('div'));
        }
        
        return this.ddel;
    },
    
    /**
     * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
     * for a valid target to drag based on the mouse down. Override this method
     * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
     * object has a "ddel" attribute (with an HTML Element) for other functions to work.
     * @param {EventObject} e The mouse down event
     * @return {Object} The dragData
     */
    getDragData: function(e) {
        var target = this.tabpanel.findTargets(e);

        if (target.el) {
            this.pos = this.tabpanel.items.indexOf(target.item);
            
            var ddel = this.getDDEl();
            ddel.update(Ext.DomQuery.selectNode('span[class^=x-tab-strip-text]', target.el).innerHTML);
            
            return Ext.apply(target, {
                pos: this.pos,
                ddel: ddel.dom,
                repairXY: Ext.fly(target.el).getXY()
            });
        };
    },
    
    /**
     * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
     * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
     * The default implementation returns this.dropNotAllowed, so it should be
     * overridden to provide the proper feedback.
     * 
     * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
     * {@link #getTargetFromEvent} for this node)
     * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
     * @param {Event} e The event
     * @param {Object} data An object containing arbitrary data supplied by the drag source
     * @return {String} status The CSS class that communicates the drop status back to the source so that the
     * underlying {@link Ext.dd.StatusProxy} can be updated
     */
    onNodeOver : function(target, dd, e, data){
        var target = this.tabpanel.findTargets(e);
        if (target.el) {
            this.pos = this.tabpanel.items.indexOf(target.item);
            var box = Ext.fly(target.el).getBox(),
                side = (e.getXY()[0] > box.x + box.width/2) ? 'r' : 'l';
            
            if (this.pos > data.pos && side === 'l') {
//                Ext.fly(data.el).insertBefore(target.el);
                this.pos = --this.pos;
            } else if (this.pos < data.pos && side === 'r') {
//                Ext.fly(data.el).insertAfter(target.el);
                this.pos = ++this.pos;
            }
        }
        
        //@TODO: separation line
        return Ext.dd.DropZone.prototype.dropAllowed;
    },
    
    /**
     * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
     * the drop node.  The default implementation returns false, so it should be overridden to provide the
     * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
     * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
     * {@link #getTargetFromEvent} for this node)
     * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
     * @param {Event} e The event
     * @param {Object} data An object containing arbitrary data supplied by the drag source
     * @return {Boolean} True if the drop was valid, else false
     */
    onNodeDrop: function(target, dd, e, data){
        if (this.pos == (this.tabpanel.items.length)-1) {
            Ext.fly(data.el).insertAfter(this.tabpanel.items.itemAt(this.pos).tabEl);
        } else {
            if (this.tabpanel.items.indexOf(data.item) < this.pos) {
                Ext.fly(data.el).insertBefore(this.tabpanel.items.itemAt(this.pos+1).tabEl);
            } else {
                Ext.fly(data.el).insertBefore(this.tabpanel.items.itemAt(this.pos).tabEl);
            }
        }
        
        this.tabpanel.items.remove(data.item);
        this.tabpanel.items.insert(this.pos, data.item);
        
        this.tabpanel.fireEvent('tabsort', this.tabpanel);
        return true;
    },
    
    /**
     * Called before a repair of an invalid drop to get the XY to animate to. By default returns
     * the XY of this.dragData.ddel
     * @param {EventObject} e The mouse up event
     * @return {Array} The xy location (e.g. [100, 200])
     */
    getRepairXY: function(e) {
        return this.dragZone.dragData.repairXY;
    },
    
    /**
     * Returns a custom data object associated with the DOM node that is the target of the event.  By default
     * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
     * provide your own custom lookup.
     * @param {Event} e The event
     * @return {Object} data The custom data
     */
    getTargetFromEvent: function(e) {
        return e.getTarget('ul[class^=x-tab]', 10);
    }
};
Ext.ns('Ext.ux');

/**
 * plugin to insert additional registered items into a container
 *
 * @namespace   Ext.ux
 * @class       Ext.ux.ItemRegistry
 * @autor       Cornelius Weiss <c.weiss@metaways.de>
 * @license     BSD, MIT and GPL
 *
 * @example
// register 'additional item' for myDialog
Ext.ux.ItemRegistry.registerItem('myDialog', 'add-item-xtype', 20);

// in myDialog use itemRegistyPlugin
myDialog = Ext.extend(Ext.Container, {
    ...
    plugins: [{
        ptype: 'ux.itemregistry',
        key:   'myDialog'
    }]
})
 */
Ext.ux.ItemRegistry = function (config) {
    Ext.apply(this, config);
};

/**
 * @static
 * @private
 */
Ext.ux.ItemRegistry.itemMap = {};

/**
 * registers an item for a given key
 * @static
 * 
 * @param {String} key
 * @param {String/Constructor/Object} item
 * @param {Number} pos (optional)
 */
Ext.ux.ItemRegistry.registerItem = function(key, item, pos) {
    if (! Ext.ux.ItemRegistry.itemMap.hasOwnProperty(key)) {
        Ext.ux.ItemRegistry.itemMap[key] = [];
    }

    Ext.ux.ItemRegistry.itemMap[key].push({
        item: item,
        pos: pos
    });
};

Ext.ux.ItemRegistry.prototype = {
    /**
     * @cfg {String} key
     * key the items are registered under. If no key is given, the itemId
     * of the component will be used
     */
    key: null,

    init: function(cmp) {
        this.cmp = cmp;

        if (! this.key) {
            this.key = cmp.getItemId();
        }
        
        // give static item pos to existing items
        this.cmp.items.each(function(item, idx) {
            if (! item.hasOwnProperty('registerdItemPos')) {
                item.registerdItemPos = idx * 10;
            }
        }, this);

        var regItems = Ext.ux.ItemRegistry.itemMap[this.key] || [];
        
        Ext.each(regItems, function(reg) {
            var addItem = this.getItem(reg),
                addPos = null;

            // insert item 
            this.cmp.items.each(function(item, idx) {
                if (addItem.registerdItemPos < item.registerdItemPos) {
                    this.cmp.insert(idx, addItem);
                    addPos = idx;
                    return false;
                }
                return true;
            }, this);

            if (! Ext.isNumber(addPos)) {
                this.cmp.add(addItem);
            }
        }, this);
    },

    getItem: function(reg) {
        var def = reg.item,
            item;
            
        if (typeof def === 'function') {
            item = new def(this.config);
        } else {
            if (Ext.isString(def)) {
                def = {xtype: def};
            }
            
            item = this.cmp.lookupComponent(def);
        }

        item.registerdItemPos = reg.pos ? reg.pos : this.cmp.items.length * 10;
        
        return item;
    }
};
Ext.ComponentMgr.registerPlugin('ux.itemregistry', Ext.ux.ItemRegistry);

/* test
 *
Ext.onReady(function() {
    var testWin = new Ext.Window({
        width: 640,
        height: 480,
        layout: 'fit',
        title: 'ux.itemregistry test',
        items: [{
            xtype: 'tabpanel',
            activeTab: 0,
            border: false,
            defaults: {border: false},
            itemId: 'testWin',
            plugins: ['ux.itemregistry'],
            items: [{
                title: 'basepanel',
                html: 'basepanel'
            }, {
                title: 'no pos',
                html: 'no pos'
            }, {
                title: 'pos 50',
                html: 'pos 50',
                registerdItemPos: 50
            }]
        }]
    });
    testWin.show();
});

itemRegTestPanel20 = Ext.extend(Ext.Panel, {
    title: 'add panel pos 20',
    html: 'add panel pos 20',

    initComponent: function() {
        // example how to hook in owner
        this.on('added', function(me, owner, pos) {
            owner.on('tabchange', function() {
                console.log('tabchange');
            })
        }),

        itemRegTestPanel20.superclass.initComponent.call(this);
    }
});
Ext.ux.ItemRegistry.registerItem('testWin', itemRegTestPanel20, 20);

itemRegTestPanel60 = {
    xtype: 'panel',
    title: 'add panel pos 60',
    html: 'add panel pos 60'
};
Ext.ux.ItemRegistry.registerItem('testWin', itemRegTestPanel60, 60);
*/
Ext.applyIf(Function.prototype, {

    /**
     * defers execution on a ticket bases. The function gets executed when all registered tickets are back.
     * 
     * @namespace   Function
     * @autor       Cornelius Weiss <c.weiss@metaways.de>
     * @license     BSD, MIT and GPL
     *
     *
var sayHi = function(name){
alert('Hi, ' + name);
}

var ticketFn = sayHi.deferByTickets(this, ['Fred']);

// take ticket
var ticketFn1 = ticketFn();
var ticketFn2 = ticketFn();

// give tickets back
ticketFn1();
ticketFn2();

     * NOTE: the function gets never executed if not at least one ticket is taken and given back
     *
     * @param {Object} scope (optional) The scope (this reference) in which the function is executed.
     * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
     * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
     * if a number the args are inserted at the specified position
     * @return {Function} ticketFn
     */
    deferByTickets: function(obj, args, appendArgs) {
        var fn = this.createDelegate(obj, args, appendArgs),
            waitTickets = [];
        
        // run if all tickets are back
        var run = function() {
            if (Ext.isEmpty(waitTickets)) {
                fn();
            }
        };

        return function() {
            var ticket = Ext.id();
            waitTickets.push(ticket);
            // fn to return wait ticket
            return function() {
                waitTickets.remove(ticket);
                run();
            };
        };
    }
});
/**
 * @class GetIt.GridPrinter
 * @author Ed Spencer (edward@domine.co.uk)
 * Class providing a common way of printing Ext.Components. Ext.ux.Printer.print delegates the printing to a specialised
 * renderer class (each of which subclasses Ext.ux.Printer.BaseRenderer), based on the xtype of the component.
 * Each renderer is registered with an xtype, and is used if the component to print has that xtype.
 * 
 * See the files in the renderers directory to customise or to provide your own renderers.
 * 
 * Usage example:
 * 
 * var grid = new Ext.grid.GridPanel({
 *   colModel: //some column model,
 *   store   : //some store
 * });
 * 
 * Ext.ux.Printer.print(grid);
 * 
 */
Ext.ux.Printer = function() {
  
  return {
    /**
     * @property renderers
     * @type Object
     * An object in the form {xtype: RendererClass} which is manages the renderers registered by xtype
     */
    renderers: {},
    
    /**
     * Registers a renderer function to handle components of a given xtype
     * @param {String} xtype The component xtype the renderer will handle
     * @param {Function} renderer The renderer to invoke for components of this xtype
     */
    registerRenderer: function(xtype, renderer) {
      this.renderers[xtype] = new (renderer)();
    },
    
    /**
     * Returns the registered renderer for a given xtype
     * @param {String} xtype The component xtype to find a renderer for
     * @return {Object/undefined} The renderer instance for this xtype, or null if not found
     */
    getRenderer: function(xtype) {
      return this.renderers[xtype];
    },
    
    /**
     * Prints the passed grid. Reflects on the grid's column model to build a table, and fills it using the store
     * @param {Ext.Component} component The component to print
     */
    print: function(component) {
      var xtypes = component.getXTypes().split('/');
      
      //iterate backwards over the xtypes of this component, dispatching to the most specific renderer
      for (var i = xtypes.length - 1; i >= 0; i--){
        var xtype    = xtypes[i],        
            renderer = this.getRenderer(xtype);
        
        if (renderer != undefined) {
          renderer.print(component);
          break;
        }
      }
    }
  };
}();

/**
 * Override how getXTypes works so that it doesn't require that every single class has
 * an xtype registered for it.
 */
Ext.override(Ext.Component, {
  getXTypes : function(){
      var tc = this.constructor;
      if(!tc.xtypes){
          var c = [], sc = this;
          while(sc){ //was: while(sc && sc.constructor.xtype) {
            var xtype = sc.constructor.xtype;
            if (xtype != undefined) c.unshift(xtype);
            
            sc = sc.constructor.superclass;
          }
          tc.xtypeChain = c;
          tc.xtypes = c.join('/');
      }
      return tc.xtypes;
  }
});
/**
 * @class Ext.ux.Printer.BaseRenderer
 * @extends Object
 * @author Ed Spencer
 * Abstract base renderer class. Don't use this directly, use a subclass instead
 */
Ext.ux.Printer.BaseRenderer = Ext.extend(Object, {
  /**
   * @cfg {String} printStrategy window or iframe
   */
  printStrategy: 'iframe',
  
  /**
   * Prints the component
   * @param {Ext.Component} component The component to print
   */
  print: function(component) {
    return this[this.printStrategy + 'Print'](component);
  },
  
  /**
   * Prints the component using the new window strategy
   * @param {Ext.Component} component The component to print
   */
  windowPrint: function(component) {
    var name = component && component.getXType
             ? String.format("print_{0}_{1}", component.getXType(), component.id.replace(/-/g, '_'))
             : "print";
             
    var win = window.open('', name);
    
    win.document.write(this.generateHTML(component));
    win.document.close();
    
    // gecko looses its document after document.close(). but fortunally waits with printing till css is loaded itself
    if (Ext.isGecko) {
        win.print();
        win.close();
        return;
    }
    
    this.doPrintOnStylesheetLoad.defer(10, this, [win]);
  },
  
  /**
   * Prints the component using the hidden iframe strategy
   * @param {Ext.Component} component The component to print
   */
  iframePrint: function(component) {
    var id = Ext.id(),
    doc = document,
    frame = doc.createElement('iframe');
        
    Ext.fly(frame).set({
      id: id,
      name: id,
      style: {
        position: 'absolute',
        width: '210mm',
        height: '297mm',
        top: '-10000px', 
        left: '-10000px'
      }
    });
    
    doc.body.appendChild(frame);

    Ext.fly(frame).set({
      src : Ext.SSL_SECURE_URL
    });

    var doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;
        
    doc.open();
    doc.write(this.generateHTML(component));
    doc.close();
    
    this.doPrintOnStylesheetLoad.defer(10, this, [frame.contentWindow]);
//    frame.contentWindow.focus();
//    frame.contentWindow.print();
//    
//    // NOTE: remving frame crashes chrome
//    setTimeout(function(){Ext.removeNode(frame);}, 100);
  },
  
  /**
   * check if style is loaded and do print afterwards
   * 
   * @param {window} win
   */
  doPrintOnStylesheetLoad: function(win) {
    var el = win.document.getElementById('csscheck'),
        comp = el.currentStyle || getComputedStyle(el, null);
    if (comp.display !== "none") {
      this.doPrintOnStylesheetLoad.defer(10, this, [win]);
      return;
    }
    win.print();
    win.close();
  },
  
  /**
   * Generates the HTML Markup which wraps whatever this.generateBody produces
   * @param {Ext.Component} component The component to generate HTML for
   * @return {String} An HTML fragment to be placed inside the print window
   */
  generateHTML: function(component) {
    return new Ext.XTemplate(
      '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
      '<html>',
        '<head>',
          '<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />',
          '<link href="' + this.stylesheetPath + '?' + new Date().getTime() + '" rel="stylesheet" type="text/css" media="screen,print" />',
          '<title>' + this.getTitle(component) + '</title>',
        '</head>',
        '<body>',
          '<div id="csscheck"></div>',
          this.generateBody(component),
        '</body>',
      '</html>'
    ).apply(this.prepareData(component));
  },
  
  /**
   * Returns the HTML that will be placed into the print window. This should produce HTML to go inside the
   * <body> element only, as <head> is generated in the print function
   * @param {Ext.Component} component The component to render
   * @return {String} The HTML fragment to place inside the print window's <body> element
   */
  generateBody: Ext.emptyFn,
  
  /**
   * Prepares data suitable for use in an XTemplate from the component 
   * @param {Ext.Component} component The component to acquire data from
   * @return {Array} An empty array (override this to prepare your own data)
   */
  prepareData: function(component) {
    return component;
  },
  
  /**
   * Returns the title to give to the print window
   * @param {Ext.Component} component The component to be printed
   * @return {String} The window title
   */
  getTitle: function(component) {
    return typeof component.getTitle == 'function' ? component.getTitle() : (component.title || "Printing");
  },
  
  /**
   * @property stylesheetPath
   * @type String
   * The path at which the print stylesheet can be found (defaults to 'stylesheets/print.css')
   */
  stylesheetPath: 'stylesheets/print.css'
});
/**
 * @class Ext.ux.Printer.GridPanelRenderer
 * @extends Ext.ux.Printer.BaseRenderer
 * @author Ed Spencer
 * Helper class to easily print the contents of a grid. Will open a new window with a table where the first row
 * contains the headings from your column model, and with a row for each item in your grid's store. When formatted
 * with appropriate CSS it should look very similar to a default grid. If renderers are specified in your column
 * model, they will be used in creating the table. Override headerTpl and bodyTpl to change how the markup is generated
 */
Ext.ux.Printer.GridPanelRenderer = Ext.extend(Ext.ux.Printer.BaseRenderer, {
  
  /**
   * Generates the body HTML for the grid
   * @param {Ext.grid.GridPanel} grid The grid to print
   */
  generateBody: function(grid) {
    var columns = this.getColumns(grid);
    
    //use the headerTpl and bodyTpl XTemplates to create the main XTemplate below
    var headings = this.headerTpl.apply(columns);
    var body     = this.bodyTpl.apply(columns);
    
    return String.format('<table>{0}<tpl for=".">{1}</tpl></table>', headings, body);
  },
  
  /**
   * Prepares data from the grid for use in the XTemplate
   * @param {Ext.grid.GridPanel} grid The grid panel
   * @return {Array} Data suitable for use in the XTemplate
   */
  prepareData: function(grid) {
    //We generate an XTemplate here by using 2 intermediary XTemplates - one to create the header,
    //the other to create the body (see the escaped {} below)
    var columns = this.getColumns(grid);
  
    //build a useable array of store data for the XTemplate
    var data = [];
    grid.store.data.each(function(item) {
      var convertedData = {};
      
      //apply renderers from column model
      Ext.iterate(item.data, function(key, value) {
        Ext.each(columns, function(column) {
          if (column.dataIndex == key) {
            convertedData[key] = column.renderer ? column.renderer(value, null, item) : value;
            convertedData[key] = convertedData[key] || '';
            return false;
          }
        }, this);
      });
    
      data.push(convertedData);
    });
    
    return data;
  },
  
  /**
   * Returns the array of columns from a grid
   * @param {Ext.grid.GridPanel} grid The grid to get columns from
   * @return {Array} The array of grid columns
   */
  getColumns: function(grid) {
    var columns = [];
    
      Ext.each(grid.getColumnModel().config, function(col) {
        if (col.hidden != true) columns.push(col);
      }, this);
      
      return columns;
  },
  
  /**
   * @property headerTpl
   * @type Ext.XTemplate
   * The XTemplate used to create the headings row. By default this just uses <th> elements, override to provide your own
   */
  headerTpl:  new Ext.XTemplate(
    '<tr><thead>',
      '<tpl for=".">',
        '<th>{header}</th>',
      '</tpl>',
    '</thead></tr>'
  ),
 
   /**
    * @property bodyTpl
    * @type Ext.XTemplate
    * The XTemplate used to create each row. This is used inside the 'print' function to build another XTemplate, to which the data
    * are then applied (see the escaped dataIndex attribute here - this ends up as "{dataIndex}")
    */
  bodyTpl:  new Ext.XTemplate(
    '<tr>',
      '<tpl for=".">',
        '<td>\{{dataIndex}\}</td>',
      '</tpl>',
    '</tr>'
  )
});

Ext.ux.Printer.registerRenderer('grid', Ext.ux.Printer.GridPanelRenderer);
/**
 * @class Ext.ux.Printer.ColumnTreeRenderer
 * @extends Ext.ux.Printer.BaseRenderer
 * @author Ed Spencer
 * Helper class to easily print the contents of a column tree
 */
Ext.ux.Printer.ColumnTreeRenderer = Ext.extend(Ext.ux.Printer.BaseRenderer, {

  /**
   * Generates the body HTML for the tree
   * @param {Ext.tree.ColumnTree} tree The tree to print
   */
  generateBody: function(tree) {
    var columns = this.getColumns(tree);
    
    //use the headerTpl and bodyTpl XTemplates to create the main XTemplate below
    var headings = this.headerTpl.apply(columns);
    var body     = this.bodyTpl.apply(columns);
    
    return String.format('<table>{0}<tpl for=".">{1}</tpl></table>', headings, body);
  },
    
  /**
   * Returns the array of columns from a tree
   * @param {Ext.tree.ColumnTree} tree The tree to get columns from
   * @return {Array} The array of tree columns
   */
  getColumns: function(tree) {
    return tree.columns;
  },
  
  /**
   * Descends down the tree from the root, creating an array of data suitable for use in an XTemplate
   * @param {Ext.tree.ColumnTree} tree The column tree
   * @return {Array} Data suitable for use in the body XTemplate
   */
  prepareData: function(tree) {
    var root = tree.root,
        data = [],
        cols = this.getColumns(tree),
        padding = this.indentPadding;
        
    var f = function(node) {
      if (node.hidden === true || node.isHiddenRoot() === true) return;
      
      var row = Ext.apply({depth: node.getDepth() * padding}, node.attributes);
      
      Ext.iterate(row, function(key, value) {
        Ext.each(cols, function(column) {
          if (column.dataIndex == key) {
            row[key] = column.renderer ? column.renderer(value) : value;
          }
        }, this);
      });
      
      //the property used in the first column is renamed to 'text' in node.attributes, so reassign it here
      row[this.getColumns(tree)[0].dataIndex] = node.attributes.text;
      
      data.push(row);
    };
    
    root.cascade(f, this);
    
    return data;
  },
  
  /**
   * @property indentPadding
   * @type Number
   * Number of pixels to indent node by. This is multiplied by the node depth, so a node with node.getDepth() == 3 will
   * be padded by 45 (or 3x your custom indentPadding)
   */
  indentPadding: 15,
  
  /**
   * @property headerTpl
   * @type Ext.XTemplate
   * The XTemplate used to create the headings row. By default this just uses <th> elements, override to provide your own
   */
  headerTpl:  new Ext.XTemplate(
    '<tr>',
      '<tpl for=".">',
        '<th width="{width}">{header}</th>',
      '</tpl>',
    '</tr>'
  ),
 
  /**
   * @property bodyTpl
   * @type Ext.XTemplate
   * The XTemplate used to create each row. This is used inside the 'print' function to build another XTemplate, to which the data
   * are then applied (see the escaped dataIndex attribute here - this ends up as "{dataIndex}")
   */
  bodyTpl:  new Ext.XTemplate(
    '<tr>',
      '<tpl for=".">',
        '<td style="padding-left: {[xindex == 1 ? "\\{depth\\}" : "0"]}px">\{{dataIndex}\}</td>',
      '</tpl>',
    '</tr>'
  )
});

Ext.ux.Printer.registerRenderer('columntree', Ext.ux.Printer.ColumnTreeRenderer);
/*
 * Tine 2.0
 * 
 * @package     Tine
 * @subpackage  Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Tine.Tinebase', 'Tine.Tinebase.data');

Tine.Tinebase.data.Record = function(data, id) {
    if (id || id === 0) {
        this.id = id;
    } else if (data[this.idProperty]) {
        this.id = data[this.idProperty];
    } else {
        this.id = ++Ext.data.Record.AUTO_ID;
    }
    this.data = data;
};

/**
 * @namespace Tine.Tinebase.data
 * @class     Tine.Tinebase.data.Record
 * @extends   Ext.data.Record
 * 
 * Baseclass of Tine 2.0 models
 */
Ext.extend(Tine.Tinebase.data.Record, Ext.data.Record, {
    /**
     * @cfg {String} appName
     * internal/untranslated app name (required)
     */
    appName: null,
    /**
     * @cfg {String} modelName
     * name of the model/record  (required)
     */
    modelName: null,
    /**
     * @cfg {String} idProperty
     * property of the id of the record
     */
    idProperty: 'id',
    /**
     * @cfg {String} titleProperty
     * property of the title attibute, used in generic getTitle function  (required)
     */
    titleProperty: null,
    /**
     * @cfg {String} recordName
     * untranslated record/item name
     */
    recordName: 'record',
    /**
     * @cfg {String} recordName
     * untranslated records/items (plural) name
     */
    recordsName: 'records',
    /**
     * @cfg {String} containerProperty
     * name of the container property
     */
    containerProperty: 'container_id',
    /**
     * @cfg {String} containerName
     * untranslated container name
     */
    containerName: 'container',
    /**
     * @cfg {string} containerName
     * untranslated name of container (plural)
     */
    containersName: 'containers',
    /**
     * default filter
     * @type {string}
     */
    defaultFilter: null,
    
    cfExp: /^#(.+)/,
    
    /**
     * Get the value of the {@link Ext.data.Field#name named field}.
     * @param {String} name The {@link Ext.data.Field#name name of the field} to get the value of.
     * @return {Object} The value of the field.
     */
    get: function(name) {
        
        if (cfName = String(name).match(this.cfExp)) {
            return this.data.customfields ? this.data.customfields[cfName[1]] : null;
        }
        
        return this.data[name];
    },
    
    /**
     * Set the value of the {@link Ext.data.Field#name named field}.
     * @param {String} name The {@link Ext.data.Field#name name of the field} to get the value of.
     * @return {Object} The value of the field.
     */
    set : function(name, value) {
        var encode = Ext.isPrimitive(value) ? String : Ext.encode,
            current = this.get(name);
            
        if (encode(current) == encode(value)) {
            return;
        }
        this.dirty = true;
        if (!this.modified) {
            this.modified = {};
        }
        if (this.modified[name] === undefined) {
            this.modified[name] = current;
        }
        
        if (cfName = String(name).match(this.cfExp)) {
            this.data.customfields = this.data.customfields || {};
            this.data.customfields[cfName[1]] = value;
        } else {
            this.data[name] = value;
        }
        
        if (!this.editing) {
            this.afterEdit();
        }
    },
    
    /**
     * returns title of this record
     * 
     * @return {String}
     */
    getTitle: function() {
        var s = this.titleProperty ? this.titleProperty.split('.') : [null];
        return (s.length > 0 && this.get(s[0]) && this.get(s[0])[s[1]]) ? this.get(s[0])[s[1]] : s[0] ? this.get(this.titleProperty) : '';
    },
    /**
     * returns the id of the record
     */
    getId: function() {
        return this.get(this.idProperty ? this.idProperty : 'id');
    },
    
    /**
     * converts data to String
     * 
     * @return {String}
     */
    toString: function() {
        return Ext.encode(this.data);
    }
});

/**
 * Generate a constructor for a specific Record layout.
 * 
 * @param {Array} def see {@link Ext.data.Record#create}
 * @param {Object} meta information see {@link Tine.Tinebase.data.Record}
 * 
 * <br>usage:<br>
<b>IMPORTANT: the ngettext comments are required for the translation system!</b>
<pre><code>
var TopicRecord = Tine.Tinebase.data.Record.create([
    {name: 'summary', mapping: 'topic_title'},
    {name: 'details', mapping: 'username'}
], {
    appName: 'Tasks',
    modelName: 'Task',
    idProperty: 'id',
    titleProperty: 'summary',
    // ngettext('Task', 'Tasks', n);
    recordName: 'Task',
    recordsName: 'Tasks',
    containerProperty: 'container_id',
    // ngettext('to do list', 'to do lists', n);
    containerName: 'to do list',
    containesrName: 'to do lists'
});
</code></pre>
 * @static
 */
Tine.Tinebase.data.Record.create = function(o, meta) {
    var f = Ext.extend(Tine.Tinebase.data.Record, {});
    var p = f.prototype;
    Ext.apply(p, meta);
    p.fields = new Ext.util.MixedCollection(false, function(field) {
        return field.name;
    });
    for(var i = 0, len = o.length; i < len; i++) {
        p.fields.add(new Ext.data.Field(o[i]));
    }
    f.getField = function(name) {
        return p.fields.get(name);
    };
    f.getMeta = function(name) {
        var value = null;
        switch(name) {
            case ('phpClassName'):
                value = p.appName + '_Model_' + p.modelName;
                break;
            default:
                value = p[name];
        }
        return value;
    };
    f.getDefaultData = function() {
        return {};
    };
    f.getFieldDefinitions = function() {
        return p.fields.items;
    };
    f.getFieldNames = function() {
        if (! p.fieldsarray) {
            var arr = p.fieldsarray = [];
            Ext.each(p.fields.items, function(item) {arr.push(item.name);});
        }
        return p.fieldsarray;
    };
    f.hasField = function(n) {
        return p.fields.indexOfKey(n) >= 0;
    };
    f.getRecordName = function() {
        var app = i18n = Tine.Tinebase.appMgr.get(p.appName),
            i18n = app && app.i18n ? app.i18n :Tine.Tinebase.translation;
            
        return i18n.n_(p.recordName, p.recordsName, 1);
    };
    f.getRecordsName = function() {
        var app = i18n = Tine.Tinebase.appMgr.get(p.appName),
            i18n = app && app.i18n ? app.i18n :Tine.Tinebase.translation;
            
        return i18n.n_(p.recordName, p.recordsName, 50);
    };
    f.getContainerName = function() {
        var app = i18n = Tine.Tinebase.appMgr.get(p.appName),
            i18n = app && app.i18n ? app.i18n :Tine.Tinebase.translation;
            
        return i18n.n_(p.containerName, p.containersName, 1);
    };
    f.getContainersName = function() {
        var app = i18n = Tine.Tinebase.appMgr.get(p.appName),
            i18n = app && app.i18n ? app.i18n :Tine.Tinebase.translation;
            
        return i18n.n_(p.containerName, p.containersName, 50);
    };
    f.getAppName = function() {
        return Tine.Tinebase.appMgr.get(p.appName).i18n._(p.appName);
    };
    /**
     * returns the php class name of the record itself or by the application(name) and model(name)
     * @param {mixed} app       the application instance or the application name or the record class
     * @param {mixed} model     the model name
     * @return {String} php class name
     */
    f.getPhpClassName = function(app, model) {
        // without arguments the php class name of the this is returned
        if (!app && !model) {
            return f.getMeta('phpClassName');
        }
        // if var app is a record class, the getMeta method is called
        if (Ext.isFunction(app.getMeta)) {
            return app.getMeta('phpClassName');
        }

        var appName = (Ext.isObject(app) && app.hasOwnProperty('name')) ? app.name : app;
        return appName + '_Model_' + model;
    };
    
    // sanitize containerProperty label
    var containerProperty = f.getMeta('containerProperty');
    if (containerProperty) {
        var field = p.fields.get(containerProperty);
        if (field) {
            field.label = p.containerName;
        }
    }
    Tine.Tinebase.data.RecordMgr.add(f);
    return f;
};

Tine.Tinebase.data.Record.generateUID = function(length) {
    var uid = String(CryptoJS.SHA1(String(Math.floor(Math.random()*Math.pow(10, 16))) + String(new Date().getMilliseconds())));
    
    if (length) {
        uid = uid.substring(0, length);
    }
    
    return uid;
};
Tine.Tinebase.data.RecordManager = Ext.extend(Ext.util.MixedCollection, {
    add: function(record) {
        if (! Ext.isFunction(record.getMeta)) {
            throw new Ext.Error('only records of type Tinebase.data.Record could be added');
        }
        var appName = record.getMeta('appName'),
            modelName = record.getMeta('modelName');
            
        if (! appName && modelName) {
            throw new Ext.Error('appName and modelName must be in the metadatas');
        }
        
//        console.log('register model "' + appName + '.' + modelName + '"');
        Tine.Tinebase.data.RecordManager.superclass.add.call(this, appName + '.' + modelName, record);
    },
    
    get: function(appName, modelName) {
        if (Ext.isFunction(appName.getMeta)) {
            return appName;
        }
        if (! modelName && appName.modelName) {
            modelName = appName.modelName;
        }
        if (appName.appName) {
            appName = appName.appName;
        }
            
        if (! Ext.isString(appName)) {
            throw new Ext.Error('appName must be a string');
        }
        
        Ext.each([appName, modelName], function(what) {
            if (! Ext.isString(what)) return;
            var parts = what.split(/(?:_Model_)|(?:\.)/);
            if (parts.length > 1) {
                appName = parts[0];
                modelName = parts[1];
            }
        });
        
        return Tine.Tinebase.data.RecordManager.superclass.get.call(this, appName + '.' + modelName);
    }
});
Tine.Tinebase.data.RecordMgr = new Tine.Tinebase.data.RecordManager(true);/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
/**
 * @namespace Tine.Tinebase.data
 * @class     Tine.Tinebase.data.RecordStore
 * @extends   Ext.data.Store
 * @author    Cornelius Weiss <c.weiss@metaways.de>
 * 
 * Small helper class to create an {@link Ext.data.Store} configured with an
 * {@link Ext.data.DirectProxy} and {@link Ext.data.JsonReader} to make interacting
 * with an {@link Ext.Direct} Server-side {@link Ext.ux.direct.ZendFrameworkProvider} easier.
 * 
 * @xtype     tinerecordstore
 * @constructor
 * @param {Object} config config object
 */
Tine.Tinebase.data.RecordStore = function(c){
    /**
     * @cfg {bool} readOnly
     * set to true to skip creation of a writer
     */
    /**
     * @cfg {Tine.Tinebase.data.Record} recordClass
     * model of this proxy (required)
     */
    
    c.batchTransactions = false;
    
    c.appName = c.recordClass.getMeta('appName');
    c.modelName = c.recordClass.getMeta('modelName');
    
    if (typeof(c.reader) == 'undefined') {
        c.reader = new Ext.data.JsonReader({
            root: c.root || 'results',
            totalProperty: c.totalProperty || 'totalcount',
            id: c.recordClass.getMeta('idProperty')
        }, c.recordClass);
    }
    
    if (typeof(c.writer) == 'undefined' && ! c.readOnly) {
        c.writer = new Ext.data.JsonWriter({
        });
    }
    
    if (typeof(c.proxy) == 'undefined') {
        c.proxy = new Tine.Tinebase.data.RecordProxy({
            recordClass: c.recordClass
        });
    }
    
    Tine.Tinebase.data.RecordStore.superclass.constructor.call(this, c);
};
Ext.extend(Tine.Tinebase.data.RecordStore, Ext.data.Store, {});
Ext.reg('tinerecordstore', Tine.Tinebase.data.RecordStore);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.Tinebase.data');

/**
 * @namespace   Tine.Tinebase.data
 * @class       Tine.Tinebase.data.RecordProxy
 * @extends     Ext.data.DataProxy
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * 
 * Generic record proxy for an model/datatype of an application
 * 
 * @constructor
 * @param {Object} config Config Object
 */
Tine.Tinebase.data.RecordProxy = function(c) {
    // we support all actions
    c.api = {read: true, create: true, update: true, destroy: true};
    
    Tine.Tinebase.data.RecordProxy.superclass.constructor.call(this, c);
    
    Ext.apply(this, c);
    this.appName    = this.appName    ? this.appName    : c.recordClass.getMeta('appName');
    this.modelName  = this.modelName  ? this.modelName  : c.recordClass.getMeta('modelName');
    this.idProperty = this.idProperty ? this.idProperty : c.recordClass.getMeta('idProperty');
    
    /* NOTE: in ExtJS records always are part of a store. The store is
             the only instance which triggers read/write actions.
             
             In our edit dialoges in contrast we work with single (store-less) 
             records and the handlers itselve trigger the read/write actions.
             This might change in future, but as long as we do so, we also need
             the reader/writer here.
     */
    this.jsonReader = new Ext.data.JsonReader({
        id: this.idProperty,
        root: 'results',
        totalProperty: 'totalcount'
    }, this.recordClass);
};

Ext.extend(Tine.Tinebase.data.RecordProxy, Ext.data.DataProxy, {
    /**
     * @cfg {Ext.data.Record} recordClass
     * record definition class  (required)
     */
    recordClass: null,
    
    /**
     * @type String 
     * @property appName
     * internal/untranslated app name
     */
    appName: null,
    
    /**
     * @type String 
     * @property idProperty
     * property of the id of the record
     */
    idProperty: null,
    
    /**
     * @type String 
     * @property modelName
     * name of the model/record 
     */
    modelName: null,
    
    /**
     * id of last transaction
     * @property transId
     */
    transId: null,
    
    /**
     * Aborts any outstanding request.
     * @param {Number} transactionId (Optional) defaults to the last transaction
     */
    abort : function(transactionId) {
        return Ext.Ajax.abort(transactionId);
    },
    
    /**
     * Determine whether this object has a request outstanding.
     * @param {Number} transactionId (Optional) defaults to the last transaction
     * @return {Boolean} True if there is an outstanding request.
     */
    isLoading : function(transId){
        return Ext.Ajax.isLoading(transId);
    },
        
    /**
     * loads a single 'full featured' record
     * 
     * @param   {Ext.data.Record} record
     * @param   {Object} options
     * @return  {Number} Ext.Ajax transaction id
     * @success {Ext.data.Record}
     */
    loadRecord: function(record, options) {
        options = options || {};
        options.params = options.params || {};
        options.beforeSuccess = function(response) {
            return [this.recordReader(response)];
        };
        
        var p = options.params;
        p.method = this.appName + '.get' + this.modelName;
        p.id = record.get(this.idProperty);
        
        return this.doXHTTPRequest(options);
    },
    
    /**
     * searches all (lightweight) records matching filter
     * 
     * @param   {Object} filter
     * @param   {Object} paging
     * @param   {Object} options
     * @return  {Number} Ext.Ajax transaction id
     * @success {Object} root:[records], totalcount: number
     */
    searchRecords: function(filter, paging, options) {
        options = options || {};
        options.params = options.params || {};
        
        var p = options.params;
        
        p.method = this.appName + '.search' + this.modelName + 's';
        p.filter = (filter) ? filter : [];
        p.paging = paging;
        
        options.beforeSuccess = function(response) {
            return [this.jsonReader.read(response)];
        };
        
        // increase timeout as this can take a longer (1 minute)
        options.timeout = 60000;
        
        return this.doXHTTPRequest(options);
    },
    
    /**
     * saves a single record
     * 
     * @param   {Ext.data.Record} record
     * @param   {Object} options
     * @return  {Number} Ext.Ajax transaction id
     * @success {Ext.data.Record}
     */
    saveRecord: function(record, options, additionalArguments) {
        options = options || {};
        options.params = options.params || {};
        options.beforeSuccess = function(response) {
            return [this.recordReader(response)];
        };
        
        var p = options.params;
        p.method = this.appName + '.save' + this.modelName;
        p.recordData = record.data;
        if (additionalArguments) {
            Ext.apply(p, additionalArguments);
        }
        
        // increase timeout as this can take a longer (5 minutes)
        options.timeout = 300000;
        
        return this.doXHTTPRequest(options);
    },
    
    /**
     * deletes multiple records identified by their ids
     * 
     * @param   {Array} records Array of records or ids
     * @param   {Object} options
     * @return  {Number} Ext.Ajax transaction id
     * @success 
     */
    deleteRecords: function(records, options) {
        options = options || {};
        options.params = options.params || {};
        options.params.method = this.appName + '.delete' + this.modelName + 's';
        options.params.ids = this.getRecordIds(records);

        // increase timeout as this can take a long time (2 mins)
        options.timeout = 120000;
        
        return this.doXHTTPRequest(options);
    },

    /**
     * deletes multiple records identified by a filter
     * 
     * @param   {Object} filter
     * @param   {Object} options
     * @return  {Number} Ext.Ajax transaction id
     * @success 
     */
    deleteRecordsByFilter: function(filter, options) {
        options = options || {};
        options.params = options.params || {};
        options.params.method = this.appName + '.delete' + this.modelName + 'sByFilter';
        options.params.filter = filter;
        
        // increase timeout as this can take a long time (5 mins)
        options.timeout = 300000;
        
        return this.doXHTTPRequest(options);
    },
    
    /**
     * updates multiple records with the same data
     * 
     * @param   {Array} filter filter data
     * @param   {Object} updates
     * @return  {Number} Ext.Ajax transaction id
     * @success
     */
    updateRecords: function(filter, updates, options) {
        options = options || {};
        options.params = options.params || {};
        options.params.method = this.appName + '.updateMultiple' + this.modelName + 's';
        options.params.filter = filter;
        options.params.values = updates;
        
        options.beforeSuccess = function(response) {
            return [Ext.util.JSON.decode(response.responseText)];
        };
        
        return this.doXHTTPRequest(options);
    },
    
    /**
     * returns an array of ids
     * 
     * @private 
     * @param  {Ext.data.Record|Array}
     * @return {Array} of ids
     */
    getRecordIds : function(records) {
        var ids = [];
        
        if (! Ext.isArray(records)) {
            records = [records];
        }
        
        for (var i=0; i<records.length; i++) {
            ids.push(records[i].id ? records[i].id : records.id);
        }
        
        return ids;
    },
    
    /**
     * required method for Ext.data.Proxy, used by store
     */
    load : function(params, reader, callback, scope, arg){
        if(this.fireEvent("beforeload", this, params) !== false){
            
            // move paging to own object
            var paging = {
                sort:  params.sort,
                dir:   params.dir,
                start: params.start,
                limit: params.limit
            };
            
            this.searchRecords(params.filter, paging, {
                scope: this,
                success: function(records) {
                    callback.call(scope||this, records, arg, true);
                },
                failure: function(exception) {
                    //@todo compute traditional options/request/response here -> dont' waste time ondepricated stuff
                    //this.fireEvent('exception', this, 'remote', 'read', options, response, arg);
                    this.fireEvent('loadexception', this, 'remote',  exception, arg);
                }
            });
            
        } else {
            callback.call(scope||this, null, arg, false);
        }
    },
    
    /**
     * do the request
     * 
     * @param {} action
     * @param {} rs
     * @param {} params
     * @param {} reader
     * @param {} callback
     * @param {} scope
     * @param {} options
     */
    doRequest : function(action, rs, params, reader, callback, scope, options) {
        var opts = {
            params: params, 
            callback: callback,
            scope: scope
        };
        
        switch (action) {
            case Ext.data.Api.actions.create:
                this.saveRecord(rs, opts);
                break;
            case Ext.data.Api.actions.read:
                this.load(params, reader, callback, scope, options);
                break;
            case Ext.data.Api.actions.update:
                this.saveRecord(rs, opts);
                break;
            case Ext.data.Api.actions.destroy:
                this.deleteRecords(rs, opts);
                break;
        }
    },

    
    /**
     * returns reader
     * 
     * @return {Ext.data.DataReader}
     */
    getReader: function() {
        return this.jsonReader;
    },
    
    /**
     * reads a single 'fully featured' record from json data
     * 
     * NOTE: You might want to overwride this method if you have a more complex record
     * 
     * @param  XHR response
     * @return {Ext.data.Record}
     */
    recordReader: function(response) {
        var recordData = Ext.util.JSON.decode('{"results": [' + response.responseText + ']}');
        var data = this.jsonReader.readRecords(recordData);
        
        var record = data.records[0];
        var recordId = record.get(record.idProperty);
        
        record.id = recordId ? recordId : 0;
        
        return record;
    },
    
    /**
     * is request still loading?
     * 
     * @param  {Number} Ext.Ajax transaction id
     * @return {Bool}
     */
    isLoading: function(tid) {
        return Ext.Ajax.isLoading(tid);
    },
    
    /**
     * performs an Ajax request
     */
    doXHTTPRequest: function(options) {
        var requestOptions = {
            scope: this,
            params: options.params,
            callback: options.callback,
            success: function(response) {
                if (typeof options.success == 'function') {
                    var args = [];
                    if (typeof options.beforeSuccess == 'function') {
                        args = options.beforeSuccess.call(this, response);
                    }
                    
                    options.success.apply(options.scope, args);
                }
            },
            // note incoming options are implicitly json-rpc converted
            failure: function (response, jsonrpcoptions) {
                var responseData = Ext.decode(response.responseText),
                    exception = responseData.data ? responseData.data : responseData;
                    
                exception.request = jsonrpcoptions.jsonData;
                exception.response = response.responseText;
                
                if (typeof options.failure == 'function') {
                    var args = [];
                    if (typeof options.beforeFailure == 'function') {
                        args = options.beforeFailure.call(this, response);
                    } else {
                        args = [exception];
                    }
                    Tine.log.debug('Tine.Tinebase.data.RecordProxy::doXHTTPRequest -> call failure fn');
                    options.failure.apply(options.scope, args);
                }
                // requests with callback need to define their own exception handling
                else if (! options.callback) {
                    Tine.log.debug('Tine.Tinebase.data.RecordProxy::doXHTTPRequest -> handle exception');
                    this.handleRequestException(exception);
                } else {
                    Tine.log.debug('Tine.Tinebase.data.RecordProxy::doXHTTPRequest -> call callback fn');
                }
            }
        };
        
        if (options.timeout) {
            requestOptions.timeout = options.timeout;
        }
        
        this.transId = Ext.Ajax.request(requestOptions);
        
        return this.transId;
    },
    
    /**
     * default exception handler
     * 
     * @param {Object} exception
     */
    handleRequestException: function(exception) {
        Tine.Tinebase.ExceptionHandler.handleRequestException(exception);
    }
});
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @subpackage  data
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.Tinebase.data');

/**
 * Abstract Backdend for an model/datatype of an application
 * 
 * @namespace   Tine.Tinebase.data
 * @class       Tine.Tinebase.data.AbstractBackend
 * @extends     Ext.data.DataProxy
 * @constructor 
 */
Tine.Tinebase.data.AbstractBackend = function(config) {
    Tine.Tinebase.data.AbstractBackend.superclass.constructor.call(this);
    Ext.apply(this, config);
};

Ext.extend(Tine.Tinebase.data.AbstractBackend, Ext.data.DataProxy, {
    /**
     * @cfg {String} appName
     * internal/untranslated app name (required)
     */
    appName: null,
    /**
     * @cfg {String} modelName
     * name of the model/record  (required)
     */
    modelName: null,
    /**
     * @cfg {Ext.data.Record} recordClass
     * record definition class  (required)
     */
    recordClass: null,
    /**
     * @cfg {String} idProperty
     * property of the id of the record
     */
    idProperty: 'id',
    
    /**
     * loads a single 'full featured' record
     * 
     * @param   {Ext.data.Record} record
     * @param   {Object} options
     * @return  {Number} Ext.Ajax transaction id
     * @success {Ext.data.Record}
     */
    loadRecord: function(record, options) {
        options.success.defer(1000, options.scope, record);
    },
    
    /**
     * searches all (lightweight) records matching filter
     * 
     * @param   {Object} filter
     * @param   {Object} paging
     * @param   {Object} options
     * @return  {Number} Ext.Ajax transaction id
     * @success {Object} root:[recrods], totalcount: number
     */
    searchRecords: function(filter, paging, options) {
        options.success.defer(1000, options.scope, [{records: [], success: 1, totalRecords: 0}]);
    },
    
    /**
     * saves a single record
     * 
     * @param   {Ext.data.Record} record
     * @param   {Object} options
     * @return  {Number} Ext.Ajax transaction id
     * @success {Ext.data.Record}
     */
    saveRecord: function(record, options) {
        record.commit(true);
        options.success.defer(1000, options.scope, [record]);
    },
    
    /**
     * deletes multiple records identified by their ids
     * 
     * @param   {Array} records Array of records or ids
     * @param   {Object} options
     * @return  {Number} Ext.Ajax transaction id
     * @success 
     */
    deleteRecords: function(records, options) {
        options.success.defer(1000, options.scope);
    },
    
    /**
     * updates multiple records with the same data
     * 
     * @param   {Array} filter filter data
     * @param   {Object} updates
     * @return  {Number} Ext.Ajax transaction id
     * @success
     */
    updateRecords: function(filter, updates, options) {
        options.success.defer(1000, options.scope, []);
    },
    
    /**
     * returns an array of ids
     * 
     * @private 
     * @param  {Ext.data.Record|Array}
     * @return {Array} of ids
     */
    getRecordIds : function(records) {
        var ids = [];
        
        if (! Ext.isArray(records)) {
            records = [records];
        }
        
        for (var i=0; i<records.length; i++) {
            ids.push(records[i].id ? records[i].id : records.id);
        }
        
        return ids;
    },
    
    /**
     * reqired method for Ext.data.Proxy, used by store
     * @todo read the specs and implement success/fail handling
     * @todo move reqest to searchRecord
     */
    load : function(params, reader, callback, scope, arg){
        if(this.fireEvent("beforeload", this, params) !== false){
            
            // move paging to own object
            var paging = {
                sort:  params.sort,
                dir:   params.dir,
                start: params.start,
                limit: params.limit
            };
            
            this.searchRecords(params.filter, paging, {
                scope: this,
                success: function(records) {
                    callback.call(scope||this, records, arg, true);
                }
            });
            
        } else {
            callback.call(scope||this, null, arg, false);
        }
    },
    
    /**
     * returns reader
     * 
     * @return {Ext.data.DataReader}
     */
    getReader: function() {
        
    },
    
    
    /**
     * is request still loading?
     * 
     * @param  {Number} Ext.Ajax transaction id
     * @return {Bool}
     */
    isLoading: function(tid) {
        
    }
    
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.Tinebase');

/**
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.StateProvider
 * @extends     Ext.state.Provider
 */
Tine.Tinebase.StateProvider = function(config) {
    Tine.Tinebase.StateProvider.superclass.constructor.call(this);
    Ext.apply(this, config);
    this.state = this.readRegistry();
};

Ext.extend(Tine.Tinebase.StateProvider, Ext.state.Provider, {
    
    // private
    clear: function(name){
        // persistent clear
        Tine.Tinebase.clearState(name);
        
        // store back in registry (as needed for popups)
        var stateInfo = Tine.Tinebase.registry.get('stateInfo');
        delete stateInfo[name];
        
        Tine.Tinebase.StateProvider.superclass.clear.call(this, name);
    },
    
    // private
    readRegistry: function() {
        var states = {};
        var stateInfo = Tine.Tinebase.registry.get('stateInfo');
        for (var name in stateInfo) {
            states[name] = this.decodeValue(stateInfo[name]);
        };
        
        return states;
    },
    
    // private
    set: function(name, value) {
        if(typeof value == "undefined" || value === null){
            this.clear(name);
            return;
        }
        
        var encodedValue = this.encodeValue(value);
        // persistent save
        Tine.Tinebase.setState(name, encodedValue);
        
        // store back in registry (as needed for popups)
        var stateInfo = Tine.Tinebase.registry.get('stateInfo');
        stateInfo[name] = encodedValue;
        
        Tine.Tinebase.StateProvider.superclass.set.call(this, name, value);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine', 'Tine.Tinebase');

/**
 * @namespace Tine.Tinebase
 * @class Tine.Tinebase.ExceptionHandler
 * @singleton
 * 
 * IE NOTE: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnscrpt/html/WebErrors2.asp
 *    "A common problem that bites many developers occurs when their onerror handler is not 
 *     called because they have script debugging enabled for Internet Explorer."
 *
 * central class for exception handling
 */
Tine.Tinebase.ExceptionHandler = function() {
    
    /**
     * handle window errors
     * 
     * NOTE: this isn't working cross browser and reports generated by this
     *       are useless most times as we don't have the trace and user tend to 
     *       not describe the problem. Moreover, in most cases, js exceptions are 
     *       not a problem for the UI anyway.
     *       
     *       Therefore we don't show the error reporting dialog anymore for js
     *       exceptions, but let the exception bubble, so browser specific error
     *       analysis is possible
     */
    var onWindowError = function() {
        
        var error = getNormalisedError.apply(this, arguments);
        
        var traceHtml = '<table>';
        for (p in error) {
            if (error.hasOwnProperty(p)) {
                traceHtml += '<tr><td><b>' + p + '</b></td><td>' + error[p] + '</td></tr>'
            }
        }
        traceHtml += '</table>';
        
         // check for special cases we don't want to handle
        if (traceHtml.match(/versioncheck/)) {
            return true;
        }
        // we don't wanna know fancy FF3.5 crome bugs
        if (traceHtml.match(/chrome/)) {
            return true;
        }
        // don't show openlayers error (occurs if window with contact is closed too fast)
        // TODO better fix this error ...
        if (traceHtml.match(/OpenLayers\.js/) && traceHtml.match(/element is null/)) {
            return true;
        }
        
        // really bad thing: fix exists only in close source version
        // http://www.extjs.com/forum/showthread.php?t=76860
        if (traceHtml.match(/swf\.setDataProvider/)) {
            return true;
        }
        
        // let exception bubble to browser
        return false;
    };
    
    /**
     * @todo   make this working in safari
     * @return {string}
     */
    var getNormalisedError = function() {
        var error = {
            name       : 'unknown error',
            message    : 'unknown',
            code       : 'unknown',
            description: 'unknown',
            url        : 'unknown',
            line       : 'unknown'
        };
        
        // NOTE: Arguments is not always a real Array
        var args = [];
        for (var i=0; i<arguments.length; i++) {
            args[i] = arguments[i];
        }
        
        if (args[0] instanceof Error) { // Error object thrown in try...catch
            error.name        = args[0].name;
            error.message     = args[0].message;
            error.code        = args[0].number & 0xFFFF; //Apply binary arithmetic for IE number, firefox returns message string in element array element 0
            error.description = args[0].description;
            
        } else if ((args.length == 3) && (typeof(args[2]) == "number")) { // Check the signature for a match with an unhandled exception
            error.name    = 'catchable exception'
            error.message = args[0];
            error.url     = args[1];
            error.line    = args[2];
        } else {
            error.message     = "An unknown JS error has occured.";
            error.description = 'The following information may be useful:' + "\n";
            for (var x = 0; x < args.length; x++) {
                try {
                    error.description += (Ext.encode(args[x]) + "\n");
                } catch (e) {
                    error.description += 'Could not encode error args: ' + e + "\n";
                }
            }
        }
        return error;
    };
    
    /**
     * generic request exception handling
     * 
     * NOTE: status codes 9xx are reserved for applications and must not be handled here!
     * 
     * @param {Tine.Exception|Object} exception
     */
    var handleRequestException = function(exception, callback, callbackScope) {
         if (! exception.code && exception.responseText) {
            // we need to decode the exception first
            var response = Ext.util.JSON.decode(exception.responseText);
            exception = response.data;
        }
        
        var defaults = {
            buttons: Ext.Msg.OK,
            icon: Ext.MessageBox.WARNING,
            fn: callback,
            scope: callbackScope
        };
        
        Tine.log.debug('Tine.Tinebase.ExceptionHandler::handleRequestException -> Exception:');
        Tine.log.debug(exception);
    
        if (!callback) callback = Ext.emptyFn;
        if (!callbackScope) callbackScope = this;
        
        switch (exception.code) {
            // not authorised
            case 401:
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('Authorisation Required'), 
                    msg: _('Your session timed out. You need to login again.'),
                    fn: function() {
                        var redirect = (Tine.Tinebase.registry.get('redirectUrl'));
                        if (redirect && redirect != '') {
                            window.location = Tine.Tinebase.registry.get('redirectUrl');
                        } else {
                            window.location.reload();
                        }
                    }
                }));
                break;
            
            // insufficient rights
            case 403:
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('Insufficient Rights'), 
                    msg: _('Sorry, you are not permitted to perform this action'),
                    icon: Ext.MessageBox.ERROR
                }));
                break;
            
            // not found
            case 404:
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('Not Found'), 
                    msg: _('Sorry, your request could not be completed because the required data could not be found. In most cases this means that someone already deleted the data. Please refresh your current view.'),
                    icon: Ext.MessageBox.ERROR
                }));
                break;
            
            // concurrency conflict
            case 409:
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('Concurrent Updates'),
                    msg: _('Someone else saved this record while you where editing the data. You need to reload and make your changes again.')
                }));
                break;
            
            // Service Unavailable!
            // Use this error code for generic problems like misconfig we don't want to see bugreports for
            case 503:
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('Service Unavailable'), 
                    msg: _('The server is currently unable to handle the request due to a temporary overloading, maintenance or misconfiguration of the server. Please try again or contact your administrator.')
                }));
                break;
                
            // invalid record exception
            case 505:
                var message = exception.message ? '<br /><b>' + _('Server Message:') + '</b><br />' + exception.message : '';
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('Invalid Data'),
                    msg: _('Your input data is not valid. Please provide valid data.') + message
                }));
                break;
            // server communication loss
            case 510:
                // NOTE: when communication is lost, we can't create a nice ext window.
                // NOTE: - reloads/redirects cancel all open xhr requests from the browser side
                //       - we need some way to distinguish server/client connection losses
                //       - the extjs xhr abstraction has no such feature
                //       - so we defer the alert. In case of reload/redirect the deferd fn dosn't get executed
                //         if the new contet/html arrives before the defer time is over.
                //       - this might not always be the case due to network, service or session problems
                (function() {alert(_('Connection lost, please check your network!'))}).defer(1000);
                break;
                
            // transaction aborted / timeout
            case 520:
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('Timeout'), 
                    msg: _('Sorry, some timeout occured while processing your request. Please reload your browser, try again or contact your administrator.')
                }));
                
                break;
                
            // empty response
            case 540:
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('No Response'), 
                    msg: _('Sorry, the Server did not respond any data. Please reload your browser, try again or contact your administrator.')
                }));
                break;
            
            // memory exhausted
            case 550: 
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('Out of Resources'), 
                    msg: _('Sorry, the Server stated a "memory exhausted" condition. Please contact your administrator.')
                }));
                break;
                
            // generic error with message generated on the server
                
            case 600:
                Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _(exception.title), 
                    msg: _(exception.message)
                }));
                break;
                
            // user in no role
            case 610:
            Ext.MessageBox.show(Ext.apply(defaults, {
                    title: _('No Role Memberships'), 
                    msg: _('Your user account has no role memberships. Please contact your administrator.')
                }));
                break;
                
            // generic failure -> notify developers
            default:
                var windowHeight = 400;
                if (Ext.getBody().getHeight(true) * 0.7 < windowHeight) {
                    windowHeight = Ext.getBody().getHeight(true) * 0.7;
                }
                
                if (! Tine.Tinebase.exceptionDlg) {
                    Tine.Tinebase.exceptionDlg = new Tine.Tinebase.ExceptionDialog({
                        height: windowHeight,
                        exception: exception,
                        listeners: {
                            close: function() {
                                Tine.Tinebase.exceptionDlg = null;
                            }
                        }
                    });
                    Tine.Tinebase.exceptionDlg.show();
                }
                break;
        }
        
        if (Tine.Tinebase.registry && Tine.Tinebase.registry.get('config') && 
            Tine.Tinebase.registry.get('config').automaticBugreports && 
            Tine.Tinebase.registry.get('config').automaticBugreports.value && ! Tine.Tinebase.exceptionDlg
        ) {
            Tine.log.debug('Tine.Tinebase.ExceptionHandler::handleRequestException -> Activate non-interacive exception dialog.');
            Tine.Tinebase.exceptionDlg = new Tine.Tinebase.ExceptionDialog({
                exception: exception,
                nonInteractive: true,
                listeners: {
                    close: function() {
                        Tine.Tinebase.exceptionDlg = null;
                    }
                }
            });
            Tine.Tinebase.exceptionDlg.show();
        }
    }
    
    // init window error handler
    window.onerror = !window.onerror ? 
        onWindowError :
        window.onerror.createSequence(onWindowError);
        
    return {
        handleRequestException: handleRequestException
    };
}();
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

/*global Ext, Tine, window*/

Ext.ns('Tine', 'Tine.Tinebase');
 
 /**
  * @namespace  Tine.Tinebase
  * @class      Tine.Tinebase.ExceptionDialog
  * @extends    Ext.Window
  */
Tine.Tinebase.ExceptionDialog = Ext.extend(Ext.Window, {
    
    width: 400,
    height: 600,
    xtype: 'panel',
    layout: 'fit',
    plain: true,
    closeAction: 'close',
    autoScroll: true,
    releaseMode: true,
    
    /**
     * non-interactive mode: do not show anything and just submit the report
     * 
     * @type Boolean
     */
    nonInteractive: false,
    
    /**
     * @private
     */
    initComponent: function () {
        this.currentAccount = Tine.Tinebase.registry.get('currentAccount');
        
        // check if we have the version in registry (is not the case in the setup)
        if (! Tine.Tinebase.registry.get('version') || Tine.Tinebase.registry.get('version').buildType != 'RELEASE') {
            this.releaseMode = false;
            this.width = 800;
        }
        
        var trace = '';
        if (Ext.isArray(this.exception.trace)) {
            for (var i = 0,j = this.exception.trace.length; i < j; i++) {
                trace += (this.exception.trace[i].file ? this.exception.trace[i].file : '[internal function]') +
                         (this.exception.trace[i].line ? '(' + this.exception.trace[i].line + ')' : '') + ': ' +
                         (this.exception.trace[i]['class'] ? '<b>' + this.exception.trace[i]['class'] + this.exception.trace[i].type + '</b>' : '') +
                         '<b>' + this.exception.trace[i]['function'] + '</b>' +
                        '(' + ((this.exception.trace[i].args && this.exception.trace[i].args[0]) ? this.exception.trace[i].args[0] : '') + ')<br/>';
            }
            
            this.exception.traceHTML = trace;
        }
        
        this.title = _('Abnormal End');
        this.items = this.getReportForm();
        
        Tine.Tinebase.ExceptionDialog.superclass.initComponent.call(this);
        
        this.on('show', function () {
            if (this.nonInteractive) {
                Tine.log.debug('Tine.Tinebase.ExceptionDialog::onShow -> Sending bugreport non-interactive ...');
                this.onSendReport();
            } else {
                // fix layout issue
                this.setHeight(this.getHeight() + 10);
            }
        }, this);
        
        this.on('close', function() {
            if (Tine.Tinebase.registry && Tine.Tinebase.registry.get('config') && 
                Tine.Tinebase.registry.get('config').automaticBugreports && 
                Tine.Tinebase.registry.get('config').automaticBugreports.value && ! this.nonInteractive
            ) {
                Tine.log.debug('Tine.Tinebase.ExceptionDialog::onCancel -> Activate non-interacive exception dialog.');
                Tine.Tinebase.exceptionDlg = new Tine.Tinebase.ExceptionDialog({
                    exception: this.exception,
                    nonInteractive: true,
                    listeners: {
                        close: function() {
                            Tine.Tinebase.exceptionDlg = null;
                        }
                    }
                });
                Tine.Tinebase.exceptionDlg.show();
            }
        }, this);
    },
    
    /**
     * @private
     * @return {Array}
     */
    initButtons: function () {
        this.reportButtons = [{
            text: _('Cancel'),
            iconCls: 'action_cancel',
            scope: this,
            handler: function() {
                this.close();
            }
        }, {
            text: _('Send Report'),
            iconCls: 'action_saveAndClose',
            scope: this,
            enabled: Tine.Tinebase.common.hasRight('report_bugs', 'Tinebase'),
            handler: this.onSendReport
        }];
        
        return this.reportButtons;
    },
    
    /**
     * @private
     */
    getReportForm: function () {
        this.initButtons();
        
        this.reportForm = new Ext.FormPanel({
            id: 'tb-exceptiondialog-frompanel',
            bodyStyle: 'padding:5px;',
            buttonAlign: 'right',
            labelAlign: 'top',
            autoScroll: true,
            buttons: this.reportButtons,
            items: [{
                xtype: 'panel',
                border: false,
                html: '<div class="tb-exceptiondialog-text">' + 
                        '<p>' + _('An error occurred, the program ended abnormal.') + '</p>' +
                        '<p>' + _('The last action you made was potentially not performed correctly.') + '</p>' +
                        '<p>' + _('Please help improving this software and notify the vendor. Include a brief description of what you where doing when the error occurred.') + '</p>' + 
                    '</div>'
            }, {
                id: 'tb-exceptiondialog-description',
                height: 60,
                xtype: 'textarea',
                fieldLabel: _('Description'),
                name: 'description',
                anchor: '95%',
                readOnly: false
            }, {
                xtype: 'fieldset',
                id: 'tb-exceptiondialog-send-contact',
                anchor: '95%',
                title: _('Send Contact Information'),
                autoHeight: true,
                checkboxToggle: true,
                items: [{
                    id: 'tb-exceptiondialog-contact',
                    xtype: 'textfield',
                    hideLabel: true,
                    anchor: '100%',
                    name: 'contact',
                    value: this.currentAccount.accountFullName + ' ' + this.currentAccount.accountEmailAddress
                }]
            }, {
                xtype: 'panel',
                width: '95%',
                layout: 'form',
                collapsible: true,
                collapsed: this.releaseMode,
                title: _('Details:'),
                defaults: {
                    xtype: 'textfield',
                    readOnly: true,
                    anchor: '95%'
                },
                html: '<div class="tb-exceptiondialog-details">' +
                        '<p class="tb-exceptiondialog-msg">' + this.exception.message + '</p>' +
                        '<p class="tb-exceptiondialog-trace">' + this.exception.traceHTML + '</p>' +
                    '</div>'
            }]
        });
        
        return this.reportForm;
    },
    
    /**
     * send the report to tine20.org bugracker
     * 
     * NOTE: due to same domain policy, we need to send data via a img get request
     * @private
     */
    onSendReport: function () {
        if (! this.nonInteractive) {
            Ext.MessageBox.wait(_('Sending report...'), _('Please wait a moment'));
        }
        var baseUrl = 'http://www.tine20.org/bugreport.php';
        var hash = this.generateHash();
        
        this.exception.msg           = this.exception.message;
        this.exception.description   = Ext.getCmp('tb-exceptiondialog-description').getValue();
        this.exception.clientVersion = Tine.clientVersion;
        this.exception.serverVersion = (Tine.Tinebase.registry.get('version')) ? Tine.Tinebase.registry.get('version') : {};
        
        // tinebase version
        Ext.each(Tine.Tinebase.registry.get('userApplications'), function (app) {
            if (app.name == 'Tinebase') {
                this.exception.tinebaseVersion = app;
                return false;
            }
        }, this);
        
        // append contact?
        if (! Ext.getCmp('tb-exceptiondialog-send-contact').collapsed) {
            this.exception.contact = Ext.getCmp('tb-exceptiondialog-contact').getValue();
        }
        
        // NOTE:  - we have about 80 chars overhead (url, paramnames etc) in each request
        //        - 1024 chars are expected to be pass client/server limits savely => 940
        //        - base64 means about 30% overhead => 600 
        var chunks = this.strChunk(Ext.util.JSON.encode(this.exception), 600);
        
        var img = [];
        for (var i = 0; i < chunks.length; i++) {
            var part = i+1 + '/' + chunks.length;
            var data = {data : this.base64encode('hash=' + hash + '&part=' + part + '&data=' + chunks[i])};
            
            var url = baseUrl + '?' + Ext.urlEncode(data);
            img.push(Ext.DomHelper.insertFirst(this.el, {tag: 'img', src: url, hidden: true}, true));
        }
        
        if (! this.nonInteractive) {
            window.setTimeout(this.showTransmissionCompleted, 4000);
        }
        
        this.close();
    },
    
    /**
     * @private
     */
    showTransmissionCompleted: function () {
        Ext.MessageBox.show({
            title: _('Transmission Completed'),
            msg: _('Your report has been sent. Thanks for your contribution') + '<br /><b>' + _('Please restart your browser now!') + '</b>',
            buttons: Ext.MessageBox.OK,
            icon: Ext.MessageBox.INFO
        });
    },
    
    /**
     * @private
     */
    strChunk: function (str, chunklen) {
        var chunks = [];
        
        var numChunks = Math.ceil(str.length / chunklen);
        for (var i = 0; i < str.length; i +=  chunklen) {
            chunks.push(str.substr(i, chunklen));
        }
        return chunks;
    },
    
    /**
     * @private
     */
    generateHash: function () {
        // if the time isn't unique enough, the addition 
        // of random chars should be
        var t = String(new Date().getTime()).substr(4);
        var s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        for(var i = 0; i < 4; i++){
            t += s.charAt(Math.floor(Math.random()*26));
        }
        return t;
    },
    

    /**
     * base 64 encode given string
     * @private
     */
    base64encode : function (input) {
        var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        while (i < input.length) {

            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
            keyStr.charAt(enc1) + keyStr.charAt(enc2) +
            keyStr.charAt(enc3) + keyStr.charAt(enc4);

        }

        return output;
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.Tinebase.container');

/**
 * Tinebase container class
 * 
 * @todo move container model here
 * 
 * @namespace   Tine.Tinebase.container
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Tinebase.container = {
    /**
     * type for personal containers
     * 
     * @constant TYPE_PERSONAL
     * @type String
     */
    TYPE_PERSONAL: 'personal',
    
    /**
     * type for shared container
     * 
     * @constant TYPE_SHARED
     * @type String
     */
    TYPE_SHARED: 'shared',
    
    /**
     * @private
     * 
     * @property isLeafRegExp
     * @type RegExp
     */
    isLeafRegExp: /^\/personal\/[0-9a-z_\-]+\/|^\/shared\/[a-f0-9]+/i,
    
    /**
     * @private
     * 
     * @property isPersonalNodeRegExp
     * @type RegExp
     */
    isPersonalNodeRegExp: /^\/personal\/([0-9a-z_\-]+)$/i,
    
    /**
     * returns the path of the 'my ...' node
     * 
     * @return {String}
     */
    getMyNodePath: function() {
        return '/personal/' + Tine.Tinebase.registry.get('currentAccount').accountId;
    },
    
    /**
     * returns the file node path of the 'my ...' node
     * 
     * @return {String}
     */
    getMyFileNodePath: function() {
        return '/personal/' + Tine.Tinebase.registry.get('currentAccount').accountLoginName;
    },
    
    /**
     * returns true if given path represents a (single) container
     * 
     * NOTE: if path could only be undefined when server send container without path.
     *       This happens only in server json classes which only could return containers
     * 
     * @static
     * @param {String} path
     * @return {Boolean}
     */
    pathIsContainer: function(path) {
        return !Ext.isString(path) || !!path.match(Tine.Tinebase.container.isLeafRegExp);
    },
    
    /**
     * returns true if given path represents a personal container of current user
     * 
     * @param {String} path
     * @return {Boolean}
     */
    pathIsMyPersonalContainer: function(path) {
        var regExp = new RegExp('^' + Tine.Tinebase.container.getMyNodePath() + '\/([0-9a-z_\-]+)$');
        var matches = String(path).match(regExp);
        
        return !!matches;
    },
    
    /**
     * returns owner id if given path represents an personal _node_
     * 
     * @static
     * @param {String} path
     * @return {String/Boolean}
     */
    pathIsPersonalNode: function(path) {
        if (! Ext.isString(path)) {
            return false;
        }
        var matches = path.match(Tine.Tinebase.container.isPersonalNodeRegExp);
        
        return matches ? matches[1] : false;
    },
    
    /**
     * gets translated container name by path
     * 
     * @static
     * @param {String} path
     * @param {String} containerName
     * @param {String} containersName
     * @return {String}
     */
    path2name: function(path, containerName, containersName) {
        switch (path) {
            case '/':           return String.format(_('All {0}'), containersName);
            case '/shared':     return String.format(_('Shared {0}'), containersName);
            case '/personal':   return String.format(_('Other Users {0}'), containersName);
        }
        
        if (path === Tine.Tinebase.container.getMyNodePath()
                || path === Tine.Tinebase.container.getMyFileNodePath()) {
            return String.format(_('My {0}'), containersName);
        }
        
        return path;
    },
    
    /**
     * returns container type (personal/shared) of given path
     * 
     * @static
     * @param {String} path
     * @return {String}
     */
    path2type: function(path) {
        var pathParts = Ext.isArray(path) ? path : String(path).split('/');
        
        return pathParts[1];
    }
    
};
/*
 * Tine 2.0
 * 
 * @package     Tine
 * @subpackage  Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */

/*global Ext, Tine, Locale*/

Ext.ns('Tine', 'Tine.Tinebase');

/**
 * static common helpers
 */
Tine.Tinebase.common = {
    
    /**
     * Open browsers native popup
     * 
     * @param {string}     windowName
     * @param {string}     url
     * @param {int}     width
     * @param {int}     height
     */
    openWindow: function (windowName, url, width, height) {
        // M$ IE has its internal location bar in the viewport
        if (Ext.isIE) {
            height = height + 20;
        }
        
        // chrome counts window decoration and location bar to window height
        if (Ext.isChrome) {
            height += 40;
        }
        
        windowName = Ext.isString(windowName) ? windowName.replace(/[^a-zA-Z0-9_]/g, '') : windowName;
        
        var    w, h, x, y, leftPos, topPos, popup;

        if (document.all) {
            w = document.body.clientWidth;
            h = document.body.clientHeight;
            x = window.screenTop;
            y = window.screenLeft;
        } else {
            if (window.innerWidth) {
                w = window.innerWidth;
                h = window.innerHeight;
                x = window.screenX;
                y = window.screenY;
            }
        }
        leftPos = ((w - width) / 2) + y;
        topPos = ((h - height) / 2) + x;
        

        try {
            popup = window.open(url, windowName, 'width=' + width + ',height=' + height + ',top=' + topPos + ',left=' + leftPos +
                ',directories=no,toolbar=no,location=no,menubar=no,scrollbars=no,status=no,resizable=yes,dependent=no');
            
            return popup;
        }
        catch(e) {
            Tine.log.info('window.open Exception: ');
            Tine.log.info(e);
            
            popup = false;
            
        }
        
        if (! popup) {
            var openCode = "window.open('http://127.0.0.1/tine20/tine20/" + url + "','" + windowName + "','width=" + width + ",height=" + height + ",top=" + topPos + ",left=" + leftPos +
            ",directories=no,toolbar=no,location=no,menubar=no,scrollbars=no,status=no,resizable=yes,dependent=no')";
        
            var exception = {
                openCode: openCode,
                popup: null
            };
            
            Tine.log.debug('openCode: ' + openCode);
            popup = openCode;
            
//            if(Tine.Tinebase.MainScreen.fireEvent('windowopenexception', exception) !== false){
//                // show message 'your popupblocker ... please click here'
//                // mhh how to make this syncron???
//                
//                // todo: review code in Ext.ux.PopupWindow...
//                popup = window;
//            } else {
//                popup = exception.popup;
//            }
        }
        
        return popup;
        
    },
    
    showDebugConsole: function () {
        if (! Ext.debug) {
            var head = document.getElementsByTagName("head")[0],
                scriptTag = document.createElement("script");
            
            scriptTag.setAttribute("src", 'library/ExtJS/src/debug.js');
            scriptTag.setAttribute("type", "text/javascript");
            head.appendChild(scriptTag);
            
            var scriptEl = Ext.get(scriptTag);
            scriptEl.on('load', function () {
                Ext.log('debug console initialised');
            });
            scriptEl.on('fail', function () {
                Ext.msg.alert('could not activate debug console');
            });
        } else {
            Ext.log('debug console reactivated');
        }
    },
    
    /**
     * Returns localised date and time string
     * 
     * @param {mixed} $_iso8601
     * @see Ext.util.Format.date
     * @return {String} localised date and time
     */
    dateTimeRenderer: function ($_iso8601) {
        var dateObj = $_iso8601 instanceof Date ? $_iso8601 : Date.parseDate($_iso8601, Date.patterns.ISO8601Long);
        
        return Ext.util.Format.date(dateObj, Locale.getTranslationData('Date', 'medium') + ' ' + Locale.getTranslationData('Time', 'medium'));
    },

    /**
     * Returns localised date string
     * 
     * @param {mixed} date
     * @see Ext.util.Format.date
     * @return {String} localised date
     */
    dateRenderer: function (date) {
        var dateObj = date instanceof Date ? date : Date.parseDate(date, Date.patterns.ISO8601Long);
        
        return Ext.util.Format.date(dateObj, Locale.getTranslationData('Date', 'medium'));
    },
    
    /**
     * Returns localised time string
     * 
     * @param {mixed} date
     * @see Ext.util.Format.date
     * @return {String} localised time
     */
    timeRenderer: function (date) {
        var dateObj = date instanceof Date ? date : Date.parseDate(date, Date.patterns.ISO8601Long);
        
        return Ext.util.Format.date(dateObj, Locale.getTranslationData('Time', 'medium'));
    },
    
    /**
     * renders bytes for filesize 
     * @param {Integer} value
     * @param {Object} metadata
     * @param {Tine.Tinebase.data.Record} record
     * @param {Integer} decimals
     * @param {Boolean} useDecimalValues
     * @return {String}
     */
    byteRenderer: function (value, metadata, record, decimals, useDecimalValues) {
        if (record && record.get('type') == 'folder') {
            return '';
        }
        return Tine.Tinebase.common.byteFormatter(value, null, decimals, useDecimalValues);
    },

    /**
     * format byte values
     * @param {String} value
     * @param {Boolean} forceUnit
     * @param {Integer} decimals
     * @param {Boolean} useDecimalValues
     */
    byteFormatter: function(value, forceUnit, decimals, useDecimalValues) {
        value = parseInt(value, 10);
        decimals = Ext.isNumber(decimals) ? decimals : 2;
        var suffix = ['Bytes', 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            divisor = useDecimalValues ? 1000 : 1024;
            
        if (forceUnit) {
            var i = suffix.indexOf(forceUnit);
            i = (i == -1) ? 0 : i;
        } else {
            for (var i=0,j; i<suffix.length; i++) {
                if (value < Math.pow(divisor, i)) break;
            }
        }
        return ((i<=1) ? value : Ext.util.Format.round(value/(Math.pow(divisor, Math.max(1, i-1))), decimals)) + ' ' + suffix[i];
    },
    
    /**
     * Returns rendered tags for grids
     * 
     * @param {mixed} tags
     * @return {String} tags as colored squares with qtips
     * 
     * TODO add style for tag divs
     */
    tagsRenderer: function (tags) {
        var result = '';
        if (tags) {
            for (var i = 0; i < tags.length; i += 1) {
                var qtipText = Tine.Tinebase.common.doubleEncode(tags[i].name);
                if (tags[i].description) {
                    qtipText += ' | ' + Tine.Tinebase.common.doubleEncode(tags[i].description);
                }
                if(tags[i].occurrence) {
                    qtipText += ' (' + _('Usage:&#160;') + tags[i].occurrence + ')';
                }
                result += '<div ext:qtip="' + qtipText + '" class="tb-grid-tags" style="background-color:' + (tags[i].color ? tags[i].color : '#fff') + ';">&#160;</div>';
            }
        }
        
        return result;
    },
    
    /**
     * render single tag
     * 
     * @param {Tine.Tinebase.Model.Tag} tag
     */
    tagRenderer: function(tag) {
        if (! Tine.Tinebase.common.tagRenderer.tpl) {
            Tine.Tinebase.common.tagRenderer.tpl = new Ext.XTemplate(
                '<div class="tb-grid-tags" style="background-color:{values.color};">&#160;</div>',
                '<div class="x-widget-tag-tagitem-text" ext:qtip="', 
                    '{[this.encode(values.name)]}', 
                    '<tpl if="type == \'personal\' ">&nbsp;<i>(' + _('personal') + ')</i></tpl>',
                    '</i>&nbsp;[{occurrence}]',
                    '<tpl if="description != null && description.length &gt; 1"><hr>{[this.encode(values.description)]}</tpl>" >',
                    
                    '&nbsp;{[this.encode(values.name)]}',
                    '<tpl if="type == \'personal\' ">&nbsp;<i>(' + _('personal') + ')</i></tpl>',
                '</div>',
            {
                encode: function(value) {
                     if (value) {
                        return Tine.Tinebase.common.doubleEncode(value);
                    } else {
                        return '';
                    }
                }
            }).compile();
        }
        
        var result =  _('No Information');
        
        if (tag && Ext.isFunction(tag.beginEdit)) {
            // support records
            tag = tag.data;
        } else if (arguments[2] && Ext.isFunction(arguments[2].beginEdit)) {
            // support grid renderers
            tag = arguments[2].data;
        }
        
        // non objects are treated as ids and -> No Information
        if (Ext.isObject(tag)) {
            result = Tine.Tinebase.common.tagRenderer.tpl.apply(tag);
        }
        
        return result;
    },
    
    /**
     * Returns rendered containers
     * 
     * @TODO show qtip with grants
     * 
     * @param {mixed} container
     * @return {String} 
     */
    containerRenderer: function(container, metaData) {
        // lazy init tempalte
        if (! Tine.Tinebase.common.containerRenderer.tpl) {
            Tine.Tinebase.common.containerRenderer.tpl = new Ext.XTemplate(
                '<div class="x-tree-node-leaf x-unselectable file">',
                    '<img class="x-tree-node-icon" unselectable="on" src="', Ext.BLANK_IMAGE_URL, '">',
                    '<span style="color: {color};">&nbsp;&#9673;&nbsp</span>',
                    '<span> ', '{name}','</span>',
                '</div>'
            ).compile();
        }
        
        var result =  _('No Information');
        
        // support container records
        if (container && Ext.isFunction(container.beginEdit)) {
            container = container.data;
        }
        
        // non objects are treated as ids and -> No Information
        if (Ext.isObject(container)) {
            var name = Ext.isFunction(container.beginEdit) ? container.get('name') : container.name,
                color = Ext.isFunction(container.beginEdit) ? container.get('color') : container.color;
            
            if (name) {
                result = Tine.Tinebase.common.containerRenderer.tpl.apply({
                    name: Ext.util.Format.htmlEncode(name).replace(/ /g,"&nbsp;"),
                    color: color ? color : '#808080'
                });
            } else if (Ext.isObject(metaData)) {
                metaData.css = 'x-form-empty-field';
            }
        }
        
        return result;
    },
    
    /**
     * Returns prettyfied minutes
     * 
     * @param  {Number} minutes
     * @param  {String} format -> {0} will be replaced by Hours, {1} with minutes
     * @param  {String} leadingZeros add leading zeros for given item {i|H}
     * @return {String}
     */
    minutesRenderer: function (minutes, format, leadingZeros) {
        var s,
            i = minutes % 60,
            H = Math.floor(minutes / 60),
            Hs;
        
        if (leadingZeros && (Ext.isString(leadingZeros) || leadingZeros === true)) {
            if (leadingZeros === true || (leadingZeros.match(/i/) && String(i).length === 1)) {
                i = '0' + String(i);
            }
            if (leadingZeros === true || (leadingZeros.match(/H/) && String(H).length === 1)) {
                H = '0' + String(H);
            }
        }
        
        if (! format || ! Ext.isString(format)) {
            s = String.format(Tine.Tinebase.translation.ngettext('{0} minute', '{0} minutes', i), i);
            Hs = String.format(Tine.Tinebase.translation.ngettext('{0} hour', '{0} hours', H), H);
            //var ds = String.format(Tine.Tinebase.translation.ngettext('{0} workday', '{0} workdays', d), d);
            
            if (i === 0) {
                s = Hs;
            } else {
                s = H ? Hs + ', ' + s : s;
            }
            //s = d ? ds + ', ' + s : s;
            
            return s;
        }
        
        return String.format(format, H, i);
    },

    /**
     * Returns prettyfied seconds
     * 
     * @param  {Number} seconds
     * @return {String}
     */
    secondsRenderer: function (seconds) {
        
        var s = seconds % 60,
            m = Math.floor(seconds / 60),
            result = '';
        
        var secondResult = String.format(Tine.Tinebase.translation.ngettext('{0} second', '{0} seconds', s), s);
        
        if (m) {
            result = Tine.Tinebase.common.minutesRenderer(m);
        }
        
        if (s) {
            if (result !== '') {
                result += ', ';
            }
            result += secondResult;
        }
        
        return result;
    },
    
    /**
     * Returns the formated username
     * 
     * @param {object} account object 
     * @return {string} formated user display name
     */
    usernameRenderer: function (accountObject) {
        var result = (accountObject) ? accountObject.accountDisplayName : '';
        
        return Ext.util.Format.htmlEncode(result);
    },
    
    /**
     * Returns a username or groupname with according icon in front
     */
    accountRenderer: function (accountObject, metadata, record, rowIndex, colIndex, store) {
        if (! accountObject) {
            return '';
        }
        var type, iconCls, displayName;
        
        if (accountObject.accountDisplayName) {
            type = 'user';
            displayName = accountObject.accountDisplayName;
        } else if (accountObject.name) {
            type = 'group';
            displayName = accountObject.name;
        } else if (record.data.name) {
            type = record.data.type;
            displayName = record.data.name;
        } else if (record.data.account_name) {
            type = record.data.account_type;
            displayName = record.data.account_name;
        }
        
        iconCls = type === 'user' ? 'renderer renderer_accountUserIcon' : 'renderer renderer_accountGroupIcon';
        
        return '<div class="' + iconCls  + '">&#160;</div>' + Ext.util.Format.htmlEncode(displayName);
    },
    
    /**
     * Returns account type icon
     * 
     * @return String
     */
    accountTypeRenderer: function (type) {
        var iconCls = (type) === 'user' ? 'renderer_accountUserIcon' : 'renderer_accountGroupIcon';
        
        return '<div style="background-position: 0px" class="' + iconCls  + '">&#160;</div>';
    },
    
    /**
     * Returns dropdown hint icon for editor grid columns with comboboxes
     * 
     * @return String
     */
    cellEditorHintRenderer: function (value) {
        return '<div style="position:relative">' + value + '<div class="tine-grid-cell-hint">&#160;</div></div>';
    },

    /**
     * return yes or no in the selected language for a boolean value
     * 
     * @param {string} value
     * @return {string}
     */
    booleanRenderer: function (value) {
        var translationString = String.format("{0}",(value == 1) ? Locale.getTranslationData('Question', 'yes') : Locale.getTranslationData('Question', 'no'));
        
        return translationString.substr(0, translationString.indexOf(':'));
    },
    
    /**
     * sorts account/user objects
     * 
     * @param {Object|String} user_id
     * @return {String}
     */
    accountSortType: function(user_id) {
        if (user_id && user_id.accountDisplayName) {
            return user_id.accountDisplayName;
        } else if (user_id && user_id.n_fileas) {
            return user_id.n_fileas;
        } else if (user_id && user_id.name) {
            return user_id.name;
        } else {
            return user_id;
        }
    },

    /**
     * sorts records
     * 
     * @param {Object} record
     * @return {String}
     */
    recordSortType: function(record) {
        if (record && Ext.isFunction(record.getTitle)) {
            return record.getTitle();
        } else if (record && record.id) {
            return record.id;
        } else {
            return record;
        }
    },
    
    /**
     * check whether given value can be interpreted as true
     * 
     * @param {String|Integer|Boolean} value
     * @return {Boolean}
     */
    isTrue: function (value) {
        return value === 1 || value === '1' || value === true || value === 'true';
    },
    
    /**
     * check whether object is empty (has no property)
     * 
     * @param {Object} obj
     * @return {Boolean}
     */
    isEmptyObject: function (obj) {
        for (var name in obj) {
            if (obj.hasOwnProperty(name)) {
                return false;
            }
        }
        return true;
    },
    
    /**
     * clone function
     * 
     * @param {Object/Array} o Object or array to clone
     * @return {Object/Array} Deep clone of an object or an array
     */
    clone: function (o) {
        if (! o || 'object' !== typeof o) {
            return o;
        }
        
        if ('function' === typeof o.clone) {
            return o.clone();
        }
        
        var c = '[object Array]' === Object.prototype.toString.call(o) ? [] : {},
            p, v;
            
        for (p in o) {
            if (o.hasOwnProperty(p)) {
                v = o[p];
                if (v && 'object' === typeof v) {
                    c[p] = Tine.Tinebase.common.clone(v);
                }
                else {
                    c[p] = v;
                }
            }
        }
        return c;
    },
    
    /**
     * assert that given object is comparable
     * 
     * @param {mixed} o
     * @return {mixed} o
     */
    assertComparable: function(o) {
        // NOTE: Ext estimates Object/Array by a toString operation
        if (Ext.isObject(o) || Ext.isArray(o)) {
            Tine.Tinebase.common.applyComparableToString(o);
        }
        
        return o;
    },
    
    /**
     * apply Ext.encode as toString functino to given object
     * 
     * @param {mixed} o
     */
    applyComparableToString: function(o) {
        o.toString = function() {return Ext.encode(o)};
    },
    
    /**
     * check if user has right to view/manage this application/resource
     * 
     * @param   {String}      right (view, admin, manage)
     * @param   {String}      application
     * @param   {String}      resource (for example roles, accounts, ...)
     * @returns {Boolean} 
     */
    hasRight: function (right, application, resource) {
        var userRights = [];
        
        if (! (Tine && Tine[application] && Tine[application].registry && Tine[application].registry.get('rights'))) {
            if (! Tine.Tinebase.appMgr) {
                console.error('Tine.Tinebase.appMgr not yet available');
            } else if (Tine.Tinebase.appMgr.get(application)) {
                console.error('Tine.' + application + '.rights is not available, initialisation Error!');
            }
            return false;
        }
        userRights = Tine[application].registry.get('rights');
        
        //console.log(userRights);
        var result = false;
        
        for (var i = 0; i < userRights.length; i += 1) {
            if (userRights[i] === 'admin') {
                result = true;
                break;
            }
            
            if (right === 'view' && (userRights[i] === 'view_' + resource || userRights[i] === 'manage_' + resource)) {
                result = true;
                break;
            }
            
            if (right === 'manage' && userRights[i] === 'manage_' + resource) {
                result = true;
                break;
            }
            
            if (right === userRights[i]) {
                result = true;
                break;
            }
        }
    
        return result;
    },
    
    /**
     * returns random integer number
     * @param {Integer} min
     * @param {Integer} max
     * @return {Integer}
     */
    getRandomNumber: function (min, max) {
        if (min > max) {
            return -1;
        }
        if (min === max) {
            return min;
        }
        return min + parseInt(Math.random() * (max - min + 1), 10);
    },
    /**
     * HTML-encodes a string twice
     * @param {String} value
     * @return {String}
     */
    doubleEncode: function(value) {
        return Ext.util.Format.htmlEncode(Ext.util.Format.htmlEncode(value));
    },
    
    /**
     * resolves an appName to applicationInstance or vice versa
     * returns applicationinstance if getInstance is true
     * @param {String/Tine.Tinebase.Application}    app 
     * @param {Boolean}                             getInstance
     */
    resolveApp: function(app, getInstance) {
        if(getInstance) {
            return Ext.isObject(app) ? app : Tinebase.appMgr.get(app);
        }
        return Ext.isObject(app) ? app.name : app;
    },
    
    /**
     * resolves model to modelName or returns recordClass if an application was given
     * @param {String/Tine.Tinebase.data.Record}    model
     * @param {String/Tine.Tinebase.Application}    app
     */
    resolveModel: function(model, app) {
        var modelName = Ext.isObject(model) ? model.getMeta('modelName') : model;
        if(app) {
            var appName = this.resolveApp(app);
            return Tine[appName].Model[modelName];
        }
        return modelName;
    }
};
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.Tinebase.Model');

/**
 * @type {Array}
 * 
 * modlog Fields
 */

Tine.Tinebase.Model.modlogFields = [
    { name: 'creation_time',      type: 'date', dateFormat: Date.patterns.ISO8601Long, isMetaField: true },
    { name: 'created_by',                                                              isMetaField: true },
    { name: 'last_modified_time', type: 'date', dateFormat: Date.patterns.ISO8601Long, isMetaField: true },
    { name: 'last_modified_by',                                                        isMetaField: true },
    { name: 'is_deleted',         type: 'boolean',                                     isMetaField: true },
    { name: 'deleted_time',       type: 'date', dateFormat: Date.patterns.ISO8601Long, isMetaField: true },
    { name: 'deleted_by',                                                              isMetaField: true }
];

/**
 * @type {Array}
 * generic Record fields
 */
Tine.Tinebase.Model.genericFields = Tine.Tinebase.Model.modlogFields.concat([
    { name: 'container_id', header: 'Container',                                       isMetaField: false}
]);
    
/**
 * Model of a language
 */
Tine.Tinebase.Model.Language = Ext.data.Record.create([
    { name: 'locale' },
    { name: 'language' },
    { name: 'region' }
]);

/**
 * Model of a timezone
 */
Tine.Tinebase.Model.Timezone = Ext.data.Record.create([
    { name: 'timezone' },
    { name: 'timezoneTranslation' }
]);

/**
 * Model of a role
 */
Tine.Tinebase.Model.Role = Tine.Tinebase.data.Record.create([
    {name: 'id'},
    {name: 'name'},
    {name: 'description'}
], {
    appName: 'Tinebase',
    modelName: 'Role',
    idProperty: 'id',
    titleProperty: 'name',
    recordName: 'Role',
    recordsName: 'Roles',
    containerProperty: null
});

/**
 * Model of a generalised account (user or group)
 */
Tine.Tinebase.Model.Account = Ext.data.Record.create([
    {name: 'id'},
    {name: 'type'},
    {name: 'name'},
    {name: 'data'} // todo: throw away data
]);

/**
 * Model of a container
 */
Tine.Tinebase.Model.Container = Tine.Tinebase.data.Record.create(Tine.Tinebase.Model.genericFields.concat([
    {name: 'id'},
    {name: 'name'},
    {name: 'type'},
    {name: 'color'},
    {name: 'path'},
    {name: 'is_container_node', type: 'boolean'},
    {name: 'dtselect', type: 'number'},
    {name: 'backend'},
    {name: 'application_id'},
    {name: 'account_grants'},
    {name: 'model'}
]), {
    appName: 'Tinebase',
    modelName: 'Container',
    idProperty: 'id',
    titleProperty: 'name'
});

/**
 * Model of a grant
 */
Tine.Tinebase.Model.Grant = Ext.data.Record.create([
    {name: 'id'},
    {name: 'account_id'},
    {name: 'account_type'},
    {name: 'account_name', sortType: Tine.Tinebase.common.accountSortType},
    {name: 'freebusyGrant',type: 'boolean'},
    {name: 'readGrant',    type: 'boolean'},
    {name: 'addGrant',     type: 'boolean'},
    {name: 'editGrant',    type: 'boolean'},
    {name: 'deleteGrant',  type: 'boolean'},
    {name: 'privateGrant', type: 'boolean'},
    {name: 'exportGrant',  type: 'boolean'},
    {name: 'syncGrant',    type: 'boolean'},
    {name: 'adminGrant',   type: 'boolean'}
]);

/**
 * Model of a tag
 * 
 * @constructor {Tine.Tinebase.data.Record}
 */
Tine.Tinebase.Model.Tag = Tine.Tinebase.data.Record.create(Tine.Tinebase.Model.genericFields.concat([
    {name: 'id'         },
    {name: 'app'        },
    {name: 'owner'      },
    {name: 'name'       },
    {name: 'type'       },
    {name: 'description'},
    {name: 'color'      },
    {name: 'occurrence' },
    {name: 'rights'     },
    {name: 'contexts'   },
    {name: 'selection_occurrence', type: 'number'}
]), {
    appName: 'Tinebase',
    modelName: 'Tag',
    idProperty: 'id',
    titleProperty: 'name',
    // ngettext('Tag', 'Tags', n); gettext('Tag');
    recordName: 'Tag',
    recordsName: 'Tags'
});

/**
 * replace template fields with data
 * @static
 */
Tine.Tinebase.Model.Tag.replaceTemplateField = function(tagData) {
    if (Ext.isArray(tagData)) {
        return Ext.each(tagData, Tine.Tinebase.Model.Tag.replaceTemplateField);
    }
    
    if (Ext.isFunction(tagData.beginEdit)) {
        tagData = tagData.data;
    }
    
    var replace = {
        'CURRENTDATE': Tine.Tinebase.common.dateRenderer(new Date()),
        'CURRENTTIME': Tine.Tinebase.common.timeRenderer(new Date()),
        'USERFULLNAME': Tine.Tinebase.registry.get('currentAccount').accountDisplayName
    };
    
    Ext.each(['name', 'description'], function(field) {
        for(var token in replace) {
            if (replace.hasOwnProperty(token) && Ext.isString(tagData[field])) {
                tagData[field] = tagData[field].replace(new RegExp('###' + token + '###', 'g'), replace[token]);
            }
        }
    }, this);
    
};

/**
 * Model of a PickerRecord
 * 
 * @constructor {Ext.data.Record}
 * 
 * @deprecated
 */
Tine.Tinebase.PickerRecord = Ext.data.Record.create([
    {name: 'id'}, 
    {name: 'name'}, 
    {name: 'data'}
]);

/**
 * Model of a note
 * 
 * @constructor {Ext.data.Record}
 */
Tine.Tinebase.Model.Note = Ext.data.Record.create([
    {name: 'id'             },
    {name: 'note_type_id'   },
    {name: 'note'           },
    {name: 'creation_time', type: 'date', dateFormat: Date.patterns.ISO8601Long },
    {name: 'created_by'     }
]);

/**
 * Model of a note type
 * 
 * @constructor {Ext.data.Record}
 */
Tine.Tinebase.Model.NoteType = Ext.data.Record.create([
    {name: 'id'             },
    {name: 'name'           },
    {name: 'icon'           },
    {name: 'icon_class'     },
    {name: 'description'    },
    {name: 'is_user_type'   }
]);

/**
 * Model of a customfield definition
 */
Tine.Tinebase.Model.Customfield = Ext.data.Record.create([
    { name: 'id'             },
    { name: 'application_id' },
    { name: 'model'          },
    { name: 'name'           },
    { name: 'definition'     },
    { name: 'account_grants' }
]);

/**
 * Model of a customfield value
 */
Tine.Tinebase.Model.CustomfieldValue = Ext.data.Record.create([
    { name: 'record_id'      },
    { name: 'customfield_id' },
    { name: 'value'          }
]);

/**
 * Model of a preference
 * 
 * @constructor {Ext.data.Record}
 */
Tine.Tinebase.Model.Preference = Ext.data.Record.create([
    {name: 'id'             },
    {name: 'name'           },
    {name: 'value'          },
    {name: 'type'           },
    {name: 'label'          },
    {name: 'description'    },
    {name: 'personal_only',         type: 'boolean' },
    {name: 'options'        }
]);

/**
 * Model of an alarm
 * 
 * @constructor {Ext.data.Record}
 */
Tine.Tinebase.Model.Alarm = Tine.Tinebase.data.Record.create([
    {name: 'id'             },
    {name: 'record_id'      },
    {name: 'model'          },
    {name: 'alarm_time',      type: 'date', dateFormat: Date.patterns.ISO8601Long },
    {name: 'minutes_before',  sortType: Ext.data.SortTypes.asInt},
    {name: 'sent_time',       type: 'date', dateFormat: Date.patterns.ISO8601Long },
    {name: 'sent_status'    },
    {name: 'sent_message'   },
    {name: 'options'        }
], {
    appName: 'Tinebase',
    modelName: 'Alarm',
    idProperty: 'id',
    titleProperty: 'minutes_before',
    // ngettext('Alarm', 'Alarms', n); gettext('Alarm');
    recordName: 'Alarm',
    recordsName: 'Alarms',
    containerProperty: null,
    getOption: function(name) {
        var encodedOptions = this.get('options'),
            options = encodedOptions ? Ext.decode(encodedOptions) : {}
        
        return options[name];
    },
    setOption: function(name, value) {
        var encodedOptions = this.get('options'),
            options = encodedOptions ? Ext.decode(encodedOptions) : {}
        
        options[name] = value;
        this.set('options', Ext.encode(options));
    }
});

/**
 * @namespace Tine.Tinebase.Model
 * @class     Tine.Tinebase.Model.ImportJob
 * @extends   Tine.Tinebase.data.Record
 * 
 * Model of an import job
 */
Tine.Tinebase.Model.ImportJob = Tine.Tinebase.data.Record.create([
    {name: 'files'                  },
    {name: 'import_definition_id'   },
    {name: 'model'                  },
    {name: 'import_function'        },
    {name: 'container_id'           },
    {name: 'dry_run'                },
    {name: 'options'                }
], {
    appName: 'Tinebase',
    modelName: 'Import',
    idProperty: 'id',
    titleProperty: 'model',
    // ngettext('Import', 'Imports', n); gettext('Import');
    recordName: 'Import',
    recordsName: 'Imports',
    containerProperty: null
});

/**
 * @namespace Tine.Tinebase.Model
 * @class     Tine.Tinebase.Model.ExportJob
 * @extends   Tine.Tinebase.data.Record
 * 
 * Model of an export job
 */
Tine.Tinebase.Model.ExportJob = Tine.Tinebase.data.Record.create([
    {name: 'filter'                 },
    {name: 'export_definition_id'   },
    {name: 'format'                 },
    {name: 'exportFunction'         },
    {name: 'recordsName'            },
    {name: 'model'                  },
    {name: 'count', type: 'int'     },
    {name: 'options'                }
], {
    appName: 'Tinebase',
    modelName: 'Export',
    idProperty: 'id',
    titleProperty: 'model',
    // ngettext('Export', 'Export', n); gettext('Export');
    recordName: 'Export',
    recordsName: 'Exports',
    containerProperty: null
});

/**
 * Model of an export/import definition
 * 
 * @constructor {Ext.data.Record}
 */
Tine.Tinebase.Model.ImportExportDefinition = Ext.data.Record.create(Tine.Tinebase.Model.genericFields.concat([
    {name: 'id'             },
    {name: 'name'           },
    {name: 'label', sortType: Ext.data.SortTypes.asUCText },
    {name: 'filename'       },
    {name: 'plugin'         },
    {name: 'description'    },
    {name: 'model'          },
    {name: 'plugin_options' }
]));

/**
 * @namespace Tine.Tinebase.Model
 * @class     Tine.Tinebase.Model.Credentials
 * @extends   Tine.Tinebase.data.Record
 * 
 * Model of user credentials
 */
Tine.Tinebase.Model.Credentials = Tine.Tinebase.data.Record.create([
    {name: 'id'},
    {name: 'username'},
    {name: 'password'}
], {
    appName: 'Tinebase',
    modelName: 'Credentials',
    idProperty: 'id',
    titleProperty: 'username',
    // ngettext('Credentials', 'Credentials', n); gettext('Credentials');
    recordName: 'Credentials',
    recordsName: 'Credentials',
    containerProperty: null
});

/**
 * @namespace Tine.Tinebase.Model
 * @class     Tine.Tinebase.Model.Relation
 * @extends   Tine.Tinebase.data.Record
 * 
 * Model of a Relation
 */
Tine.Tinebase.Model.Relation = Tine.Tinebase.data.Record.create([
    {name: 'id'},
    {name: 'own_model'},
    {name: 'own_id'},
    {name: 'related_model'},
    {name: 'related_id'},
    {name: 'type'},
    {name: 'remark'},
    {name: 'related_record', sortType: Tine.Tinebase.common.recordSortType},
    {name: 'creation_time'}
], {
    appName: 'Tinebase',
    modelName: 'Relation',
    idProperty: 'id',
    titleProperty: 'related_model',
    // ngettext('Relation', 'Relations', n); gettext('Relation');
    recordName: 'Relation',
    recordsName: 'Relations',
    containerProperty: null
});

/**
 * @namespace Tine.Tinebase.Model
 * @class     Tine.Tinebase.Model.Department
 * @extends   Tine.Tinebase.data.Record
 * 
 * Model of a Department
 */
Tine.Tinebase.Model.Department = Tine.Tinebase.data.Record.create([
    {name: 'id'},
    {name: 'name'},
    {name: 'description'}
], {
    appName: 'Tinebase',
    modelName: 'Department',
    idProperty: 'id',
    titleProperty: 'name',
    // ngettext('Department', 'Departments', n); gettext('Department');
    recordName: 'Department',
    recordsName: 'Departments',
    containerProperty: null
});

Tine.Tinebase.Model.Department.getFilterModel = function() {
    return [
        {label: _('Name'),          field: 'name',       operators: ['contains']}
    ];
};

/**
 * @namespace Tine.Tinebase.Model
 * @class     Tine.Tinebase.Model.Config
 * @extends   Tine.Tinebase.data.Record
 * 
 * Model of a application config settings
 */
Tine.Tinebase.Model.Config = Tine.Tinebase.data.Record.create([
    {name: 'id'}, // application name
    {name: 'settings'}
], {
    appName: 'Tinebase',
    modelName: 'Config',
    idProperty: 'id',
    titleProperty: 'id',
    // ngettext('Config', 'Configs', n); gettext('Configs');
    recordName: 'Config',
    recordsName: 'Configs',
    containerProperty: null
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.Tinebase');

/**
 * <p>Abstract base class for all Tine applications</p>
 * 
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.Application
 * @extends     Ext.util.Observable
 * @consturctor
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Tinebase.Application = function(config) {
    config = config || {};
    Ext.apply(this, config);
    
    Tine.Tinebase.Application.superclass.constructor.call(this);
    
    this.i18n = new Locale.Gettext();
    this.i18n.textdomain(this.appName);
    
    this.init();
    this.initAutoHooks();
};

Ext.extend(Tine.Tinebase.Application, Ext.util.Observable , {
    
    /**
     * @cfg {String} appName
     * untranslated application name (required)
     */
    appName: null,
    
    /**
     * @cfg {Boolean} hasMainScreen
     */
    hasMainScreen: true,
    
    /**
     * @property {Locale.gettext} i18n
     */
    i18n: null,
    
    /**
     * returns title of this application
     * 
     * @return {String}
     */
    getTitle: function() {
        return this.i18n._(this.appName);
    },
    
    /**
     * returns iconCls of this application
     * 
     * @param {String} target
     * @return {String}
     */
    getIconCls: function(target) {
        return this.appName + 'IconCls';
    },
    
    /**
     * returns the mainscreen of this application
     * 
     * @return {Tine.widgets.app.MainScreen}
     */
    getMainScreen: function() {
        if (! this.mainScreen && typeof Tine[this.appName].MainScreen === 'function') {
            this.mainScreen = new Tine[this.appName].MainScreen({
                app: this
            });
        }
        
        return this.mainScreen;
    },
    
    /**
     * returns registry of this app
     * 
     * @return {Ext.util.MixedCollection}
     */
    getRegistry: function() {
        return Tine[this.appName].registry;
    },
    
    /**
     * template function for subclasses to initialize application
     */
    init: Ext.emptyFn,

    /**
     * init some auto hooks
     */
    initAutoHooks: function() {
        if (this.addButtonText) {
            Ext.ux.ItemRegistry.registerItem('Tine.widgets.grid.GridPanel.addButton', {
                text: this.i18n._hidden(this.addButtonText), 
                iconCls: this.getIconCls(),
                scope: this,
                handler: function() {
                    var ms = this.getMainScreen(),
                        cp = ms.getCenterPanel();
                        
                    cp.onEditInNewWindow.call(cp, {});
                }
            });
        }
    },
    
    /**
     * template function for subclasses is called before app activation. Return false to cancel activation
     */
    onBeforeActivate: Ext.emptyFn,
    
    /**
     * template function for subclasses is called after app activation.
     */
    onActivate: Ext.emptyFn,
    
    /**
     * template function for subclasses is called before app deactivation. Return false to cancel deactivation
     */
    onBeforeDeActivate: Ext.emptyFn,
    
    /**
     * template function for subclasses is called after app deactivation.
     */
    onDeActivate: Ext.emptyFn
    
});
/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
Ext.ns('Tine.Tinebase.widgets.keyfield');

/**
 * key field store
 * 
 * @namespace   Tine.Tinebase.widgets.keyfield
 * @param       {Object} config
 * @constructor
 */
Tine.Tinebase.widgets.keyfield.Store = function(config) {
    // init app
    config.app = Ext.isString(config.app) ? Tine.Tinebase.appMgr.get(config.app) : config.app;
    
    // get keyField config
    config.keyFieldConfig = config.app.getRegistry().get('config')[config.keyFieldName];
    
    // init data / translate values
    var data = config.keyFieldConfig && config.keyFieldConfig.value && config.keyFieldConfig.value.records.length ? config.keyFieldConfig.value.records : [];
    Ext.each(data, function(d) {
        d.i18nValue = d.value ? config.app.i18n._hidden(d.value) : "";
    });
    config.data = data;
    
    if (! config.keyFieldConfig) {
        throw ('No keyfield config found for ' + config.keyFieldName + ' in ' + config.app.appName + ' registry.');
    }
    
    var modelName = config.keyFieldConfig.definition && config.keyFieldConfig.definition.options ? config.keyFieldConfig.definition.options['recordModel'] : "Tinebase_Config_KeyFieldRecord",
        modelParts = modelName.split('_'),
        recordClass = Tine[modelParts[0]] && Tine[modelParts[0]]['Model'] && Tine[modelParts[0]]['Model'][modelParts[2]] ? Tine[modelParts[0]]['Model'][modelParts[2]] : null;
    
    config.fields = recordClass ? recordClass : ['id', 'value', 'icon', 'system', 'i18nValue'];
    
    Tine.Tinebase.widgets.keyfield.Store.superclass.constructor.call(this, config);
};

/**
 * @namespace   Tine.Tinebase.widgets.keyfield
 * @class       Tine.Tinebase.widgets.keyfield.Store
 * @extends     Ext.data.JsonStore
 */
Ext.extend(Tine.Tinebase.widgets.keyfield.Store, Ext.data.JsonStore, {
    /**
     * @cfg {String/Application} app
     */
    app: null,
    
    /**
     * @cfg {String} keyFieldName 
     * name of key field
     */
    keyFieldName: null,
    
    /**
     * @property keyFieldConfig
     * @type Object
     */
    keyFieldConfig: null
});

/**
 * manages key field stores
 * 
 * @namespace   Tine.Tinebase.widgets.keyfield
 * @class       Tine.Tinebase.widgets.keyfield.StoreMgr
 * @singleton
 */
Tine.Tinebase.widgets.keyfield.StoreMgr = function(){
    var stores = {};
    
    return {
        /**
         * returns key field record store
         * 
         * @param {String/Application} app
         * @param {String} keyFieldName 
         * @return Ext.data.Store
         */
        get: function(app, keyFieldName) {
            var appName = Ext.isString(app) ? app : app.appName,
                key = appName + '_' + keyFieldName;
                
            if (! stores[key]) {
                stores[key] = new Tine.Tinebase.widgets.keyfield.Store({
                    app: app,
                    keyFieldName: keyFieldName
                });
            }
            
            return stores[key];
        }
    };
}();
/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
Ext.ns('Tine.Tinebase.widgets.keyfield');

/**
 * key field combo
 * 
 * @namespace   Tine.Tinebase.widgets.keyfield
 * @class       Tine.Tinebase.widgets.keyfield.ComboBox
 * @extends     Ext.form.ComboBox
 */
Tine.Tinebase.widgets.keyfield.ComboBox = Ext.extend(Ext.form.ComboBox, {
    /**
     * @cfg {String/Application} app
     */
    app: null,
    
    /**
     * @cfg {String} keyFieldName 
     * name of key field
     */
    keyFieldName: null,
    
    /**
     * @cfg {Boolean} showIcon
     * show icon in list and value
     */
    showIcon: true,
    
    /**
     * sort by a field
     * 
     * @cfg {String} sortBy
     */
    sortBy: null,
    
    /* begin config */
    blurOnSelect  : true,
    expandOnFocus : true,
    mode          : 'local',
    displayField  : 'i18nValue',
    valueField    : 'id',
    /* end config */
    
    initComponent: function() {
        this.app = Ext.isString(this.app) ? Tine.Tinebase.appMgr.get(this.app) : this.app;
    
        // get keyField config
        this.keyFieldConfig = this.app.getRegistry().get('config')[this.keyFieldName];
    
        if (! this.value && (this.keyFieldConfig && Ext.isObject(this.keyFieldConfig.value) && this.keyFieldConfig.value.hasOwnProperty('default'))) {
            this.value = this.keyFieldConfig.value['default'];
            //@todo see if keyFieldConfig.definition has a default
        }
        
        this.store = Tine.Tinebase.widgets.keyfield.StoreMgr.get(this.app, this.keyFieldName);
        if (this.sortBy) {
            this.store.sort(this.sortBy);
        }
        
        this.showIcon = this.showIcon && this.store.find('icon', /^.+$/) > -1;
        
        this.initTpl();
        Tine.Tinebase.widgets.keyfield.ComboBox.superclass.initComponent.call(this);
    },
    
    initTpl: function() {
        if (this.showIcon) {
            this.tpl = '<tpl for="."><div class="x-combo-list-item"><img src="{icon}" class="tine-keyfield-icon"/>{' + this.displayField + '}</div></tpl>';
        }
    }
});

Ext.reg('widget-keyfieldcombo', Tine.Tinebase.widgets.keyfield.ComboBox);/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
Ext.ns('Tine.Tinebase.widgets.keyfield');

/**
 * manages key field renderers
 * 
 * @namespace   Tine.Tinebase.widgets.keyfield
 * @class       Tine.Tinebase.widgets.keyfield.Renderer
 * @singleton
 */
Tine.Tinebase.widgets.keyfield.Renderer = function(){
    var renderers = {};
    
    return {
        /**
         * returns key field record renderer
         * 
         * @param {String/Application}  app
         * @param {String}              keyFieldName 
         * @param {String}              what pipe seperated field with text|icon
         * @return Ext.data.Store
         */
        get: function(app, keyFieldName, what) {
            var appName = Ext.isString(app) ? app : app.appName,
                app = Tine.Tinebase.appMgr.get(appName),
                store = Tine.Tinebase.widgets.keyfield.StoreMgr.get(app, keyFieldName),
                what = what ? what : 'text|icon',
                whatParts = what.split('|'),
                key = appName + keyFieldName + what;
                
            if (! renderers[key]) {
                renderers[key] = function(id) {
                    if (! id) return "";
                    var record = store.getById(id),
                        i18nValue = record ? record.get('i18nValue') : app.i18n._hidden(id),
                        icon = record ? record.get('icon') : null,
                        string = '';
                        
                    if (whatParts.indexOf('icon') > -1 && icon) {
                        string = string + '<img src="' + icon + '" class="tine-keyfield-icon" ext:qtip="' + Ext.util.Format.htmlEncode(i18nValue) + '" />';
                    }
                        
                    if (whatParts.indexOf('text') > -1 && i18nValue) {
                        string = string + Ext.util.Format.htmlEncode(i18nValue);
                    }
                    
                    return string;
                }
            }
            
            return renderers[key];
        },
        
        /**
         * render a given value
         * 
         * @param {String/Application}  app
         * @param {String}              keyFieldName 
         * @return Ext.data.Store
         */
        render: function(app, keyFieldName, id) {
            var renderer = this.get(app, keyFieldName);
            
            return renderer(id);
        },
        
        /**
         * register a custom renderer
         * 
         * @param {String/Application}  app
         * @param {String}              keyFieldName 
         * @param {Function}            renderer
         */
        register: function(app, keyFieldName, renderer) {
            var appName = Ext.isString(app) ? app : app.appName,
                key = appName + '_' + keyFieldName;
                
            renderers[key] = renderer;
        }
    }
}();
/**
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine.widgets');

/**
 * lang chooser widget
 *
 * @namespace   Tine.widgets
 * @class       Tine.widgets.LangChooser
 * @extends     Ext.form.ComboBox
 */
Tine.widgets.LangChooser = Ext.extend(Ext.form.ComboBox, {
    
    /**
     * @cfg {Sring}
     */
    fieldLabel: null,
    
    displayField: 'language',
    valueField: 'locale',
    triggerAction: 'all',
    editable: false,
    width: 100,
    listWidth: 200,
    
    initComponent: function () {
        this.value = Tine.Tinebase.registry.get('locale').language;
        this.fieldLabel = this.fieldLabel ? this.fieldLabel : _('Language');
        
        this.tpl = new Ext.XTemplate(
            '<tpl for=".">' +
                '<div class="x-combo-list-item">' +
                    '{language} <tpl if="region.length &gt; 1">{region}</tpl> [{locale}]' + 
                '</div>' +
            '</tpl>', {
                encode: function (value) {
                    return Ext.util.Format.htmlEncode(value);
                }
            }
        );
        
        this.store = new Ext.data.JsonStore({
            id: 'locale',
            root: 'results',
            totalProperty: 'totalcount',
            fields: Tine.Tinebase.Model.Language,
            baseParams: {
                method: 'Tinebase.getAvailableTranslations'
            }
        });
        Tine.widgets.LangChooser.superclass.initComponent.call(this);
        
        this.on('select', this.onLangSelect, this);
    },
    
    onLangSelect: function (combo, localeRecord, idx) {
        var currentLocale = Tine.Tinebase.registry.get('locale').locale;
        var newLocale = localeRecord.get('locale');
        
        if (newLocale !== currentLocale) {
            Ext.MessageBox.wait(_('setting new language...'), _('Please Wait'));
            
            Ext.Ajax.request({
                scope: this,
                params: {
                    method: 'Tinebase.setLocale',
                    localeString: newLocale,
                    saveaspreference: true,
                    setcookie: true
                },
                success: function (result, request) {
                    if (window.google && google.gears && google.gears.localServer) {
                        var pkgStore = google.gears.localServer.openStore('tine20-package-store');
                        if (pkgStore) {
                            google.gears.localServer.removeStore('tine20-package-store');
                        }
                    }
                    window.location.reload(true);
                }
            });
        }
    }
});
Ext.reg('tinelangchooser', Tine.widgets.LangChooser);

/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
 Ext.ns('Tine', 'Tine.widgets');
 
 Tine.widgets.ActionUpdater = function(config) {
    config = config || {};
    var actions = config.actions || [];
    config.actions = [];
    
    Ext.apply(this, config);
    this.addActions(actions);
 };

 Tine.widgets.ActionUpdater.prototype = {
    /**
     * @cfg {Array|Toolbar} actions
     * all actions to update
     */
    actions: null,
    
    /**
     * @cfg {Bool} evalGrants
     * should grants of a grant-aware records be evaluated (defaults to true)
     */
    evalGrants: true,
    
    /**
     * @cfg {String} grantsProperty
     * property in the record to find the grants in
     */
    grantsProperty: 'account_grants',
    
    /**
     * @cfg {String} containerProperty
     * container property of records (if set, grants are expected to be  a property of the container)
     */
    containerProperty: 'container_id',
    
    /**
     * add actions to update
     * @param {Array|Toolbar} actions
     */
    addActions: function(actions) {
        switch (typeof(actions)) {
            case 'object':
                if (typeof(actions.each) == 'function') {
                    actions.each(this.addAction, this);
                } else {
                    for (var action in actions) {
                        this.addAction(actions[action]);
                    }
                }
            break;
            case 'array':
                for (var i=0; i<actions.length; i++) {
                    this.addAction(actions[i]);
                }
            break;
        }
    },
    
    /**
     * add a single action to update
     * @param {Ext.Action} action
     */
    addAction: function(action) {
        // if action has to initialConfig it's no Ext.Action!
        if (action && action.initialConfig) {
            
            // in some cases our actionUpdater config is not in the initial config
            // this happens for direct extensions of button class, like the notes button
            if (action.requiredGrant) {
                Ext.applyIf(action.initialConfig, {
                    requiredGrant: action.requiredGrant,
                    actionUpdater: action.actionUpdater,
                    allowMultiple: action.allowMultiple,
                    singularText: action.singularText,
                    pluralText: action.pluralText,
                    translationObject: action.translationObject
                });
            }
            
            this.actions.push(action);
        }
    },
    
    /**
     * performs the actual update
     * @param {Array|SelectionModel} records
     */
    updateActions: function(records) {
        var isFilterSelect = false;
        if (typeof(records.getSelections) == 'function') {
            isFilterSelect = records.isFilterSelect;
            records = records.getSelections();
        } else if (typeof(records.beginEdit) == 'function') {
            records = [records];
        }
        
        var grants = this.getGrantsSum(records);
        
        this.each(function(action) {
            // action updater opt-in fn has precedence over generic action updater!
            var actionUpdater = action.actionUpdater || action.initialConfig.actionUpdater;
            if (typeof(actionUpdater) == 'function') {
                var scope = action.scope || action.initialConfig.scope || window;
                actionUpdater.call(scope, action, grants, records);
            } else {
                this.defaultUpdater(action, grants, records, isFilterSelect);
            }
        }, this);
        
    },
    
    /**
     * default action updater
     * 
     * - sets disabled status based on grants and required grant
     * - sets disabled status based on allowMutliple
     * - sets action text (signular/plural text)
     * 
     * @param {Ext.Action} action
     * @param {Object} grants
     * @param {Object} records
     * @param {Boolean} isFilterSelect
     */
    defaultUpdater: function(action, grants, records, isFilterSelect) {
        var nCondition = records.length != 0 && (records.length > 1 ? action.initialConfig.allowMultiple : true),
            mCondition = records && records.length > 1,
            grantCondition = (! this.evalGrants) || grants[action.initialConfig.requiredGrant];

        // @todo discuss if actions are only to be touched if requiredGrant is set
        if (action.initialConfig.requiredGrant && action.initialConfig.requiredGrant != 'addGrant') {
            action.setDisabled(! (grantCondition && nCondition));
        }
        
        // disable on filter selection if required
        if(action.disableOnFilterSelection && isFilterSelect) {
            action.setDisabled(true);
            return;
        }
        
        if (nCondition) {
            if (mCondition) {
                if (action.initialConfig.requiredMultipleRight) {
                    var right = action.initialConfig.requiredMultipleRight.split('_');
                    if (right.length == 2) {
                        action.setDisabled(!Tine.Tinebase.common.hasRight(right[0], action.initialConfig.scope.app.name, right[1]));
                    } else {
                        Tine.log.debug('multiple edit right was not properly applied');
                    }
                }
                if(action.initialConfig.requiredMultipleGrant) {
                    var hasRight = true;
                    if(Ext.isArray(records)) {
                        Ext.each(records, function(record) {
                            if(record.get('container_id') && record.get('container_id').account_grants) {
                                hasRight = (hasRight && (record.get('container_id').account_grants[action.initialConfig.requiredMultipleGrant]));
                            } else {
                                return false;
                            }
                        }, this);
                        action.setDisabled(! hasRight);
                    }
                }
            }
            if (action.initialConfig.singularText && action.initialConfig.pluralText && action.initialConfig.translationObject) {
                var text = action.initialConfig.translationObject.n_(action.initialConfig.singularText, action.initialConfig.pluralText, records.length);
                action.setText(text);
            }
        }
    },
    
    /**
     * Calls the specified function for each action
     * 
     * @param {Function} fn The function to call. The action is passed as the first parameter.
     * Returning <tt>false</tt> aborts and exits the iteration.
     * @param {Object} scope (optional) The scope in which to call the function (defaults to the action).
     */
    each: function(fn, scope) {
        for (var i=0; i<this.actions.length; i++) {
            if (fn.call(scope||this.actions[i], this.actions[i]) === false) break;
        }
    },
    
    /**
     * calculats the grants sum of the given record(s)
     * 
     * @param  {Array}  records
     * @return {Object} grantName: sum
     */
    getGrantsSum: function(records) {

        var defaultGrant = records.length == 0 ? false : true;
        var grants = {
            addGrant:       defaultGrant,
            adminGrant:     defaultGrant,
            deleteGrant:    defaultGrant,
            editGrant:      defaultGrant,
            readGrant:      defaultGrant,
            exportGrant:    defaultGrant,
            syncGrant:      defaultGrant
        };
        
        if (! this.evalGrants) {
            return grants;
        }
        
        var recordGrants;
        for (var i=0; i<records.length; i++) {
            recordGrants = this.containerProperty ? 
                records[i].get(this.containerProperty)[this.grantsProperty] : this.grantsProperty ? 
                records[i].get(this.grantsProperty) : records[i].data;
            
            for (var grant in grants) {
                if (grants.hasOwnProperty(grant) && recordGrants && recordGrants.hasOwnProperty(grant)) {
                    grants[grant] = grants[grant] & recordGrants[grant];
                }
            }
        }
        // if calculated admin right is true, overwrite all grants with true
        if(grants.adminGrant) {
            for (var grant in grants) {
                grants[grant] = true;
            }
        }
        
        return grants;
    }
};
 
/**
 * sets text and disabled status of a set of actions according to the grants 
 * of a set of records
 * @legacy use ActionUpdater Obj. instead!
 * 
 * @param {Array|SelectionModel} records
 * @param {Array|Toolbar}        actions
 * @param {String}               containerField
 * @param {Bool}                 skipGrants evaluation
 */
Tine.widgets.actionUpdater = function(records, actions, containerField, skipGrants) {
    if (!containerField) {
        containerField = 'container_id';
    }

    if (typeof(records.getSelections) == 'function') {
        records = records.getSelections();
    } else if (typeof(records.beginEdit) == 'function') {
        records = [records];
    }
    
    // init grants
    var defaultGrant = records.length == 0 ? false : true;
    var grants = {
        addGrant:    defaultGrant,
        adminGrant:  defaultGrant,
        deleteGrant: defaultGrant,
        editGrant:   defaultGrant,
        exportGrant: defaultGrant,
        readGrant:   defaultGrant,
        syncGrant:   defaultGrant
    };
    
    // calculate sum of grants
    for (var i=0; i<records.length; i++) {
        var recordGrants = records[i].get(containerField) ? records[i].get(containerField).account_grants : {};
        for (var grant in grants) {
            grants[grant] = grants[grant] & recordGrants[grant];
        }
    }
    
    /**
     * action iterator
     */
    var actionIterator = function(action) {
        var initialConfig = action.initialConfig;
        if (initialConfig) {
            
            // happens for direct extensions of button class, like the notes button
            if (action.requiredGrant) {
                initialConfig = {
                    requiredGrant: action.requiredGrant,
                    allowMultiple: action.allowMultiple,
                    singularText: action.singularText,
                    pluralText: action.pluralText,
                    translationObject: action.translationObject
                };
            }
            
            // NOTE: we don't handle add action for the moment!
            var requiredGrant = initialConfig.requiredGrant;
            if (requiredGrant && requiredGrant != 'addGrant') {
                var enable = skipGrants || grants[requiredGrant];
                if (records.length > 1 && ! initialConfig.allowMultiple) {
                    enable = false;
                }
                if (records.length == 0) {
                    enable = false;
                }
                
                action.setDisabled(!enable);
                if (initialConfig.singularText && initialConfig.pluralText && initialConfig.translationObject) {
                    var text = initialConfig.translationObject.n_(initialConfig.singularText, initialConfig.pluralText, records.length);
                    action.setText(text);
                }
            }
        }
    };
    
    /**
     * call action iterator
     */
    switch (typeof(actions)) {
        case 'object':
            if (typeof(actions.each) == 'function') {
                actions.each(actionIterator, this);
            } else {
                for (var action in actions) {
                    actionIterator(actions[action]);
                }
            }
        break;
        case 'array':
            for (var i=0; i<actions.length; i++) {
                actionIterator(actions[i]);
            }
        break;
    }
    
};
/**
 * Tine 2.0
 * 
 * @package     Tine
 * @subpackage  Widgets
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * @deprecated  use EditDialog instead. This class will be removed when all
 *              dialogs are moved to the EditDialog widget
 */

/*global Ext, Tine*/

Ext.ns('Tine.widgets', 'Tine.widgets.dialog');

/**
 * Generic 'Edit Record' dialog
 *
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.EditRecord
 * @extends     Ext.FormPanel
 */
Tine.widgets.dialog.EditRecord = Ext.extend(Ext.FormPanel, {
    /**
     * @cfg {Array} additional toolbar items
     */
    tbarItems: false,
    /**
     * @cfg {String} internal app name
     */
    appName: null,
    /**
     * @cfg {String} translated container item name
     */
    //containerItemName: 'record',
    /**
     * @cfg {String} translated container name
     */
    containerName: 'container',
    /**
     * @cfg {string} containerName
     * name of container (plural)
     */
    containersName: 'containers',
    /**
     * @cfg {String} name of the container property
     */
    containerProperty: 'container_id',
    /**
     * @cfg {Bool} show container selector in bottom area
     */
    showContainerSelector: false,
    /**
     * @cfg {Object} handlerScope scope, the defined handlers will be executed in 
     */
    handlerScope: null,
    /**
     * @cfg {function} handler for generic save and close action
     */
    handlerSaveAndClose: null,
    /**
     * @cfg {function} handler for generic save and close action
     */
    handlerApplyChanges: null,
    /**
     * @cfg {function} handler for generic save and close action
     */
    handlerCancel: null,
    /**
     * @cfg {String} layout of the containing window
     */
    windowLayout: 'border',
    /**
     * @property {Ext.Window|Ext.ux.PopupWindow|Ext.Air.Window}
     */
    window: null,
    
    // private
    bodyStyle: 'padding:5px',
    //layout: 'fit',
    anchor: '100% 100%',
    region: 'center',
    deferredRender: false,
    buttonAlign: 'right',
    cls: 'tw-editdialog',
    
    //private
    initComponent: function () {
        this.addEvents(
            /**
             * @event cancel
             * Fired when user pressed cancel button
             */
            'cancel',
            /**
             * @event saveAndClose
             * Fired when user pressed OK button
             */
            'saveAndClose',
            /**
             * @event update
             * @desc  Fired when the record got updated
             * @param {Ext.data.record} data data of the entry
             */
            'update',
            /**
             * @event apply
             * Fired when user pressed apply button
             */
            'apply');
        
        this.initHandlers();
        this.initActions();
        this.initButtons();
        
        Tine.widgets.dialog.EditRecord.superclass.initComponent.call(this);
    },
    
    /**
     * @private
     */
    onRender : function (ct, position) {
        Tine.widgets.dialog.EditRecord.superclass.onRender.call(this, ct, position);
        
        if (this.showContainerSelector) {
            this.recordContainerEl = this.footer.first().insertFirst({tag: 'div', style: {'position': 'relative', 'top': '4px', 'float': 'left'}});
            var ContainerForm = new Tine.widgets.container.selectionComboBox({
                id: this.appName + 'EditRecordContainerSelector',
                fieldLabel: _('Saved in'),
                width: 300,
                name: this.containerProperty,
                //itemName: this.containerItemName,
                containerName: this.containerName,
                containersName: this.containersName,
                appName: this.appName
            });
            this.getForm().add(ContainerForm);
            
            var containerSelect = new Ext.Panel({
                layout: 'form',
                border: false,
                renderTo: this.recordContainerEl,
                bodyStyle: {'background-color': '#F0F0F0'},
                items: ContainerForm
            });
        }
    },
    
    /**
     * Init actions
     */
    initActions: function () {
        this.action_saveAndClose = new Ext.Action({
            requiredGrant: 'editGrant',
            text: _('Ok'),
            //tooltip: 'Save changes and close this window',
            minWidth: 70,
            //handler: this.onSaveAndClose,
            handler: this.handlerSaveAndClose,
            iconCls: 'action_saveAndClose',
            scope: this.handlerScope
        });
    
        this.action_applyChanges = new Ext.Action({
            requiredGrant: 'editGrant',
            text: _('Apply'),
            //tooltip: 'Save changes',
            minWidth: 70,
            handler: this.handlerApplyChanges,
            iconCls: 'action_applyChanges',
            scope: this.handlerScope
            //disabled: true
        });
        
        this.action_cancel = new Ext.Action({
            text: _('Cancel'),
            //tooltip: 'Reject changes and close this window',
            minWidth: 70,
            handler: this.handlerCancel,
            iconCls: 'action_cancel',
            scope: this.handlerScope
        });
        
        this.action_delete = new Ext.Action({
            requiredGrant: 'deleteGrant',
            text: _('delete'),
            minWidth: 70,
            handler: this.handlerDelete,
            iconCls: 'action_delete',
            scope: this.handlerScope,
            disabled: true
        });
    },
    
    /**
     * @private
     */
    initButtons: function () {
        var genericButtons = [
            this.action_delete
        ];
        
        //this.tbarItems = genericButtons.concat(this.tbarItems);
        
        this.buttons = [
            //this.action_applyChanges,
            this.action_cancel,
            this.action_saveAndClose
        ];
       
        if (this.tbarItems) {
            this.tbar = new Ext.Toolbar({
                items: this.tbarItems
            });
        }
    },
    
    /**
     * @private
     */
    initHandlers: function () {
        this.handlerScope = this.handlerScope ? this.handlerScope : this;
        
        this.handlerSaveAndClose = this.handlerSaveAndClose ? this.handlerSaveAndClose : function (e, button) {
            this.handlerApplyChanges(e, button, true);
        };
        
        this.handlerCancel = this.handlerCancel ? this.handlerCancel : this.closeWindow;
    },
    
    /**
     * update (action updateer) top and bottom toolbars
     */
    updateToolbars: function (record, containerField) {
        var actions = [
            this.action_saveAndClose,
            this.action_applyChanges,
            this.action_delete,
            this.action_cancel
        ];
        Tine.widgets.actionUpdater(record, actions, containerField);
        Tine.widgets.actionUpdater(record, this.tbarItems, containerField);
    },
    
    /**
     * get top toolbar
     */
    getToolbar: function () {
        return this.getTopToolbar();
    },
    
    /**
     * @private
     */
    onCancel: function () {
        this.fireEvent('cancel');
        this.purgeListeners();
    },
    
    /**
     * @private
     */
    onSaveAndClose: function () {
        this.fireEvent('saveAndClose');
    },
    
    /**
     * @private
     */
    onApply: function () {
        this.fireEvent('apply');
    },
    
    /**
     * helper function to close window
     */
    closeWindow: function () {
        this.window.close();
    }
});

Ext.reg('tineeditrecord', Tine.widgets.dialog.EditRecord);

/*
 * Tine 2.0
 * 
 * @package     Tine
 * @subpackage  Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine.widgets');

/**
 * check if newer version of Tine 2.0 is available
 * 
 * @namespace   Tine.widgets
 * @class       Tine.widgets.VersionCheck
 * @constructor
 */
Tine.widgets.VersionCheck = function() {
    
    var ds = new Ext.data.Store({
        proxy: new Ext.data.ScriptTagProxy({
            url: 'https://versioncheck.officespot20.com/versionCheck/versionCheck.php'
        }),
        reader: new Ext.data.JsonReader({
            root: 'version'
        }, ['codeName', 'packageString', 'releaseTime', 'critical', 'build'])
    });
    ds.on('load', function(store, records) {
        if (! Tine.Tinebase.registry.get('version')) {
            return false;
        }
        
        var version = records[0];
        
        var local = Date.parseDate(Tine.Tinebase.registry.get('version').releasetime, Date.patterns.ISO8601Long);
        var latest = Date.parseDate(version.get('releasetime'), Date.patterns.ISO8601Long);
        
        if (latest > local && Tine.Tinebase.common.hasRight('run', 'Tinebase')) {
            if (version.get('critical') == true) {
                Ext.MessageBox.show({
                    title: _('New version of Tine 2.0 available'), 
                    msg: String.format(_('Version "{0}" of Tine 2.0 is available.'), version.get('codeName')) + "\n" +
                                 _("It's a critical update and must be installed as soon as possible!"),
                    width: 500,
                    buttons: Ext.Msg.OK,
                    icon: Ext.MessageBox.ERROR
                });
            } else {
                Ext.MessageBox.show({
                    title: _('New version of Tine 2.0 available'),
                    msg: String.format(_('Version "{0}" of Tine 2.0 is available.'), version.get('codeName')) + "\n" +
                                 _('Please consider updating!'),
                    width: 400,
                    buttons: Ext.Msg.OK,
                    icon: Ext.MessageBox.INFO
                });
            }
        }
    }, this);
    
    ds.load({params: {version: Ext.util.JSON.encode(Tine.Tinebase.registry.get('version'))}});
};
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
Ext.ns('Tine.widgets', 'Tine.widgets.dialog');

/**
 * Alarm Panel
 * 
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.AlarmPanel
 * @extends     Ext.Panel
 * 
 * @TODO add validation (alarm after alarmtime)
 */
Tine.widgets.dialog.AlarmPanel = Ext.extend(Ext.Panel, {
    
    /**
     * @cfg {string} name of alarm field
     */
    recordAlarmField: 'dtstart',
    
    //private
    layout      : 'fit',
    border      : true,
    frame       : true,
    
    
    initComponent: function() {
        this.title = _('Alarms');
        
        this.alarmOptions = [
            ['0',       _('0 minutes before')],
            ['5',       _('5 minutes before')],
            ['15',      _('15 minutes before')],
            ['30',      _('30 minutes before')],
            ['60',      _('1 hour before')],
            ['120',     _('2 hours before')],
            ['720',     _('12 hours before')],
            ['1440',    _('1 day before')],
            ['2880',    _('2 days before')],
            ['custom',  _('Custom Datetime')]
        ];
        
        this.items = this.alarmGrid = new Tine.widgets.grid.QuickaddGridPanel({
            layout: 'fit',
            autoExpandColumn: 'alarm_time',
            quickaddMandatory: 'minutes_before',
            frame: false,
            recordClass: Tine.Tinebase.Model.Alarm,
            onNewentry: this.onNewentry.createDelegate(this),
            cm: new Ext.grid.ColumnModel([{
                id: 'minutes_before', 
                header: _('Alarm Time'), 
                dataIndex: 'minutes_before', 
                width: 200, 
                hideable: false, 
                sortable: true,
                quickaddField: new Ext.form.ComboBox({
                    triggerAction   : 'all',
                    lazyRender      : false,
                    editable        : false,
                    mode            : 'local',
                    forceSelection  : true,
                    store: this.alarmOptions,
                    listeners       : {
                        scope: this,
                        beforeselect: function(combo, record, index) {
                            var alarmFieldDate = this.record ? this.record.get(this.recordAlarmField) : null;
                            
                            this.alarmTimeQuickAdd.setDisabled(record.data.field1 != 'custom');
                            
                            // preset a usefull value
                            if (Ext.isDate(alarmFieldDate) && record.data.field1 == 'custom') {
                                this.alarmTimeQuickAdd.setValue(alarmFieldDate.add(Date.MINUTE, -1 * Ext.isNumber(combo.getValue()) ? combo.getValue() : 0));
                            }
                            
                            if (record.data.field1 != 'custom') {
                                this.alarmTimeQuickAdd.setValue();
                            }
                        }
                    }
                }),
                editor: new Ext.form.ComboBox({
                    triggerAction   : 'all',
                    lazyRender      : false,
                    editable        : false,
                    mode            : 'local',
                    forceSelection  : true,
                    allowBlank      : false,
                    expandOnFocus   : true,
                    blurOnSelect    : true,
                    store: this.alarmOptions
                }),
                renderer: this.minutesBeforeRenderer.createDelegate(this)
            }, {
                id: 'alarm_time', 
                dataIndex: 'alarm_time', 
                hideable: false, 
                sortable: false,
                renderer: this.alarmTimeRenderer.createDelegate(this),
                quickaddField: this.alarmTimeQuickAdd = new Ext.ux.form.DateTimeField({
                    allowBlank      : true
                }),
                editor: new Ext.ux.form.DateTimeField({
                    allowBlank      : false
                })
            }])
        });
        
        this.alarmGrid.on('beforeedit', this.onBeforeEdit, this);
        this.alarmGrid.on('afteredit', this.onAfterEdit, this);
        Tine.widgets.dialog.AlarmPanel.superclass.initComponent.call(this);
        
    },
    
    onNewentry: function(recordData) {
        if (recordData.minutes_before == 'custom' && ! Ext.isDate(recordData.alarm_time)) {
            return false;
        }
        
        Tine.widgets.grid.QuickaddGridPanel.prototype.onNewentry.apply(this.alarmGrid, arguments);
        this.alarmGrid.store.sort('minutes_before', 'ASC');
    },
    
    onBeforeEdit: function(o) {
        if (o.field == 'alarm_time') {
            var minutesBefore = o.record.get('minutes_before'),
                alarmTime = o.record.get('alarm_time');
            
            // allow status setting if status authkey is present
            if (minutesBefore != 'custom') {
                o.cancel = true;
            } else if (alarmTime && ! Ext.isDate(alarmTime)) {
                var alarmDate = Date.parseDate(alarmTime, Date.patterns.ISO8601Long);
                o.record.set('alarm_time', '');
                o.record.set('alarm_time', alarmDate);
            }
        }
    },
    
    onAfterEdit: function(o) {
        if (o.record.get('minutes_before') == 'custom' && ! Ext.isDate(o.record.get('alarm_time'))) {
            var alarmFieldDate = this.record ? this.record.get(this.recordAlarmField) : null;
            
            o.record.set('alarm_time', Ext.isDate(alarmFieldDate) ? alarmFieldDate.clone() : new Date());
        }
        
        if (o.record.get('minutes_before') != 'custom') {
            o.record.set('alarm_time', '');
        }
        
        this.alarmGrid.store.sort('minutes_before', 'ASC');
    },
    
    minutesBeforeRenderer: function(value) {
        var string = null;
        Ext.each(this.alarmOptions, function(opt) {
            if (opt[0] == value) {
                string = opt[1];
                return false;
            }
        });
        
        if (! string ) {
            string = String.format(_('{0} minutes before'), value);
        }
        
        return string;
    },
    
    alarmTimeRenderer: function(value, metaData, record) {
        return record.get('minutes_before') == 'custom' ? Tine.Tinebase.common.dateTimeRenderer.apply(this, arguments) : '';
    },
    
    /**
     * on record load event
     * 
     * @param {Object} record
     */
    onRecordLoad: function(record) {
        this.record = record;
        this.alarmGrid.store.removeAll();
        this.alarmGrid.setStoreFromArray(record.get('alarms') || []);
        
        this.alarmGrid.store.sort('minutes_before', 'ASC');
    },

    /**
     * on record update event
     * 
     * @param {Object} record
     */
    onRecordUpdate: function(record) {
        // we need to initialze alarms because stringcompare would detect no change of the arrays
        record.set('alarms', '');
        record.set('alarms', this.alarmGrid.getFromStoreAsArray());
    },
    
    /**
     * disable contents not panel
     */
    setDisabled: function(v) {
        this.el[v ? 'mask' : 'unmask']();
    }
});

/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.dialog');

/**
 * Generic 'Edit Record' dialog
 * Base class for all 'Edit Record' dialogs
 * 
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.EditDialog
 * @extends     Ext.FormPanel
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @constructor
 * @param {Object} config The configuration options.
 */

Tine.widgets.dialog.EditDialog = Ext.extend(Ext.FormPanel, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     * instance of the app object (required)
     */
    app: null,
    /**
     * @cfg {String} mode
     * Set to 'local' if the EditDialog only operates on this.record (defaults to 'remote' which loads and saves using the recordProxy)
     */
    mode : 'remote',
    /**
     * @cfg {Array} tbarItems
     * additional toolbar items (defaults to false)
     */
    tbarItems: false,
    /**
     * @cfg {String} appName
     * internal/untranslated app name (required)
     */
    appName: null,
    /**
     * the modelName (filled by application starter)
     * @type 
     */
    modelName: null,
    /**
     * @cfg {Ext.data.Record} recordClass
     * record definition class  (required)
     */
    recordClass: null,
    /**
     * @cfg {Ext.data.DataProxy} recordProxy
     */
    recordProxy: null,
    /**
     * @cfg {Bool} showContainerSelector
     * show container selector in bottom area
     */
    showContainerSelector: null,
    /**
     * @cfg {Bool} evalGrants
     * should grants of a grant-aware records be evaluated (defaults to true)
     */
    evalGrants: true,
    /**
     * @cfg {Ext.data.Record} record
     * record in edit process.
     */
    record: null,

    /**
     * @cfg {String} saveAndCloseButtonText
     * text of save and close button
     */
    saveAndCloseButtonText: '',
    /**
     * @cfg {String} cancelButtonText
     * text of cancel button
     */
    cancelButtonText: '',
    
    /**
     * @cfg {Boolean} copyRecord
     * copy record
     */
    copyRecord: false,
    
    /**
     * @cfg {Boolean} doDuplicateCheck
     * do duplicate check when saving record (mode remote only)
     */
    doDuplicateCheck: true,
    
    /**
     * required grant for apply/save
     * @type String
     */
    editGrant: 'editGrant',

    /**
     * when a record has the relations-property the relations-panel can be disabled here
     * @cfg {Boolean} hideRelationsPanel
     */
    hideRelationsPanel: false,
    
    /**
     * ignore relations to given php-class names in the relation grid
     * @type {Array}
     */
    ignoreRelatedModels: null,
    
    /**
     * dialog is currently saving data
     * @type Boolean
     */
    saving: false,
    
    /**
     * @property window {Ext.Window|Ext.ux.PopupWindow|Ext.Air.Window}
     */
    /**
     * @property {Number} loadRequest 
     * transaction id of loadData request
     */
    /**
     * @property loadMask {Ext.LoadMask}
     */
    
    /**
     * @property containerSelectCombo {Tine.widgets.container.selectionComboBox}
     */
    containerSelectCombo: null,
    
    /**
     * If set, these fields are readOnly (when called dependent to related record)
     * json-encoded Array of Object
     * @type String
     */
    fixedFields: null,

    // private
    bodyStyle:'padding:5px',
    layout: 'fit',
    border: false,
    cls: 'tw-editdialog',
    anchor:'100% 100%',
    deferredRender: false,
    buttonAlign: null,
    bufferResize: 500,
    // the relationsPanel
    relationsPanel: null,
    // Array of Relation Pickers
    relationPickers: null,
    //private
    initComponent: function() {
        this.addEvents(
            /**
             * @event cancel
             * Fired when user pressed cancel button
             */
            'cancel',
            /**
             * @event saveAndClose
             * Fired when user pressed OK button
             */
            'saveAndClose',
            /**
             * @event update
             * @desc  Fired when the record got updated
             * @param {Json String} data data of the entry
             * @pram  {String} this.mode
             */
            'update',
            /**
             * @event apply
             * Fired when user pressed apply button
             */
            'apply',
            /**
             * @event load
             * @param {Tine.widgets.dialog.EditDialog} this
             * @param {Tine.data.Record} record which got loaded
             * @param {Function} ticket function for async defer
             * Fired when record is loaded
             */
            'load',
            /**
             * @event save
             * @param {Tine.widgets.dialog.EditDialog} this
             * @param {Tine.data.Record} record which got loaded
             * @param {Function} ticket function for async defer
             * Fired when remote record is saving
             */
            'save'
        );
        
        if(! this.recordClass && this.modelName) {
            this.recordClass = Tine[this.appName].Model[this.modelName];
        }
        
        if (this.recordClass) {
            this.appName    = this.appName    ? this.appName    : this.recordClass.getMeta('appName');
            this.modelName  = this.modelName  ? this.modelName  : this.recordClass.getMeta('modelName');
        }
        
        if (! this.app) {
            this.app = Tine.Tinebase.appMgr.get(this.appName);
        }
        
        if(! this.windowNamePrefix) {
            this.windowNamePrefix = this.modelName + 'EditWindow_';
        }
        
        Tine.log.debug('initComponent: appName: ', this.appName);
        Tine.log.debug('initComponent: modelName: ', this.modelName);
        Tine.log.debug('initComponent: app: ', this.app);
        
        // init some translations
        if (this.app.i18n && this.recordClass !== null) {
            this.i18nRecordName = this.app.i18n.n_hidden(this.recordClass.getMeta('recordName'), this.recordClass.getMeta('recordsName'), 1);
            this.i18nRecordsName = this.app.i18n._hidden(this.recordClass.getMeta('recordsName'));
        }
    
        if (! this.recordProxy && this.recordClass) {
            Tine.log.debug('no record proxy given, creating a new one...');
            this.recordProxy = new Tine.Tinebase.data.RecordProxy({
                recordClass: this.recordClass
            });
        }
        // init plugins
        this.plugins = Ext.isString(this.plugins) ? Ext.decode(this.plugins) : Ext.isArray(this.plugins) ? this.plugins.concat(Ext.decode(this.initialConfig.plugins)) : [];
        
        this.plugins.push(new Tine.widgets.customfields.EditDialogPlugin({}));
        this.plugins.push(this.tokenModePlugin = new Tine.widgets.dialog.TokenModeEditDialogPlugin({}));
        
        // init actions
        this.initActions();
        // init buttons and tbar
        this.initButtons();
        // init container selector
        this.initContainerSelector();
        // init record 
        this.initRecord();
        // get items for this dialog
        this.items = this.getFormItems();
        // init relations panel if relations are defined
        this.initRelationsPanel();

        Tine.widgets.dialog.EditDialog.superclass.initComponent.call(this);
        // set fields readOnly if set
        this.fixFields();
    },

    /**
     * fix fields (used for preselecting form fields when called in dependency to another record)
     * @return {Boolean}
     */
    fixFields: function() {
        if(this.fixedFields) {
            if(!this.rendered) {
                this.fixFields.defer(100, this);
                return false;
            }
            Ext.each(Ext.decode(this.fixedFields), function(prefill) {
                var field = this.getForm().findField(prefill.key);
                if(Ext.isFunction(this.recordClass.getField(prefill.key).type)) {
                    var foreignRecordClass = this.recordClass.getField(prefill.key).type;
                    var record = new foreignRecordClass(prefill.value);
                    field.selectedRecord = record;
                    field.setValue(prefill.value);
                    field.fireEvent('select');
                } else {
                    field.setValue(prefill.value);
                }
                field.disable();
            }, this);
        }
    },

    /**
     * init actions
     */
    initActions: function() {
        this.action_saveAndClose = new Ext.Action({
            requiredGrant: this.editGrant,
            text: (this.saveAndCloseButtonText != '') ? this.app.i18n._(this.saveAndCloseButtonText) : _('Ok'),
            minWidth: 70,
            ref: '../btnSaveAndClose',
            scope: this,
            // TODO: remove the defer when all subpanels use the deferByTicket mechanism
            handler: function() { this.onSaveAndClose.defer(500, this); },
            iconCls: 'action_saveAndClose'
        });
    
        this.action_applyChanges = new Ext.Action({
            requiredGrant: this.editGrant,
            text: _('Apply'),
            minWidth: 70,
            ref: '../btnApplyChanges',
            scope: this,
            handler: this.onApplyChanges,
            iconCls: 'action_applyChanges'
        });
        
        this.action_cancel = new Ext.Action({
            text: (this.cancelButtonText != '') ? this.app.i18n._(this.cancelButtonText) : _('Cancel'),
            minWidth: 70,
            scope: this,
            handler: this.onCancel,
            iconCls: 'action_cancel'
        });
        
        this.action_delete = new Ext.Action({
            requiredGrant: 'deleteGrant',
            text: _('delete'),
            minWidth: 70,
            scope: this,
            handler: this.onDelete,
            iconCls: 'action_delete',
            disabled: true
        });
    },
    
    /**
     * init buttons
     */
    initButtons: function() {
        this.fbar = [
            '->',
            this.action_cancel,
            this.action_saveAndClose
        ];
       
        if (this.tbarItems) {
            this.tbar = new Ext.Toolbar({
                items: this.tbarItems
            });
        }
    },
    
    /**
     * init container selector
     */
    initContainerSelector: function() {
        if (this.showContainerSelector) {
            this.containerSelectCombo = new Tine.widgets.container.selectionComboBox({
                id: this.app.appName + 'EditDialogContainerSelector-' + Ext.id(),
                fieldLabel: _('Saved in'),
                width: 300,
                listWidth: 300,
                name: this.recordClass.getMeta('containerProperty'),
                recordClass: this.recordClass,
                containerName: this.app.i18n.n_hidden(this.recordClass.getMeta('containerName'), this.recordClass.getMeta('containersName'), 1),
                containersName: this.app.i18n._hidden(this.recordClass.getMeta('containersName')),
                appName: this.app.appName,
                requiredGrant: this.evalGrants ? 'addGrant' : false,
                disabled: this.isContainerSelectorDisabled(),
                listeners: {
                    scope: this,
                    select: function() {    
                        // enable or disable save button dependent to containers account grants
                        var grants = this.containerSelectCombo.selectedContainer ? this.containerSelectCombo.selectedContainer.account_grants : {};
                        // on edit check editGrant, on add check addGrant
                        if (this.record.data.id) {  // edit if record has already an id
                            var disable = grants.hasOwnProperty('editGrant') ? ! grants.editGrant : false;
                        } else {
                            var disable = grants.hasOwnProperty('addGrant') ? ! grants.addGrant : false;
                        }
                        this.action_saveAndClose.setDisabled(disable);
                    }
                }
            });
            this.on('render', function() { this.getForm().add(this.containerSelectCombo); }, this);
            
            this.fbar = [
                _('Saved in'),
                this.containerSelectCombo
            ].concat(this.fbar);
        }
        
    },
    
    /**
     * checks if the container selector should be disabled (dependent on account grants of the container itself)
     * @return {}
     */
    isContainerSelectorDisabled: function() {
        if(this.record) {
            var cp = this.recordClass.getMeta('containerProperty'),
                container = this.record.data[cp],
                grants = (container && container.hasOwnProperty('account_grants')) ? container.account_grants : null,
                cond = false;
                
            // check grants if record already exists and grants should be evaluated
            if(this.evalGrants && this.record.data.id && grants) {
                cond = ! (grants.hasOwnProperty('editGrant') && grants.editGrant);
            }
            
            return cond;
        } else {
            return false;
        }
    },
    
    /**
     * init record to edit
     */
    initRecord: function() {
        Tine.log.debug('init record with mode: ' + this.mode);
        if (! this.record) {
            Tine.log.debug('creating new default data record');
            this.record = new this.recordClass(this.recordClass.getDefaultData(), 0);
        }
        
        if (this.mode !== 'local') {
            if (this.record && this.record.id) {
                this.loadRemoteRecord();
            } else {
                this.onRecordLoad();
            }
        } else {
            // note: in local mode we expect a valid record
            if (!Ext.isFunction(this.record.beginEdit)) {
                this.record = this.recordProxy.recordReader({responseText: this.record});
            }
            this.onRecordLoad();
        }
    },
    
    /**
     * load record via record proxy
     */
    loadRemoteRecord: function() {
        Tine.log.info('initiating record load via proxy');
        this.loadRequest = this.recordProxy.loadRecord(this.record, {
            scope: this,
            success: function(record) {
                this.record = record;
                this.onRecordLoad();
            }
        });
    },

    /**
     * copy record
     */
    doCopyRecord: function() {
        var omitFields = this.recordClass.getMeta('copyOmitFields') || [];
        // always omit id + notes
        omitFields = omitFields.concat(['id', 'notes']);
        
        var fieldsToCopy = this.recordClass.getFieldNames().diff(omitFields),
            recordData = Ext.copyTo({}, this.record.data, fieldsToCopy);

        this.record = new this.recordClass(recordData, 0);
    },
    
    /**
     * executed after record got updated from proxy
     */
    onRecordLoad: function() {
        // interrupt process flow until dialog is rendered
        if (! this.rendered) {
            this.onRecordLoad.defer(250, this);
            return;
        }
        Tine.log.debug('Tine.widgets.dialog.EditDialog::onRecordLoad() - Loading of the following record completed:');
        Tine.log.debug(this.record);
        
        if (this.copyRecord) {
            this.doCopyRecord();
            this.window.setTitle(String.format(_('Copy {0}'), this.i18nRecordName));
        } else {
            if (! this.record.id) {
                this.window.setTitle(String.format(_('Add New {0}'), this.i18nRecordName));
            } else {
                this.window.setTitle(String.format(_('Edit {0} "{1}"'), this.i18nRecordName, this.record.getTitle()));
            }
        }
        
        var ticketFn = this.onAfterRecordLoad.deferByTickets(this),
            wrapTicket = ticketFn();
        
        this.fireEvent('load', this, this.record, ticketFn);
        wrapTicket();
    },
    
    // finally load the record into the form
    onAfterRecordLoad: function() {
        var form = this.getForm();
        
        if(form) {
            form.loadRecord(this.record);
            form.clearInvalid();
        }
        
        if(this.record && this.record.hasOwnProperty('data') && Ext.isObject(this.record.data[this.recordClass.getMeta('containerProperty')])) {
            this.updateToolbars(this.record, this.recordClass.getMeta('containerProperty'));
        }
        if(this.loadMask) {
            this.loadMask.hide();
        }
    },
    
    /**
     * executed when record gets updated from form
     */
    onRecordUpdate: function() {
        var form = this.getForm();

        // merge changes from form into record
        form.updateRecord(this.record);
    },
    
    /**
     * @private
     */
    onRender : function(ct, position){
        Tine.widgets.dialog.EditDialog.superclass.onRender.call(this, ct, position);
        
        // generalized keybord map for edit dlgs
        new Ext.KeyMap(this.el, [
            {
                key: [10,13], // ctrl + return
                ctrl: true,
                scope: this,
                fn: function() {
                    if (this.getForm().hasOwnProperty('items')) {
                        // force set last selected field
                        this.getForm().items.each(function(item) {
                            if (item.hasFocus) {
                                item.setValue(item.getRawValue());
                            }
                        }, this);
                    }
                    this.action_saveAndClose.execute();
                }
            }
        ]);
        
        this.loadMask = new Ext.LoadMask(ct, {msg: String.format(_('Transferring {0}...'), this.i18nRecordName)});
        if(this.loadRecord !== false) {
            this.loadMask.show();
        }
    },
    
    /**
     * update (action updateer) top and bottom toolbars
     */
    updateToolbars: function(record, containerField) {
        if (! this.evalGrants) {
            return;
        }
        
        var actions = [
            this.action_saveAndClose,
            this.action_applyChanges,
            this.action_delete,
            this.action_cancel
        ];
        Tine.widgets.actionUpdater(record, actions, containerField);
        Tine.widgets.actionUpdater(record, this.tbarItems, containerField);
    },
    
    /**
     * get top toolbar
     */
    getToolbar: function() {
        return this.getTopToolbar();
    },
    
    /**
     * is form valid?
     * 
     * @return {Boolean}
     */
    isValid: function() {
        return this.getForm().isValid();
    },
    
    /**
     * @private
     */
    onCancel: function(){
        this.fireEvent('cancel');
        this.purgeListeners();
        this.window.close();
    },
    
    /**
     * @private
     */
    onSaveAndClose: function() {
        this.fireEvent('saveAndClose');
        this.onApplyChanges(true);
    },
    
    /**
     * generic apply changes handler
     * @param {Boolean} closeWindow
     */
    onApplyChanges: function(closeWindow) {
        this.loadMask.show();
        
        var ticketFn = this.doApplyChanges.deferByTickets(this, [closeWindow]),
            wrapTicket = ticketFn();

        this.fireEvent('save', this, this.record, ticketFn);
        wrapTicket();
    },
    
    /**
     * is called from onApplyChanges
     * @param {Boolean} closeWindow
     */
    doApplyChanges: function(closeWindow) {
        // we need to sync record before validating to let (sub) panels have 
        // current data of other panels
        this.onRecordUpdate();

        if(this.isValid()) {
            if (this.mode !== 'local') {
                this.recordProxy.saveRecord(this.record, {
                    scope: this,
                    success: function(record) {
                        // override record with returned data
                        this.record = record;
                        if (! Ext.isFunction(this.window.cascade)) {
                            // update form with this new data
                            // NOTE: We update the form also when window should be closed,
                            //       cause sometimes security restrictions might prevent
                            //       closing of native windows
                            this.onRecordLoad();
                        }
                        var ticketFn = this.onAfterApplyChanges.deferByTickets(this, [closeWindow]),
                            wrapTicket = ticketFn();
                            
                        this.fireEvent('update', Ext.util.JSON.encode(this.record.data), this.mode, this, ticketFn);
                        wrapTicket();
                    },
                    failure: this.onRequestFailed,
                    timeout: 300000 // 5 minutes
                }, {
                    duplicateCheck: this.doDuplicateCheck
                });
            } else {
                this.onRecordLoad();
                var ticketFn = this.onAfterApplyChanges.deferByTickets(this, [closeWindow]),
                    wrapTicket = ticketFn();
                    
                this.fireEvent('update', Ext.util.JSON.encode(this.record.data), this.mode, this, ticketFn);
                wrapTicket();
            }
        } else {
            this.loadMask.hide();
            Ext.MessageBox.alert(_('Errors'), this.getValidationErrorMessage());
        }
    },
    
    onAfterApplyChanges: function(closeWindow) {
        this.window.rename(this.windowNamePrefix + this.record.id);
        this.loadMask.hide();
        if (closeWindow) {
            this.window.fireEvent('saveAndClose');
            this.purgeListeners();
            this.window.close();
        }
    },
    
    /**
     * get validation error message
     * 
     * @return {String}
     */
    getValidationErrorMessage: function() {
        return _('Please fix the errors noted.');
    },
    
    /**
     * generic delete handler
     */
    onDelete: function(btn, e) {
        Ext.MessageBox.confirm(_('Confirm'), String.format(_('Do you really want to delete this {0}?'), this.i18nRecordName), function(_button) {
            if(btn == 'yes') {
                var deleteMask = new Ext.LoadMask(this.getEl(), {msg: String.format(_('Deleting {0}'), this.i18nRecordName)});
                deleteMask.show();
                
                this.recordProxy.deleteRecords(this.record, {
                    scope: this,
                    success: function() {
                        this.purgeListeners();
                        this.window.close();
                    },
                    failure: function () {
                        Ext.MessageBox.alert(_('Failed'), String.format(_('Could not delete {0}.'), this.i18nRecordName));
                        Ext.MessageBox.hide();
                    }
                });
            }
        });
    },
    
    /**
     * duplicate(s) found exception handler
     * 
     * @param {Object} exception
     */
    onDuplicateException: function(exception) {
        var resolveGridPanel = new Tine.widgets.dialog.DuplicateResolveGridPanel({
            app: this.app,
            store: new Tine.widgets.dialog.DuplicateResolveStore({
                app: this.app,
                recordClass: this.recordClass,
                recordProxy: this.recordProxy,
                data: {
                    clientRecord: exception.clientRecord,
                    duplicates: exception.duplicates
                }
            }),
            fbar: [
                '->',
                this.action_cancel,
                this.action_saveAndClose
            ]
        });
        
        // intercept save handler
        resolveGridPanel.btnSaveAndClose.setHandler(function(btn, e) {
            var resolveStrategy = resolveGridPanel.store.resolveStrategy;
            
            // action discard -> close window
            if (resolveStrategy == 'discard') {
                return this.onCancel();
            }
            
            this.record = resolveGridPanel.store.getResolvedRecord();
            this.onRecordLoad();
            
            mainCardPanel.layout.setActiveItem(this.id);
            resolveGridPanel.doLayout();
            
            this.doDuplicateCheck = false;
            this.onSaveAndClose();
        }, this);
        
        // place in viewport
        this.window.setTitle(String.format(_('Resolve Duplicate {0} Suspicion'), this.i18nRecordName));
        var mainCardPanel = this.findParentBy(function(p) {return p.isWindowMainCardPanel });
        mainCardPanel.add(resolveGridPanel);
        mainCardPanel.layout.setActiveItem(resolveGridPanel.id);
        resolveGridPanel.doLayout();
    },
    
    /**
     * generic request exception handler
     * 
     * @param {Object} exception
     */
    onRequestFailed: function(exception) {
        this.saving = false;
        
        if (exception.code == 629) {
            this.onDuplicateException.apply(this, arguments);
        } else {
            Tine.Tinebase.ExceptionHandler.handleRequestException(exception);
        }
        this.loadMask.hide();
    },
    
    /**
     * creates the relations panel, if relations are defined
     */
    initRelationsPanel: function() {
        if(!this.relationsPanel && !this.hideRelationsPanel && this.recordClass && this.recordClass.hasField('relations')) {
            this.relationsPanel = new Tine.widgets.relation.GenericPickerGridPanel({ anchor: '100% 100%', editDialog: this }); 
            this.items.items.push(this.relationsPanel);
        }
    }
});
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine.widgets.editDialog');

/**
 * @namespace   Tine.widgets.editDialog
 * @class       Tine.widgets.dialog.MultipleEditDialogPlugin
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * 
 * @plugin for Tine.widgets.editDialog
 */
Tine.widgets.dialog.MultipleEditDialogPlugin = function(config) {
    Ext.apply(this, config);
};

Tine.widgets.dialog.MultipleEditDialogPlugin.prototype = {
    /**
     * the application calling this plugin
     */
    app : null,
    /**
     * the editDialog the plugin is applied to
     */
    editDialog : null,
    /**
     * the selected records 
     */
    selectedRecords: null,
    /**
     * the selections' filter
     */
    selectionFilter: null,
    /**
     * this record is created on the fly and never saved as is, only changes to this record are sent to the backend
     */
    interRecord: null,   
    /**
     * a shorthand for this.editDialog.getForm() 
     */
    form : null,
    /**
     * Array which holds the fieldConfigs which can be handled by this plugin
     * Array of Objects: { key: <the raw key>, type: <custom/default>', formField: <the corresponding form field>, recordKey: <used for getters/setters of the record>}
     * @type Array
     */
    handleFields: null,
    /**
     * if records are defined by a filterselection
     * @type 
     */
    isFilterSelect: null,
    
    /**
     * count of all handled records
     * @type 
     */
    totalRecordCount: null,
    
    /**
     * component registry to skip on multiple edit
     * @type 
     */
    skipItems: [],
    
    /**
     * initializes the plugin
     */    
    init : function(ed) {
        ed.mode = 'local';
        ed.evalGrants = false;
        ed.onRecordLoad = ed.onRecordLoad.createSequence(this.onAfterRender, this);
        ed.onApplyChanges = ed.onApplyChanges.createInterceptor(this.onRecordUpdate, this);
        ed.onRecordUpdate = Ext.emptyFn;
        ed.initRecord = Ext.emptyFn;
        ed.useMultiple = true;
        ed.interRecord = new ed.recordClass({});
        ed.loadRecord = true;
        
        ed.on('load', function(editDialog, record, ticketFn) {
            this.loadInterceptor = ticketFn();
        }, this);
        
        this.app = Tine.Tinebase.appMgr.get(ed.app);
        this.form = ed.getForm();
        this.handleFields = [];
        this.interRecord = ed.interRecord;
        
        if (ed.hasOwnProperty('isMultipleValid') && Ext.isFunction(ed.isMultipleValid)) {
            ed.isValid = ed.isMultipleValid;
        } else {
            ed.isValid = function() {return true};
        }
        
        this.editDialog = ed;
    },
    
    /**
     * method to register components which are disabled on multiple edit
     * call this from the component to disable by: Tine.widgets.dialog.MultipleEditDialogPlugin.prototype.registerSkipItem(this);
     * @param {} item
     */
    registerSkipItem: function(item) {
        this.skipItems.push(item);
    },
    
    /**
     * handle fields for multiedit
     */
    onAfterRender : function() {
        // skip registered form items
        Ext.each(this.skipItems, function(item) {
            item.setDisabled(true);
        }, this);

        var keys = [];
        
        // disable container selector - just container_id
        var field = this.form.findField('container_id');
        if(field) {
            field.disable();
        }
        
        // get fields to handle
        Ext.each(this.editDialog.recordClass.getFieldNames(), function(key) {
            var field = this.form.findField(key);
            if (!field) {
                Tine.log.info('No field found for property "' + key + '". Ignoring...');
                return true;
            }
            Tine.log.info('Field found for property "' + key + '".');
            keys.push({key: key, type: 'default', formField: field, recordKey: key});
        }, this);
        
        // get customfields to handle
        var cfConfigs = Tine.widgets.customfields.ConfigManager.getConfigs(this.app, this.editDialog.recordClass);
        if (cfConfigs) {
            Ext.each(cfConfigs, function (config) {
                var field = this.form.findField('customfield_' + config.data.name);
                if (!field) {
                    Tine.log.info('No customfield found for property "' + config.data.name + '". Ignoring...');
                    return true;
                }
                Tine.log.info('Customfield found for property "' + config.data.name + '".');
                keys.push({key: config.data.name, type: 'custom', formField: field, recordKey: '#' + config.data.name});
            }, this);
        }
        
        // get relationpickerfields to handle
        if(this.editDialog.relationPickers) {
            Ext.each(this.editDialog.relationPickers, function(picker) {
                Tine.log.info('RelationPicker found. Using "' + '%' + picker.relationType + '-' + picker.fullModelName + '" as key.');
                keys.push({key: picker.relationType + '-' + picker.fullModelName, type: 'relation', formField: picker.combo, recordKey: '%' + picker.relationType + '-' + picker.fullModelName});
            });
        }

        Ext.each(keys, function(field) {
            var ff = field.formField;
            // disable fields which cannot be handled atm.
            if ((!(ff.isXType('textfield'))) && (!(ff.isXType('checkbox'))) && (!(ff.isXType('datetimefield'))) || ff.multiEditable === false) {
                Tine.log.debug('Disabling field for key "' + field.recordKey + '". Cannot be handled atm.');
                ff.setDisabled(true);
                this.interRecord.set(field.recordKey, '');
                return true;
            }
            // remove empty text
            if(ff.hasOwnProperty('emptyText')) {
                ff.emptyText = '';
            }
            
            // default event for init
            ff.startEvents = ['focus'];
            // default trigger event
            ff.triggerEvents = ['blur'];
            
            // special events for special field types
            if(ff.isXType('tinedurationspinner')) {
                ff.emptyOnZero = true;
                ff.startEvents = ['focus', 'spin'];
                ff.triggerEvents = ['spin', 'blur'];
            } else if(ff.isXType('tinerecordpickercombobox')) {
                ff.startEvents = ['focus', 'expand'];
                ff.triggerEvents = ['select', 'blur'];
            }
            // add field to handlefields array
            this.handleFields.push(field);
            
            Ext.each(ff.startEvents, function(initEvent) {
                ff.on(initEvent, this.onInitField, this, [ff]);
            }, this);

        }, this);
        
        this.editDialog.record = this.interRecord;
        this.onRecordLoad();
    },
    
    /**
     * is called when an init handler for this field is called
     * @param {Object} ff the form field
     */
    onInitField: function(ff) {
        Tine.log.info('Init handler called for field "' + ff.name + '".');
        
        // if already initialized, dont't repeat setting start values and inserting button
        if(! ff.multipleInitialized) {
            if (! ff.isXType('checkbox')) {
                this.createMultiButton(ff);
            }
            
            var startValue = this.interRecord.get(ff.name);
            
            if(ff.isXType('addressbookcontactpicker') && ff.useAccountRecord) {
                ff.startRecord = startValue ? startValue : null;
                startValue = startValue['accountId'] ? startValue['accountId'] : null;
            } else if (ff.isXType('timefield')) {
                if(startValue.length) {
                    startValue = startValue.replace(/\d{4}-\d{2}-\d{2}\s/, '').replace(/:\d{2}$/, '');
                }
            } else if (ff.isXType('trigger') && ff.triggers) {
                ff.on('blur', this.hideTriggerClearer);
                ff.on('focus', this.hideTriggerClearer);
                ff.on('select', this.hideTriggerClearer);
            } else if(Ext.isObject(startValue)) {
                startValue = startValue[ff.recordClass.getMeta('idProperty')];
                ff.startRecord = new ff.recordClass(startValue);
            }
            ff.startingValue = (startValue == undefined || startValue == null) ? '' : startValue;
            
            Tine.log.info('Setting start value to "' + startValue + '".');
            
            Ext.each(ff.triggerEvents, function(triggerEvent) {
                ff.on(triggerEvent, this.onTriggerField, ff);
            }, this);
    
            ff.multipleInitialized = true;

            if(ff.isXType('tinedurationspinner')) {
                ff.fireEvent(ff.triggerEvents[1]);
            }
        } else {
            if(ff.multiButton) {
                ff.multiButton.removeClass('hidden');
            }
        }
    },
    
    /**
     * hides the default clearer
     */
    hideTriggerClearer: function() {
        this.triggers[0].hide();
    },
    /**
     * creates the multibutton (button to reset or clear the field value)
     * @param {Ext.form.field} formField
     */
    createMultiButton: function(formField) {
        var subLeft = 18;
        if(formField.isXType('tinerecordpickercombobox')) {
            formField.disableClearer = true;
            subLeft += 19;
        } else if (formField.isXType('extuxclearabledatefield')) {
            formField.disableClearer = true;
            if(!formField.multi) {
                subLeft += 17;
            }
        } else if(formField.isXType('tine.widget.field.AutoCompleteField')) {
            // is trigger, but without button, so do nothing
        } else if (formField.isXType('trigger')) {
            if(! formField.hideTrigger) {
                subLeft += 17;
            }
        } else if (formField.isXType('datetimefield')) {
            subLeft += 17; 
        }
        var el = formField.el.parent().select('.tinebase-editmultipledialog-clearer'), 
            width = formField.getWidth(), 
            left = (width - subLeft) + 'px';
            
        formField.startLeft = (width - subLeft);

        if (el.elements.length > 0) {
            el.setStyle({left: left});
            el.removeClass('hidden');
            return;
        }
        
        // create Button
        formField.multiButton = new Ext.Element(document.createElement('img'));
        formField.multiButton.set({
            'src': Ext.BLANK_IMAGE_URL,
            'ext:qtip': Ext.util.Format.htmlEncode(_('Delete value from all selected records')),
            'class': 'tinebase-editmultipledialog-clearer',
            'style': 'left:' + left
            });
        
        formField.multiButton.addClassOnOver('over');
        formField.multiButton.addClassOnClick('click');

        // handles the reset/restore button
        formField.multiButton.on('click', this.onMultiButton, formField);
        formField.el.insertSibling(formField.multiButton);
    },
    
    /**
     * is called if the multibutton of "this" field is triggered
     */
    onMultiButton: function() {
        Tine.log.debug('Multibutton called.');
        // scope: formField
        if(this.multiButton.hasClass('undo')) {
            Tine.log.debug('Resetting value to "' + this.startingValue + '".');
            if(this.startRecord) {
                this.store.removeAll();
                this.setValue(this.startRecord);
                this.value = this.startingValue;
            } else {
                this.setValue(this.startingValue);
            }
            this.clearInvalid();
            
            if(this.isXType('extuxclearabledatefield') && this.multi) {
                var startLeft = this.startLeft ? this.startLeft : 0;
                var parent = this.el.parent().select('.tinebase-editmultipledialog-clearer');
                parent.setStyle('left', startLeft + 'px');
            }
            if (this.multi) {
                this.cleared = false;
            }
        } else {
            Tine.log.debug('Clearing value.');
            if(this.isXType('extuxclearabledatefield') && this.multi) {
                var startLeft = this.startLeft ? this.startLeft : 0;
                startLeft -= 17;
                var parent = this.el.parent().select('.tinebase-editmultipledialog-clearer');
                parent.setStyle('left', startLeft + 'px');
            }
            this.setValue('');
            if (this.multi) {
                this.cleared = true;
                this.allowBlank = this.origAllowBlank;
            }
        }
        // trigger event
        this.fireEvent(this.triggerEvents[0], this);
    },
    
    /*
     * is called when a trigger event is fired
     */
    onTriggerField: function() {
        // scope on formField
        Tine.log.info('Trigger handler called for field "' + this.name + '".');
        var ar = this.el.parent().select('.tinebase-editmultipledialog-dirty');
        var originalValue = this.hasOwnProperty('startingValue') ? String(this.startingValue) : String(this.originalValue),
            currentValue;
        
        if (this.isXType('datefield')) {
            currentValue = this.fullDateTime ? this.fullDateTime.format('Y-m-d H:i:s') : '';
        } else if (this.isXType('timefield')) {
            currentValue = this.fullDateTime;
        } else {
            currentValue = String(this.getValue());
        }
        
        Tine.log.info('Start value: "' + originalValue + '", current: "' + currentValue + '"');
        if ((Ext.encode(originalValue) != Ext.encode(currentValue)) || (this.cleared === true)) {  // if edited or cleared
            // Create or set arrow
            if(ar.elements.length > 0) {
                ar.setStyle('display','block');
            } else {
                var arrow = new Ext.Element(document.createElement('img'));
                arrow.set({
                    'src': Ext.BLANK_IMAGE_URL,
                    'class': 'tinebase-editmultipledialog-dirty',
                    'height': 5,
                    'width': 5
                });
                this.el.insertSibling(arrow);
            }
            // Set field
            this.edited = true;
            this.setReadOnly(false);
            
            this.removeClass('tinebase-editmultipledialog-noneedit');
            
            // Set button
            this.multiButton.addClass('undo');
            this.multiButton.removeClass('hidden');
            this.multiButton.set({'ext:qtip': Ext.util.Format.htmlEncode(_('Undo change for all selected records'))});
            
        } else {    // If set back
            // Set arrow
            if(ar.elements.length > 0) {
                ar.setStyle('display','none');
            }
            // Set field
            this.edited = false;
            if (this.multi) {
                this.setReadOnly(true);
                this.addClass('tinebase-editmultipledialog-noneedit');
            }
            
            // Set button
            this.multiButton.removeClass('undo');
            this.multiButton.addClass('hidden');
            this.multiButton.set({'ext:qtip': Ext.util.Format.htmlEncode(_('Delete value from all selected records'))});
        }
    },
    /**
     * waits until the dialog is rendered and fetches real records by the filter on filter selection
     */
    onRecordLoad : function() {
        // fetch records from server on filterselection to get the exact difference
        if (this.isFilterSelect && this.selectionFilter) {
            this.fetchRecordsOnLoad();
        } else {
            var records = [];
            Ext.each(this.selectedRecords, function(recordData, index) {
                records.push(new this.editDialog.recordClass(recordData));
            }, this);
            this.onRecordPrepare(records);
        }
    },
    
    /**
     * find out which fields have differences
     */
    onRecordPrepare: function(records) {
        Ext.each(this.handleFields, function(field) {
            var refData = false;
            Ext.each(records, function(record, index) {
                if(field.type == 'relation') {
                    this.setFieldValue(field, false);
                } else {
                    // the first record of the selected is the reference
                    if(index === 0) {
                        refData = record.get(field.recordKey);
                    }
                    if ((Ext.encode(record.get(field.recordKey)) != Ext.encode(refData))) {
                        this.interRecord.set(field.recordKey, '');
                        this.setFieldValue(field, false);
                        return false;
                    } else {
                        if (index == records.length - 1) {
                            this.interRecord.set(field.recordKey, refData);
                            this.setFieldValue(field, true);
                            return true;
                        }
                    }
                }
            }, this);
        }, this);
        
        // TODO: grantsProperty not handled here, not needed at the moment but sometimes, perhaps.
//        var cp = this.editDialog.recordClass.getMeta('containerProperty') ? this.editDialog.recordClass.getMeta('containerProperty') : 'container_id';
//        if(this.interRecord.get(cp) !== undefined) {
//            this.interRecord.set(cp, {account_grants: {editGrant: true}});
//        }
        this.interRecord.dirty = false;
        this.interRecord.modified = {};
        
        this.editDialog.window.setTitle(String.format(_('Edit {0} {1}'), this.totalRecordCount, this.editDialog.i18nRecordsName));

        Tine.log.debug('loading of the following intermediate record completed:');
        Tine.log.debug(this.interRecord);
        
        this.editDialog.updateToolbars(this.interRecord, this.editDialog.recordClass.getMeta('containerProperty'));
        if(this.editDialog.tbarItems) {
            Ext.each(this.editDialog.tbarItems, function(item) {
                if(Ext.isFunction(item.setDisabled)) item.setDisabled(true);
                item.multiEditable = false;
            });
        }
        
        this.loadInterceptor();
        
        // some field sanitizing
        Ext.each(this.handleFields, function(field) {
            // handle TimeFields to set original value (not possible before)
            if(field.formField.isXType('timefield')) {
                var value = this.interRecord.get(field.key);
                if(value) {
                    field.formField.setValue(new Date(value));
                }
            }
        }, this);
        
        return false;
    },

    
    /**
     * Set field value
     * @param {Ext.form.Field} field
     * @param {Boolean} samevalue true, if value is the same of all records
     */
    setFieldValue: function(field, samevalue) {
        var ff = field.formField;
        
        ff.removeClass('x-form-empty-field');
        
        if (! samevalue) {  // The records does not have the same value on this field
            ff.setReadOnly(true);
            ff.addClass('tinebase-editmultipledialog-noneedit');
            ff.origAllowBlank = ff.allowBlank;
            ff.allowBlank = true;
            ff.multi = true;
            
            ff.setValue('');
            ff.originalValue = '';
            ff.clearInvalid();
            Ext.QuickTips.register({
                target: ff,
                dismissDelay: 30000,
                title: _('Different Values'),
                text: _('This field has different values. Editing this field will overwrite the old values.'),
                width: 200
            });
            
            if (ff.isXType('checkbox')) {
                this.wrapCheckbox(ff);
            } 
        } else { // All records have the same value on this field
            if (ff.isXType('checkbox')) {
                ff.originalValue = this.interRecord.get(ff.name);
                ff.setValue(this.interRecord.get(ff.name));
            } else if (ff.isXType('tinerecordpickercombobox')) {
                var val = this.interRecord.get(field.recordKey);
                if(val) {
                    if(!ff.isXType('addressbookcontactpicker')) {
                        ff.startRecord = new ff.recordClass(val);
                    } else {
                        ff.startRecord = val;
                    }
                }
            } else {
                ff.setValue(this.interRecord.get(field.recordKey));
            }
        }
        ff.edited = false;

        if (ff.isXType('checkbox')) {
            ff.on('check', function() {
                this.edited = (this.originalValue !== this.getValue());
            });
        }
    },
    
    /**
     * Wraps the checkbox with dirty colored span
     * @param {Ext.form.Field} checkbox
     */
    wrapCheckbox: function(checkbox) {
        if(checkbox.rendered !== true) {
            this.wrapCheckbox.defer(100, this, [checkbox]);
            return;
        }
        checkbox.getEl().wrap({tag: 'span', 'class': 'tinebase-editmultipledialog-dirtycheck'});
        checkbox.originalValue = null;
        checkbox.setValue(false);
    },
    
    /**
     * is called when the form is submitted. only fieldvalues with edited=true are committed 
     * @return {Boolean}
     */
    onRecordUpdate : function() {
        this.changedHuman = '<br /><br /><ul>';
        var changes = [];

        Ext.each(this.handleFields, function(field) {
            var ff = field.formField,
                renderer = Ext.util.Format.htmlEncode;

            if (ff.edited === true) {
                var label = ff.fieldLabel ? ff.fieldLabel : ff.boxLabel ? ff.boxLabel : ff.ownerCt.fieldLabel;
                    label = label ? label : ff.ownerCt.title;

                changes.push({name: (field.type == 'relation') ? field.recordKey : ff.getName(), value: ff.getValue()});
                this.changedHuman += '<li><span style="font-weight:bold">' + label + ':</span> ';
                
                if(ff.isXType('checkbox')) {
                    renderer = Tine.Tinebase.common.booleanRenderer;
                } else if(ff.isXType('tinedurationspinner')) {
                    renderer = Tine.Tinebase.common.minutesRenderer;
                }
                
                this.changedHuman += ff.lastSelectionText ? renderer(ff.lastSelectionText) : renderer(ff.getValue());
                this.changedHuman += '</li>';
            }
        }, this);
        
        this.changedHuman += '</ul>';

        if (changes.length == 0) {
            this.editDialog.onCancel();
            return false;
        }
        
        if(!this.editDialog.isMultipleValid()) {
            Ext.MessageBox.alert(_('Errors'), _('Please fix the errors noted.'));
            Ext.each(this.handleFields, function(item) {
                if(item.activeError) {
                    if(!item.edited) item.activeError = null;
                }
            });
            return false;

        } else {
            var filter = this.selectionFilter;
            
            Ext.MessageBox.confirm(_('Confirm'), String.format(_('Do you really want to change these {0} records?') + this.changedHuman, this.totalRecordCount),
                function(_btn) {
                if (_btn == 'yes') {
                    Ext.MessageBox.wait(_('Please wait'),_('Applying changes'));
                    Ext.Ajax.request({
                        url: 'index.php',
                        timeout: 3600000, // 1 hour
                        params: {
                            method: 'Tinebase.updateMultipleRecords',
                            appName: this.editDialog.recordClass.getMeta('appName'),
                            modelName: this.editDialog.recordClass.getMeta('modelName'),
                            changes: changes,
                            filter: filter
                        },
                        success: function(_result, _request) {
                            Ext.MessageBox.hide();
                            var resp = Ext.decode(_result.responseText);
                            if(resp.failcount > 0) {
                                var window = Tine.widgets.dialog.MultipleEditResultSummary.openWindow({
                                    response: _result.responseText,
                                    appName: this.app.appName,
                                    recordClass: this.editDialog.recordClass
                                });
                                window.on('close', function() {
                                    this.editDialog.fireEvent('update');
                                    this.editDialog.onCancel();
                                },this);
                            } else {
                                this.editDialog.fireEvent('update');
                                this.editDialog.onCancel();
                            }
                        },
                        scope: this
                    });
                }
            }, this);
         }
         return false;
    },
    
    /**
     * fetch records from backend on selectionFilter
     */
    fetchRecordsOnLoad: function() {
        Tine.log.debug('Fetching additional records...');
        this.editDialog.recordProxy.searchRecords(this.selectionFilter, null, {
            scope: this,
            success: function(result) {
                this.onRecordPrepare.call(this, result.records);
            }
        });
    }
};

Ext.ComponentMgr.registerPlugin('multiple_edit_dialog', Tine.widgets.dialog.MultipleEditDialogPlugin);

/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine.widgets.editDialog');

/**
 * @namespace   Tine.widgets.editDialog
 * @class       Tine.widgets.dialog.AddRelationsDialogPlugin
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * 
 * @plugin for Tine.widgets.editDialog
 */
Tine.widgets.dialog.AddRelationsEditDialogPlugin = function(config) {
    Ext.apply(this, config);
};

Tine.widgets.dialog.AddRelationsEditDialogPlugin.prototype = {
    /**
     * the recordClass of the calling component
     * @type {Ext.data.Record} recordClass
     */
    recordClass: null,
    
    /**
     * the editDialog the plugin is applied to
     * @type {Tine.widgets.dialog.EditDialog} editDialog
     */
    editDialog : null,
    
    /**
     * relations to add
     * @type {Array}
     */
    addRelations: null,
    
    /**
     * the selection filter
     * @type {Object} selectionFilter
     */
    selectionFilter: null,
    
    /**
     * the application name of the calling component
     * @type {String} callingApp
     */
    callingApp: null,
    
    /**
     * the model name of the calling component
     * @type {String} callingModel
     */
    callingModel: null,
    
    /**
     * the configuration for the relations
     * @type {Object} relationConfig
     */
    relationConfig: null,
    
    /**
     * the record proxy of the calling app
     * @type {Tine.Tinebase.data.RecordProxy} recordProxy
     */
    recordProxy: null,
    
    /**
     * initializes the plugin
     * @var {Tine.widgets.dialog.EditDialog} ed
     */    
    init: function(ed) {
        
        ed.on('load', function(editDialog, record, ticketFn) {
            this.interceptor = ticketFn();
            this.fetchRecords();
        }, this);
        
        Tine.log.info('Init plugin "Tine.widgets.dialog.AddRelationsDialogPlugin"...');
        // fetch default relation config is none is given
        this.relationConfig = this.relationConfig ? this.relationConfig : {};
        Ext.each(ed.app.getRegistry().get('relatableModels'), function(rc) {
            if(rc.relatedApp == this.callingApp && rc.relatedModel == this.callingModel) {
                Ext.applyIf(this.relationConfig, rc['default']);
                return false;
            }
        }, this);
        
        this.editDialog = ed;
        this.recordClass = Tine[this.callingApp].Model[this.callingModel];
        this.recordProxy = new Tine.Tinebase.data.RecordProxy({
            recordClass: this.recordClass
        });
    },
    
    /**
     * hook in after this.editDialog.initRecord
     */
    fetchRecords: function() {
        if(Ext.isBoolean(this.addRelations)) {
            Tine.log.debug('Fetching additional records...');
            this.recordProxy.searchRecords(this.selectionFilter, null, {
                scope: this,
                success: function(result) {
                    this.addRelationsOnLoad(result.records);
                }
            });
        } else {
            var records = [];
            Ext.each(this.addRelations, function(plainRecord) {
                records.push(this.recordProxy.recordReader({responseText: Ext.encode(plainRecord)}));
            }, this)
            this.addRelationsOnLoad(records);
        }
    },
    
    /**
     * adds the relations to the record
     * @param {Array} records
     */
    addRelationsOnLoad: function(records) {
        var ownIdProperty = this.editDialog.recordClass.getMeta('idProperty'),
            foreignIdProperty = this.recordClass.getMeta('idProperty');

        var addRelations = [];
        Ext.each(records, function(record) {
            var add = true;
            Ext.each(this.editDialog.record.get('relations'), function(existingRelation) {
                if((record.get(foreignIdProperty) == existingRelation.related_record[foreignIdProperty])) {
                    add = false;
                    return;
                }
            }, this);

            if(add) {
                if(record.data.hasOwnProperty('relations')) {
                    delete record.data.relations;
                }
                var relation = {
                    own_backend: "Sql",
                    own_degree: "sibling",
                    own_id: this.editDialog.record.get(ownIdProperty),
                    own_model: this.editDialog.app.name + '_Model_' + this.editDialog.recordClass.getMeta('modelName'),
                    related_backend: "sql",
                    related_id: record.get(foreignIdProperty),
                    related_model: this.callingApp + '_Model_' + this.callingModel,
                    related_record: record.data,
                    remark: "",
                    type: ""
                };
                Ext.apply(relation, this.relationConfig);
                addRelations.push(relation);
            }
        }, this);
        var existing = this.editDialog.record.data.hasOwnProperty('relations') ? this.editDialog.record.data.relations : [];
        Ext.each(existing, function(existingRecord) {
            if(existingRecord.related_record.hasOwnProperty('relation')) {
                delete existingRecord.related_record.relation;
            }
        }, this);

        this.editDialog.record.set('relations', existing.concat(addRelations));
        this.interceptor();
    }
};

Ext.ComponentMgr.registerPlugin('addrelations_edit_dialog', Tine.widgets.dialog.AddRelationsEditDialogPlugin);
Ext.ns('Tine.widgets.dialog');

Tine.widgets.dialog.MultipleEditResultSummary = function(config) {
    
    Tine.widgets.dialog.MultiOptionsDialog.superclass.constructor.call(this, config);
    
    this.options = config.options || {};
    this.scope = config.scope || window;
};

/**
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.MultipleEditResultSummary
 * @extends     Ext.FormPanel
 */
Ext.extend(Tine.widgets.dialog.MultipleEditResultSummary, Ext.FormPanel, {
      
    layout : 'fit',
    border : false,      
    labelAlign : 'top',
    items: null,
    anchor : '100% 100%',
    
    /**
     * Store holding the Exceptions
     * @type Ext.Store
     */
    store: null,
    
    /**
     * Json Response from updateMultipleRecords
     * @type String
     */
    response: null,

    /**
     * The appname of the calling app
     * @type String 
     */
    appName: null,
    
    /**
     * The calling app
     * @type Tinebase.Application
     */
    app: null,
    
    /**
     * {Ext.data.Record} recordClass
     * record definition class  (required)
     */
    recordClass: null,
    
    initComponent: function() {
        this.response = Ext.decode(this.response);
        
        if (!this.app) {
            this.app = Tine.Tinebase.appMgr.get(this.appName);
        }
        
        // init some translations
        if (this.app.i18n && this.recordClass !== null) {
            this.i18nRecordName = this.app.i18n.n_hidden(this.recordClass.getMeta('recordName'), this.recordClass.getMeta('recordsName'), 1);
            this.i18nRecordsName = this.app.i18n._hidden(this.recordClass.getMeta('recordsName'));
        }
        
        // init actions
        this.initActions();
        // init buttons and tbar
        this.initButtons();
        
        this.initStore();
        
        // get items for this dialog
        this.items = this.getFormItems();
       
        Tine.widgets.dialog.MultipleEditResultSummary.superclass.initComponent.call(this);
    },
    
    /**
     * init actions
     */
    initActions: function() {
        this.action_update = new Ext.Action({
            text : _('OK'),
            minWidth : 70,
            scope : this,
            handler : this.onCancel,
            iconCls : 'action_saveAndClose'
        });
    },
    
    initStore: function() {
        this.store = new Ext.data.JsonStore({
                mode: 'local',
                idProperty: 'id',
                fields: ['id', 'record','message'],
                sortInfo: {
                    field: 'record',
                    direction: 'ASC'
                }
            });
        this.store.loadData(this.response.exceptions);
    },
    
    /**
     * init buttons
     */
    initButtons : function() {
        this.fbar = [ '->', this.action_update ];
    },  
    
    /**
     * is called when the component is rendered
     * @param {} ct
     * @param {} position
     */
    onRender : function(ct, position) {
        Tine.widgets.dialog.MultipleEditResultSummary.superclass.onRender.call(this, ct, position);

        // generalized keybord map for edit dlgs
        var map = new Ext.KeyMap(this.el, [ {
            key : [ 10, 13 ], // ctrl + return
            ctrl : true,
            fn : this.onCancel,
            scope : this
        } ]);

    },
       
    /**
     * closes the window
     */
    onCancel: function() {
        this.fireEvent('cancel');
        this.purgeListeners();
        this.window.close();
    },    
    
    getFormItems: function() {
        if(this.items) return this.items;
        var allrecs = this.response.totalcount + this.response.failcount;
        
        var rn = (allrecs>1) ? this.i18nRecordsName : this.i18nRecordName;
        var summary = String.format( (allrecs>1) ? _('You edited {0} {1}.') : _('You edited {0} {1}.'), allrecs, rn);
        summary += '<br />';
        rn = (this.response.totalcount>1) ? this.i18nRecordsName : this.i18nRecordName;
        summary += String.format( (this.response.totalcount>1) ? _('{0} {1} have been updated properly.') : _('{0} {1} has been updated properly.'), this.response.totalcount, rn);
        summary += '<br />';
        rn = (this.response.failcount>1) ? this.i18nRecordsName : this.i18nRecordName;
        summary += String.format( (this.response.failcount>1) ? _('{0} {1} have invalid data after updating. These {1} have not been changed.') : _('{0} {1} has invalid data after updating. This {1} has not been changed.'), this.response.failcount, rn);
       
        return {
            border: false,
            cls : 'x-ux-display',
            layout: 'ux.display',
            
            frame: true,
            autoScroll: true,
            items: [
                {
                    height: 100,
                    border: false,
                    items: [{
                        hideLabel: true,
                        xtype: 'ux.displayfield',
                        value: summary,
                        htmlEncode: false,
                        style: 'padding: 0 5px; color: black',
                        cls: 'x-panel-mc'
                    }],
                    hideLabel: true,
                    ref: '../../summaryPanelInfo',
                    layout: 'ux.display',
                    layoutConfig: {
                        background: 'border'
                    }
                }, {
                    baseCls: 'ux-arrowcollapse',
                    cls: 'ux-arrowcollapse-plain',
                    collapsible: true,
                    hidden: false,
                    flex: 1,
                    title:'',
                    ref: '../../summaryPanelFailures',
                    items: [{
                        xtype: 'grid',
                        store: this.store,
                        autoHeight: true,
                        columns: [
                            { id: 'id', header: _('Index'), width: 60, sortable: false, dataIndex: 'id', hidden: true}, 
                            { id: 'record', header: this.i18nRecordName, width: 160, sortable: true, dataIndex: 'record', renderer: function(value) {
                                return value.n_fn;
                            }},
                            { id: 'failure', header: _('Failure'), width: 60, sortable: true, dataIndex: 'message'}
                        ],
                        autoExpandColumn: 'failure'
                    }]
                }
            ]
        };
    }

});


Tine.widgets.dialog.MultipleEditResultSummary.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: 600,
        height: 450,
        modal: true,
        title: _('Summary'),
        contentPanelConstructor: 'Tine.widgets.dialog.MultipleEditResultSummary',
        contentPanelConstructorConfig: config
    });
    return window;
};/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <alex@stintzing.net>
 * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Ext.ux');

/**
 * @namespace   Ext.ux
 * @class       Ext.ux.TabPanelKeyPlugin
 * @author      Alexander Stintzing <alex@stintzing.net>
 */
Ext.ux.TabPanelKeyPlugin = function(config) {
    Ext.apply(this, config);
};

Ext.ux.TabPanelKeyPlugin.prototype = {
    panel : null,
        
    init : function(panel) {
        this.panel = panel;
        this.panel.onRender = this.panel.onRender.createSequence(this.onRender, this);
    },
    
    /**
     * creates shortcuts for tabs
     */
    onRender: function() {
        
        if (! this.panel.rendered) {
            this.onRender.defer(250, this);
            return;
        }

        var tabCount = this.panel.items.length;
        
        for (var index = 0; index < tabCount; index++) {
            var item = this.panel.items.items[index];
            if(item.disabled !== true) {
                new Ext.KeyMap(this.panel.el, [{
                    key: index + 49,
                    ctrl: true,
                    scope: this,
                    fn: this.switchTab
                }]);
            }
        }
    },
    
    /**
     * switch to tab
     * @param Integer code
     */
    switchTab: function(code) {
        var number = parseInt(code) - 49;
        if (this.panel) {
            this.panel.setActiveTab(number);
        }
    }
}

Ext.preg('ux.tabpanelkeyplugin', Ext.ux.TabPanelKeyPlugin);/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.editDialog');

/**
 * Token Mode Edit Plugin
 * 
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.TokenModeEditDialogPlugin
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @constructor
 * @param {Object} config The configuration options.
 */
Tine.widgets.dialog.TokenModeEditDialogPlugin = function(config) {
    Ext.apply(this, config);
};

Tine.widgets.dialog.TokenModeEditDialogPlugin.prototype = {
    
    /**
     * @property app
     * @type Tine.Tinebase.Application
     */
    app : null,
    
    /**
     * @property editDialog
     * @type Tine.widgets.dialog.EditDialog
     */
    editDialog : null,
    
    /**
     * @property form
     * @type Ext.form.BasicForm
     */
    form : null,
    
    /**
     * @property inTokenMode
     * @type Boolean
     */
    inTokenMode: false,
    
    /**
     * @property selection
     * @type Array
     */
    selection: null,
    
    init : function(editDialog) {
        
        this.editDialog = editDialog;
        this.app = Tine.Tinebase.appMgr.get(this.editDialog.app);
        this.form = this.editDialog.getForm();
        
        if (this.editDialog.rendered) {
            this.onAfterRender();
        } else {
            this.editDialog.on('afterrender', this.onAfterRender, this);
        }
        
        this.editDialog.onRecordLoad = this.editDialog.onRecordLoad.createSequence(this.onRecordLoad, this);
    },
    
    onAfterRender: function() {
        this.editDialog.getEl().on('keydown', function (e) {
            if (e.ctrlKey && e.getKey() === e.T) {
                if (this.inTokenMode) {
                    this.endTokenMode();
                } else {
                    this.startTokenMode();
                }
            }
        }, this);
    },
    
    onRecordLoad: function() {
        if (this.inTokenMode) {
            // restart token mode defered to don't consume time before closeing
            this.startTokenMode.defer(100, this);
        }
    },
    
    startTokenMode: function() {
        this.initialize();
        
        if (this.inTokenMode) {
            this.endTokenMode();
        }
        
        this.inTokenMode = true;
        
        this.selection = [];
        
        this.form.items.each(function(item) {
            if (item instanceof Ext.form.TextField && ! item.disabled && ! item.forceSelection) {
                if (item.rendered) {
                    this.tokenizeField(item);
                } else {
                    item.on('render', this.tokenizeField, this);
                }
            }
        }, this);
    },
    
    endTokenMode: function() {
        if (this.inTokenMode) {
            this.inTokenMode = false;
            Ext.select('div[class^=tinebase-tokenedit-tokenbox]', this.editDialog.getEl()).remove();
            
            
            this.form.items.each(function(item) {
                item.un('render', this.tokenizeField, this);
                item.un('resize', this.syncFieldSize, this);
            }, this);
        }
    },
    
    initialize: function() {
        if (! this.isInitialized) {
            this.isInitialized = true;
            
            //this.editDialog.getEl().on('dblclick', this.endTokenMode, this);
            
            this.dragZone = new Ext.dd.DragZone(this.editDialog.getEl(), {
                getDragData: this.getDragData.createDelegate(this),
                getRepairXY: Ext.emptyFn
            });
            
            this.dropZone = new Ext.dd.DropZone(this.editDialog.getEl(), {
                onNodeOver: this.onNodeOver.createDelegate(this),
                onNodeDrop: this.onNodeDrop.createDelegate(this),
                getTargetFromEvent: this.getTargetFromEvent.createDelegate(this)
            });
            
            this.editDialog.getEl().on('mousedown', this.onClick, this);
        }
    },
    
    tokenizeField: function(field) {
        if (! this.inTokenMode) {
            // token mode might be stopped in the meantime
            return;
        }
        
        var el = field.itemCt.insertFirst({
            'tag': 'div',
            'class': 'tinebase-tokenedit-tokenbox x-form-text' + (field instanceof Ext.form.TextArea ? ' tinebase-tokenedit-tokenbox-area' : '')
        });
        
        this.syncFieldSize(field);
        field.on('resize', this.syncFieldSize, this);
        
        var value = Ext.util.Format.htmlEncode(field.getValue());
        if (value) {
            value = value.replace(/\n/g, ' &#x21A9; ');
            
            var tokens = String(value).split(/\s+/);
            this.getTokenTpl().overwrite(el, tokens);
        }
    },
    
    syncFieldSize: function(field) {
        var el = field.itemCt.down('div[class^=tinebase-tokenedit-tokenbox]');
        
        el.setBox(field.el.getBox());
    },
    
    getTokenTpl: function() {
        if (! this.tokenTpl) {
            this.tokenTpl = new Ext.XTemplate(
                '<tpl for=".">',
                    '<span class="tinebase-tokenedit-token">{.}</span>',
                '</tpl>'
            ).compile();
        }
        
        return this.tokenTpl;
    },
    
    onClick: function(e) {
        
        if (this.processedEvent == e.browserEvent) {
            return;
        }
        
        this.processedEvent = e.browserEvent;
        
        var target = e.getTarget('.tinebase-tokenedit-token', 10, true);
        if (target) {
            if (e.ctrlKey) {
                if (this.selection.indexOf(target) < 0) {
                    this.selection.push(target);
                    target.addClass('tinebase-tokenedit-token-selected');
                } else {
                    this.selection.remove(target);
                    target.removeClass('tinebase-tokenedit-token-selected');
                }
            } else {
                Ext.each(this.selection, function(el) {
                    el.removeClass('tinebase-tokenedit-token-selected');
                }, this);
                this.selection = [target];
                target.addClass('tinebase-tokenedit-token-selected');
            }
        }
    },
    
    getDragData: function(e) {
        var target = e.getTarget('.tinebase-tokenedit-token', 10, true);
            
        if (target) {
            // autoselect token
            if (this.selection.indexOf(target) < 0) {
                this.onClick(e);
            }
            this.processedEvent = e.browserEvent;
            
            var ddel = new Ext.Element(document.createElement('div'));
            ddel.id = Ext.id();
            
            var sourceFields = [];
            
            Ext.each(this.selection, function(el) {
                sourceFields.push(Ext.getCmp(el.parent('div[class^=tinebase-tokenedit-tokenbox]').parent('div').child('*[id^=ext-comp]').id));
                
                var clone = el.dom.cloneNode(true);
                clone.id = Ext.id();
                ddel.appendChild(clone);
            }, this);
            
            return {
                ddel: ddel.dom,
                sourceFields: sourceFields
                
            }
        }
    },
    
    getTargetFromEvent: function(e) {
        var target = e.getTarget('.tinebase-tokenedit-tokenbox', 10, true);
        
        this.getPositionEl().setStyle('display', target ? 'inline-block' : 'none');
        return target;
    },
    
    onNodeOver: function(target, dd, e, data) {
        var tokens = target.query('.tinebase-tokenedit-token'),
            nextToken = null,
            nextTokenBox = null,
            eventXY = e.getXY();
        
        // find the 'nearest' other token
        Ext.each(tokens, function(el) {
            var token = Ext.get(el),
                box = token.getBox();
                
            var dx = eventXY[0] > box[0] ?
                Math.max(0, Math.min(eventXY[0] - box[0], eventXY[0] - box['right'])) :
                Math.max(0, Math.min(box[0] - eventXY[0], box['right'] - eventXY[0]));
                
            var dy = eventXY[1] > box[1] ?
                Math.max(0, Math.min(eventXY[1] - box[1], eventXY[1] - box['bottom'])) :
                Math.max(0, Math.min(box[1] - eventXY[1], box['bottom'] - eventXY[1]));
            
            token.distance = dx + dy;
            
            if (! nextToken || token.distance < nextToken.distance) {
                nextToken = token;
                nextTokenBox = box;
            }
        }, this);
        
        if (nextToken) {
            // show positioning symbol left or right to nearest token
            data.insertPosition = eventXY[0] > (nextTokenBox['x'] + nextTokenBox['width']/2) ? 'insertAfter' : 'insertBefore';
            
            this.getPositionEl()[data.insertPosition](nextToken);
        } else {
            target.insertFirst(this.getPositionEl());
        }
        
        // cache nextToken
        data.nextToken = nextToken;
        return Ext.dd.DropZone.prototype.dropAllowed;
    },
    
    onNodeDrop : function(target, dd, e, data) {
        var field = Ext.getCmp(target.up('div').child('*[id^=ext-comp]').id),
            positionEl = this.getPositionEl();
        
        positionEl.setStyle('display', 'none');
        
        Ext.each(this.selection, function(el) {
            el.insertBefore(positionEl);
        }, this);
        
        // update source and target fields
        Ext.each([field].concat(data.sourceFields), function(f) {
            var value = [];
            
            Ext.each(f.itemCt.query('.tinebase-tokenedit-token'), function(el) {
                value.push(el.innerHTML);
            }, this);
            
            value = value.join(' ');
            value = Ext.util.Format.htmlDecode(value.replace(/\s*\u21A9\s*/g, '\n'));
            
            f.setValue(value);
        }, this);
    },
    
    getPositionEl: function() {
        if (! this.positionEl) {
            this.positionEl = this.editDialog.getEl().createChild({
                'tag':  'span',
                'class': 'tinebase-tokenedit-position',
                'html' : '&nbsp;'
            });
        }
        
        return this.positionEl;
    }
};/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.dialog');

/**
 * Wizard Panel
 * 
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.WizardPanel
 * @extends     Ext.Panel
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @constructor
 * @param {Object} config The configuration options.
 */
Tine.widgets.dialog.WizardPanel = Ext.extend(Ext.Panel, {
    
    // private config overrides
    cls: 'tw-editdialog',
    border: false,
    layout: 'card',
    activeItem: 0,
    
    /**
     * init this wizard
     */
    initComponent: function() {
        this.addEvents(
            /**
             * @event cancel
             * @desc Fired when user pressed cancel button
             * @param {WizardPanel} this
             * @parm {Ext.Panel} active panel
             */
            'cancel',
            
            /**
             * @event finish
             * @desc Fired when user pressed finish button
             * @param {WizardPanel} this
             * @parm {Ext.Panel} active panel
             */
            'finish'
        );
        
        this.initFooterBar();
        
        this.on('afterrender', this.onAfterRender, this);
        Tine.widgets.dialog.WizardPanel.superclass.initComponent.call(this);
    },
    
    /**
     * back button handler
     */
    onBackButton: function() {
        return Ext.isFunction(this.layout.activeItem.onBackButton) ?
            this.layout.activeItem.onBackButton() :
            this.navigate(-1);
    },
    
    /**
     * next button handler
     */
    onNextButton: function() {
        return Ext.isFunction(this.layout.activeItem.onNextButton) ?
            this.layout.activeItem.onNextButton() :
            this.navigate(+1);
    },
    
    /**
     * cancel button handler
     */
    onCancelButton: function() {
        if (Ext.isFunction(this.layout.activeItem.onCancelButton)) {
            return this.layout.activeItem.onCancelButton();
        }
        
        else {
            this.fireEvent('cancel', this, this.layout.activeItem);
        }
    },
    
    /**
     * finish button handler
     */
    onFinishButton: function() {
        if (Ext.isFunction(this.layout.activeItem.onFinishButton)) {
            return this.layout.activeItem.onFinishButton();
        }
        
        else {
            this.fireEvent('finish', this, this.layout.activeItem);
        }
    },
    
    /**
     * navigate to panel
     * 
     * @param {Number/String} ref
     */
    navigate: function(ref) {
        var currentIndex = this.items.indexOf(this.layout.activeItem),
            toActivatePanel = Ext.isNumber(ref) ? 
                this.items.get(currentIndex + ref) :
                this.items.get(ref);
        
        if (toActivatePanel) {
            this.layout.setActiveItem(toActivatePanel);
            toActivatePanel.doLayout();
        }
        
        this.manageButtons();
    },
    
    /**
     * manage footer buttons
     */
    manageButtons: function() {
        Ext.each(['back', 'next', 'cancel', 'finish'], function(key) {
            this[key + 'Button'].setDisabled(! this[key + 'IsAllowed']());
        }, this);
    },
    
    /**
     * check if back button is allowed
     */
    backIsAllowed: function() {
        var panelCondition = Ext.isFunction(this.layout.activeItem.backIsAllowed) ?
            this.layout.activeItem.backIsAllowed() : true;
        
        return panelCondition && this.items.indexOf(this.layout.activeItem) > 0;
    },
    
    /**
     * check if next button is allowed
     */
    nextIsAllowed: function() {
        var panelCondition = Ext.isFunction(this.layout.activeItem.nextIsAllowed) ?
            this.layout.activeItem.nextIsAllowed() : true;
        
        return panelCondition && this.items.indexOf(this.layout.activeItem) < this.items.getCount() -1;
    },
    
    /**
     * check if cancel button is allowed
     */
    cancelIsAllowed: function() {
        var panelCondition = Ext.isFunction(this.layout.activeItem.cancelIsAllowed) ?
            this.layout.activeItem.cancelIsAllowed() : true;
        
        return panelCondition && true;
    },
    
    /**
     * check if finish button is allowed
     */
    finishIsAllowed: function() {
        var panelCondition = Ext.isFunction(this.layout.activeItem.finishIsAllowed) ?
            this.layout.activeItem.finishIsAllowed() : true;
        
        return panelCondition && this.items.indexOf(this.layout.activeItem) == this.items.getCount() -1;
    },
    
    /**
     * init footer bar
     */
    initFooterBar: function() {
        this.fbar = ['->',{
            text: _('Back'),
            minWidth: 70,
            ref: '../backButton',
            iconCls: 'action_previous',
            scope: this,
            handler: this.onBackButton
        }, {
            text: _('Next'),
            minWidth: 70,
            ref: '../nextButton',
            iconCls: 'action_next',
            scope: this,
            handler: this.onNextButton
        }, {
            text: _('Cancel'),
            minWidth: 70,
            ref: '../cancelButton',
            iconCls: 'action_cancel',
            scope: this,
            handler: this.onCancelButton
        }, {
            text: _('Finish'),
            minWidth: 70,
            ref: '../finishButton',
            iconCls: 'action_saveAndClose',
            scope: this,
            handler: this.onFinishButton
        }];
    },
    
    /**
     * called after this wizard is rendered
     */
    onAfterRender: function() {
        this.manageButtons();
    }
});

Ext.reg('tw-wizardpanel', Tine.widgets.dialog.WizardPanel);/*
 * Tine 2.0
 * 
 * @package     Admin
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine.widgets', 'Tine.widgets.dialog');

/**
 * admin settings panel
 * 
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.AdminPanel
 * @extends     Tine.widgets.dialog.EditDialog
 * 
 * <p>Admin Panel</p>
 * <p><pre>
 * </pre></p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * @param       {Object} config
 * @constructor
 * Create a new Tine.widgets.dialog.AdminPanel
 */
Tine.widgets.dialog.AdminPanel = Ext.extend(Tine.widgets.dialog.EditDialog, {
    /**
     * @private
     */
    appName: 'Admin',
    recordClass: Tine.Tinebase.Model.Config,
    evalGrants: false,
    
    //private
    initComponent: function(){
        this.record = new this.recordClass({
            id: this.appName
        });
        
        Tine.widgets.dialog.AdminPanel.superclass.initComponent.call(this);
    },
    
    /**
     * executed after record got updated from proxy
     */
    onRecordLoad: function() {
        // interrupt process flow until dialog is rendered
        if (! this.rendered) {
            this.onRecordLoad.defer(250, this);
            return;
        }
        
        this.window.setTitle(String.format(_('Change settings for application {0}'), this.appName));
        
        if (this.fireEvent('load', this) !== false) {
            var settings = this.record.get('settings'),
                form = this.getForm();
            for (var setting in settings) {
                form.findField(setting).setValue(settings[setting]);
            }
        
            form.clearInvalid();
            
            this.loadMask.hide();
        }
    },
    
    /**
     * executed when record gets updated from form
     */
    onRecordUpdate: function() {
        // merge changes from form into settings
        var settings = this.record.get('settings'),
            form = this.getForm(),
            newSettings = {};

        for (var setting in settings) {
            newSettings[setting] = form.findField(setting).getValue();
        }
        
        this.record.set('settings', newSettings);
        
        // TODO update registry
    },
    
    /**
     * returns dialog
     * 
     * NOTE: when this method gets called, all initalisation is done.
     * 
     * @return {Object}
     * @private
     */
    getFormItems: function() {
        
        return {
            xtype: 'tabpanel',
            activeTab: 0,
            border: false,
            items: [{
                title: this.app.i18n._('Defaults'),
                autoScroll: true,
                border: false,
                frame: true,
                xtype: 'columnform',
                formDefaults: {
                    anchor: '90%',
                    labelSeparator: '',
                    columnWidth: 1
                },
                items: this.getConfigItems()
            }]
        };
    },
    
    /**
     * get config items
     * - overwrite this in subclasses
     * 
     * @return {Array}
     */
    getConfigItems: function() {
        return [];
    }
});
/**
 * Tine 2.0
 * 
 * @package     Tine
 * @subpackage  Widgets
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * TODO         add general local + modal dialog (can be used here, in Tine.Courses.AddMemberDialog and ...)
 */

Ext.ns('Tine.widgets', 'Tine.widgets.dialog');

/**
 * Generic 'Credentials' dialog
 * 
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.CredentialsDialog
 * @extends     Tine.widgets.dialog.EditDialog
 * @constructor
 * @param       {Object} config The configuration options.
 */
Tine.widgets.dialog.CredentialsDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {

    credentialsId: null,
    
    /**
     * @private
     */
    windowNamePrefix: 'CredentialsWindow_',
    loadRecord: false,
    tbarItems: [],
    evalGrants: false,
    sendRequest: true,
    mode: 'local',
    
    /**
     * returns dialog
     */
    getFormItems: function() {
        return {
            bodyStyle: 'padding:5px;',
            buttonAlign: 'right',
            labelAlign: 'top',
            border: false,
            layout: 'form',
            defaults: {
                xtype: 'textfield',
                anchor: '90%',
                listeners: {
                    scope: this,
                    specialkey: function(field, event) {
                        if (event.getKey() == event.ENTER) {
                            this.onApplyChanges();
                        }
                    }
                }
            },
            items: [{
                fieldLabel: _('Username'), 
                name: 'username',
                allowBlank: false
            },{
                fieldLabel: _('Password'), 
                name: 'password',
                inputType: 'password'
            }]
        };
    },
    
    /**
     * executed after record got updated from proxy
     * 
     * @private
     */
    onRecordLoad: function() {
        // interrupt process flow till dialog is rendered
        if (! this.rendered) {
            this.onRecordLoad.defer(250, this);
            return;
        }
        
        var title = this.windowTitle || this.app.i18n._('Please enter your credentials');
        this.window.setTitle(title);
        
        this.getForm().loadRecord(this.record);
        
        this.loadMask.hide();
    },
    
    /**
     * generic apply changes handler
     */
    onApplyChanges: function() {
        var form = this.getForm();
        if(form.isValid()) {
            var values = form.getValues();
            
            if (this.sendRequest) {
                this.loadMask.show();
                
                var params = {
                    method: this.appName + '.changeCredentials',
                    password: values.password,
                    username: values.username,
                    id: this.credentialsId
                };
                
                Ext.Ajax.request({
                    params: params,
                    scope: this,
                    success: function(_result, _request){
                        this.loadMask.hide();
                        this.fireEvent('update', _result);
                        this.purgeListeners();
                        this.window.close();
                    }
                });
            } else {
                this.fireEvent('update', values);
                this.window.close();
            }
            
        } else {
            Ext.MessageBox.alert(_('Errors'), _('Please fix the errors noted.'));
        }
    }
});

/**
 * credentials dialog popup / window
 */
Tine.widgets.dialog.CredentialsDialog.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: 240,
        height: 180,
        name: Tine.widgets.dialog.CredentialsDialog.windowNamePrefix + Ext.id(),
        contentPanelConstructor: 'Tine.widgets.dialog.CredentialsDialog',
        contentPanelConstructorConfig: config,
        modal: true
    });
    return window;
};
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @subpackage  widgets
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * @todo        add filter toolbar
 * @todo        use proxy store?
 */

Ext.ns('Tine.widgets', 'Tine.widgets.dialog');

/**
 * 'Edit Preferences' dialog
 *
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.Preferences
 * @extends     Ext.FormPanel
 * @constructor
 * @param       {Object} config The configuration options.
 */
Tine.widgets.dialog.Preferences = Ext.extend(Ext.FormPanel, {
    /**
     * @property {Locale.gettext} i18n
     */
    i18n: null,

    /**
     * @property {Tine.widgets.dialog.PreferencesCardPanel} prefsCardPanel
     */
    prefsCardPanel: null,
    
    /**
     * @property {Tine.widgets.dialog.PreferencesTreePanel} treePanel
     */
    treePanel: null,
    
    /**
     * @property {Object} prefPanels
     * here we store the pref panels for all apps
     */    
    prefPanels: {},

    /**
     * @property {boolean} adminMode
     * when adminMode is activated -> show defaults/forced values
     */    
    adminMode: false,

    /**
     * @property {Object} prefPanels
     * here we store the pref panels for all apps [admin mode]
     */    
    adminPrefPanels: {},
    
    /**
     * @cfg String  initialCardName to select after render
     */
    initialCardName: null,
    
    // private
    layout: 'fit',
    cls: 'tw-editdialog',
    anchor:'100% 100%',
    buttonAlign: 'right',
    border: false,
    
    //private
    initComponent: function(){
        this.addEvents(
            /**
             * @event cancel
             * Fired when user pressed cancel button
             */
            'cancel',
            /**
             * @event saveAndClose
             * Fired when user pressed OK button
             */
            'saveAndClose',
            /**
             * @event update
             * @desc  Fired when the record got updated
             * @param {Json String} data data of the entry
             */
            'update'
        );
        
        this.i18n = new Locale.Gettext();
        this.i18n.textdomain('Tinebase');

        this.initActions();
        this.initButtons();
        this.items = this.getItems();
        
        Tine.widgets.dialog.Preferences.superclass.initComponent.call(this);
    },
    
    /**
     * init actions
     * 
     * @todo only allow admin mode if user has admin right
     */
    initActions: function() {
        this.action_saveAndClose = new Ext.Action({
            text: _('Ok'),
            minWidth: 70,
            scope: this,
            handler: this.onSaveAndClose,
            iconCls: 'action_saveAndClose'
        });
    
        this.action_cancel = new Ext.Action({
            text: _('Cancel'),
            minWidth: 70,
            scope: this,
            handler: this.onCancel,
            iconCls: 'action_cancel'
        });

        this.action_switchAdminMode = new Ext.Action({
            text: _('Admin Mode'),
            minWidth: 70,
            scope: this,
            handler: this.onSwitchAdminMode,
            iconCls: 'action_adminMode',
            enableToggle: true
        });
    },
    
    /**
     * init buttons
     */
    initButtons: function() {
        this.buttons = [
            this.action_cancel,
            this.action_saveAndClose
        ];
       
        this.tbar = new Ext.Toolbar({
            items: [
                this.action_switchAdminMode
            ]
        });
    },
    
    /**
     * returns dialog
     * 
     * NOTE: when this method gets called, all initalisation is done.
     */
    getItems: function() {
        this.prefsCardPanel = new Tine.widgets.dialog.PreferencesCardPanel({
            region: 'center'
        });
        this.treePanel = new Tine.widgets.dialog.PreferencesTreePanel({
            title: _('Applications'),
            region: 'west',
            width: 200,
            border: false,
            frame: false,
            initialNodeId: this.initialCardName
        })
        return [{
            xtype: 'panel',
            autoScroll: true,
            border: false,
            frame: false,
            layout: 'border',
            items: [
                this.treePanel,
                this.prefsCardPanel
            ]
        }];
    },
    
    /**
     * @private
     */
    onRender: function (ct, position) {
        Tine.widgets.dialog.Preferences.superclass.onRender.call(this, ct, position);
        
        // recalculate height, as autoHeight fails for Ext.Window ;-(
        this.setHeight(Ext.fly(this.el.dom.parentNode).getHeight());
        
        this.window.setTitle(this.i18n._('Edit Preferences'));
        this.loadMask = new Ext.LoadMask(ct, {msg: _('Loading ...')});
    },
    
    /**
     * @private
     */
    onCancel: function () {
        this.fireEvent('cancel');
        this.purgeListeners();
        this.window.close();
    },

    /**
     * @private
     * 
     * TODO check if this is working correctly
     */
    onDestroy: function(){
        // delete panels
        for (var panelName in this.adminPrefPanels) {
            if (this.adminPrefPanels.hasOwnProperty(panelName)) {
                if (this.adminPrefPanels[panelName] !== null) {
                    this.adminPrefPanels[panelName].destroy();
                    this.adminPrefPanels[panelName] = null;
                }
            }
        }
        for (panelName in this.prefPanels) {
            if (this.prefPanels.hasOwnProperty(panelName)) {
                if (this.prefPanels[panelName] !== null) {
                    this.prefPanels[panelName].destroy();
                    this.prefPanels[panelName] = null;
                }
            }
        }
        this.prefsCardPanel.destroy();
        this.prefsCardPanel = null;
        
        Tine.widgets.dialog.Preferences.superclass.onDestroy.apply(this, arguments);
    },
    
    /**
     * @private
     */
    onSaveAndClose: function(){
        this.onApplyChanges(true);
        this.fireEvent('saveAndClose');
    },
    
    /**
     * apply changes handler
     */
    onApplyChanges: function(closeWindow) {
        
        if (! this.isValid()) {
            Ext.MessageBox.alert(_('Errors'), _('You need to correct the red marked fields before config could be saved'));
            return;
        }
        
        this.loadMask.show();
        
        // get values from card panels
        var data = this.getValuesFromPanels();
        
        // save preference data
        Ext.Ajax.request({
            scope: this,
            params: {
                method: 'Tinebase.savePreferences',
                data: data,
                adminMode: (this.adminMode) ? 1 : 0
            },
            success: function(response) {
                this.loadMask.hide();
                
                // update registry
                this.updateRegistry(Ext.util.JSON.decode(response.responseText).results);
                
                if (closeWindow) {
                    this.purgeListeners();
                    this.window.close();
                }
            },
            failure: function (response) {
                Ext.MessageBox.alert(_('Errors'), _('Saving of preferences failed.'));
            }
        });
    },
    
    /**
     * check all panels if they are valid
     * 
     * @return {Boolean}
     */
    isValid: function() {
        var panel = {};
        var panelsToSave = (this.adminMode) ? this.adminPrefPanels : this.prefPanels;

        for (panelName in panelsToSave) {
            panel = panelsToSave[panelName];
            if (panel && typeof panel.isValid === 'function' && ! panel.isValid()) {
                return false;
            }
        }
        
        return true;
    },
    
    /**
     * get values from card panels
     * 
     * @return {Object} with form data
     */
    getValuesFromPanels: function() {
        var panel, data = {};
        var panelsToSave = (this.adminMode) ? this.adminPrefPanels : this.prefPanels;

        for (panelName in panelsToSave) {
            if (panelsToSave.hasOwnProperty(panelName)) {
                panel = panelsToSave[panelName];
                if (panel !== null) {
                    data[panel.appName] = {};
                    for (var j=0; j < panel.items.length; j++) {
                        var item = panel.items.items[j];
                        if (item && item.name) {
                            if (this.adminMode) {
                                // filter personal_only (disabled) items
                                if (! item.disabled) {
                                    data[panel.appName][item.prefId] = {value: item.getValue(), name: item.name};
                                    data[panel.appName][item.prefId].type = (Ext.getCmp(item.name + '_writable').getValue() == 1) ? 'default' : 'forced';
                                }
                            } else {
                                data[panel.appName][item.name] = {value: item.getValue()};
                            }
                        }
                    }
                }
            }
        }
        
        return data;
    },
    
    /**
     * update registry after saving of prefs
     * 
     * @param {Object} data
     */
    updateRegistry: function(data) {
        for (application in data) {
            if (data.hasOwnProperty(application)) {
                appPrefs = data[application];
                var registryValues = Tine[application].registry.get('preferences');
                var changed = false;
                for (var i=0; i < appPrefs.length; i++) {
                    if (registryValues.get(appPrefs[i].name) != appPrefs[i].value) {
                        registryValues.replace(appPrefs[i].name, appPrefs[i].value);
                        changed = true;
                    }
                }
                
                if (changed) {
                    Tine[application].registry.replace('preferences', registryValues);
                }
            }
        }
    },
    
    /**
     * onSwitchAdminMode
     * 
     * @private
     * 
     * @todo enable/disable apps according to admin right for applications
     */
    onSwitchAdminMode: function(button, event) {
        this.adminMode = (!this.adminMode);
        
        if (this.adminMode) {
            this.prefsCardPanel.addClass('prefpanel_adminMode');
        } else {
            this.prefsCardPanel.removeClass('prefpanel_adminMode');
        }
        
        // activate panel in card panel
        var selectedNode = this.treePanel.getSelectionModel().getSelectedNode();
        if (selectedNode) {
            this.showPrefsForApp(this.treePanel.getSelectionModel().getSelectedNode().id);
        }
        
        this.treePanel.checkGrants(this.adminMode);
    },

    /**
     * init app preferences store
     * 
     * @param {String} appName
     * 
     * @todo use generic json backend here?
     */
    initPrefStore: function(appName) {
        this.loadMask.show();
        
        // set filter to get only default/forced values if in admin mode
        var filter = (this.adminMode) ? [{field: 'account', operator: 'equals', value: {accountId: 0, accountType: 'anyone'}}] : '';
        
        var store = new Ext.data.JsonStore({
            fields: Tine.Tinebase.Model.Preference,
            baseParams: {
                method: 'Tinebase.searchPreferencesForApplication',
                applicationName: appName,
                filter: filter
            },
            listeners: {
                load: this.onStoreLoad,
                scope: this
            },
            root: 'results',
            totalProperty: 'totalcount',
            id: 'id',
            remoteSort: false
        });
        
        store.load();
    },

    /**
     * called after a new set of preference Records has been loaded
     * 
     * @param  {Ext.data.Store} this.store
     * @param  {Array}          loaded records
     * @param  {Array}          load options
     */
    onStoreLoad: function(store, records, options) {
        var appName = store.baseParams.applicationName;
        
        var card = new Tine.widgets.dialog.PreferencesPanel({
            prefStore: store,
            appName: appName,
            adminMode: this.adminMode
        });
        
        card.on('change', function(appName) {
            // mark card as changed in tree
            var node = this.treePanel.getNodeById(appName);
            node.setText(node.text + '*');
        }, this);
        
        // add to panel registry
        if (this.adminMode) {
            this.adminPrefPanels[appName] = card;
        } else {
            this.prefPanels[appName] = card;
        }
        
        this.activateCard(card, false);
        this.loadMask.hide();
    },
    
    /**
     * activateCard in preferences panel
     * 
     * @param {Tine.widgets.dialog.PreferencesPanel} panel
     * @param {boolean} exists
     */
    activateCard: function(panel, exists) {
        if (!exists) {
            this.prefsCardPanel.add(panel);
            this.prefsCardPanel.layout.container.add(panel);
        }
        this.prefsCardPanel.layout.setActiveItem(panel.id);
        panel.doLayout();
    },
    
    /**
     * showPrefsForApp 
     * - check stores (create new store if not exists)
     * - activate pref panel for app
     * 
     * @param {String} appName
     */
    showPrefsForApp: function(appName) {
        
        // TODO: invent panel hooking approach here
        if (appName === 'Tinebase.UserProfile') {
            
            if (! this.prefPanels[appName]) {
                this.prefPanels[appName] = new Tine.Tinebase.UserProfilePanel({
                    appName: appName
                });
                this.activateCard(this.prefPanels[appName], false);
            } 
        }
            
        var panel = (this.adminMode) ? this.adminPrefPanels[appName] : this.prefPanels[appName];

        if (!this.adminMode) {
            // check grant for pref and enable/disable button
            this.action_switchAdminMode.setDisabled(!Tine.Tinebase.common.hasRight('admin', appName));
        }
        
        // check stores/panels
        if (!panel) {
            // add new card + store
            this.initPrefStore(appName);
        } else {
            this.activateCard(panel, true);
        }
    }
});

/**
 * Timetracker Edit Popup
 */
Tine.widgets.dialog.Preferences.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: 800,
        height: 470,
        name: 'Preferences',
        contentPanelConstructor: 'Tine.widgets.dialog.Preferences',
        contentPanelConstructorConfig: config
    });
    return window;
};
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @subpackage  widgets
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * @todo        create generic app tree panel?
 * @todo        add button: set default value(s)
 */

Ext.ns('Tine.widgets', 'Tine.widgets.dialog');

/**
 * preferences application tree panel
 * 
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.PreferencesTreePanel
 * @extends     Ext.tree.TreePanel
 */
Tine.widgets.dialog.PreferencesTreePanel = Ext.extend(Ext.tree.TreePanel, {

    /**
     * @cfg String  initialNodeId to select after render
     */
    initialNodeId: null,
    
    // presets
    iconCls: 'x-new-application',
    rootVisible: true,
    border: false,
    autoScroll: true,
    bodyStyle: 'background-color:white',
    
    /**
     * initComponent
     * 
     */
    initComponent: function(){
        
        Tine.widgets.dialog.PreferencesTreePanel.superclass.initComponent.call(this);
        
        this.initTreeNodes();
        this.initHandlers();
        this.selectInitialNode.defer(200, this);
    },

    /**
     * select initial node
     */
    selectInitialNode: function() {
        var initialNode = (this.initialNodeId !== null) ? this.getNodeById(this.initialNodeId) : this.getRootNode();
        this.fireEvent('click', initialNode);
    },
    
    /**
     * initTreeNodes with Tinebase and apps prefs
     * 
     * @private
     */
    initTreeNodes: function() {
        
        // general preferences are tree root
        var treeRoot = new Ext.tree.TreeNode({
            text: _('General Preferences'),
            id: 'Tinebase',
            draggable: false,
            allowDrop: false,
            expanded: true
        });
        this.setRootNode(treeRoot);
        
        // add all apps
        var allApps = Tine.Tinebase.appMgr.getAll();

        // sort nodes by translated title (text property)
//        new Ext.tree.TreeSorter(this, {
//            folderSort: true,
//            dir: "asc"
//        });

        // add "My Profile"
        if (Tine.Tinebase.common.hasRight('manage_own_profile', 'Tinebase')) {
            var profileNode = new Ext.tree.TreeNode({
                text: _('My Profile'),
                cls: 'file',
                iconCls: 'tinebase-accounttype-user',
                id: 'Tinebase.UserProfile',
                leaf: null
            });
            treeRoot.appendChild(profileNode);
        }
        
        // console.log(allApps);
        allApps.each(function(app) {
            if (app && Ext.isFunction(app.getTitle)) {
                
                var node = new Ext.tree.TreeNode({
                    text: app.getTitle(),
                    cls: 'file',
                    id: app.appName,
                    iconCls: app.getIconCls('PreferencesTreePanel'),
                    leaf: null
                });
        
                treeRoot.appendChild(node);
            }
        }, this);
    },
    
    /**
     * initTreeNodes with Tinebase and apps prefs
     * 
     * @private
     */
    initHandlers: function() {
        this.on('click', function(node){
            // note: if node is clicked, it is not selected!
            node.getOwnerTree().selectPath(node.getPath());
            node.expand();
            
            // get parent pref panel
            var parentPanel = this.findParentByType(Tine.widgets.dialog.Preferences);

            // add panel to card panel to show prefs for chosen app
            parentPanel.showPrefsForApp(node.id);
            
        }, this);
        
        this.on('beforeexpand', function(_panel) {
            if(_panel.getSelectionModel().getSelectedNode() === null) {
                _panel.expandPath('/Tinebase');
                _panel.selectPath('/Tinebase');
            }
            _panel.fireEvent('click', _panel.getSelectionModel().getSelectedNode());
        }, this);
    },

    /**
     * check grants for tree nodes / apps
     * 
     * @param {Bool} adminMode
     */
    checkGrants: function(adminMode) {
        var root = this.getRootNode();
                
        root.eachChild(function(node) {
            // enable or disable according to admin rights / admin mode
            if (!Tine.Tinebase.common.hasRight('admin', node.id) && adminMode) {
                node.disable();
            } else {
                node.enable();
            }
        });
    }
});
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @subpackage  widgets
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * TODO         add pref description to input fields
 */

Ext.ns('Tine.widgets', 'Tine.widgets.dialog');

/**
 * preferences card panel
 * -> this panel is filled with the preferences subpanels containing the pref stores for the apps
 * 
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.PreferencesCardPanel
 * @extends     Ext.Panel
 */
Tine.widgets.dialog.PreferencesCardPanel = Ext.extend(Ext.Panel, {
    
    //private
    layout: 'card',
    border: false,
    frame: true,
    labelAlign: 'top',
    autoScroll: true,
    defaults: {
        anchor: '100%'
    },
    
    initComponent: function() {
        this.title = _('Preferences');
        Tine.widgets.dialog.PreferencesCardPanel.superclass.initComponent.call(this);
    }
});

/**
 * preferences panel with the preference input fields for an application
 * 
 * @todo add checkbox type
 */
Tine.widgets.dialog.PreferencesPanel = Ext.extend(Ext.Panel, {
    
    /**
     * the prefs store
     * @cfg {Ext.data.Store}
     */
    prefStore: null,
    
    /**
     * @cfg {String} appName
     */
    appName: 'Tinebase',

    /**
     * @cfg {Boolean} adminMode activated?
     */
    adminMode: false,
    
    //private
    layout: 'form',
    border: true,
    labelAlign: 'top',
    autoScroll: true,
    defaults: {
        anchor: '95%',
        labelSeparator: ''
    },
    bodyStyle: 'padding:5px',
    
    /**
     * init component
     * @private
     */
    initComponent: function() {
        
        this.addEvents(
            /**
             * @event change
             * @param appName
             * Fired when a value is changed
             */
            'change'
        );
        
        if (this.prefStore && this.prefStore.getCount() > 0) {
            Tine.log.debug('Tine.widgets.dialog.PreferencesPanel::initComponent() -> Adding pref items from store:');
            Tine.log.debug(this.prefStore);
            
            this.items = [];
            this.prefStore.each(function(pref) {
                // check if options available -> use combobox or textfield
                var fieldDef = {
                    fieldLabel: pref.get('label'),
                    name: pref.get('name'),
                    value: pref.get('value'),
                    listeners: {
                        scope: this,
                        change: function(field, newValue, oldValue) {
                            // fire change event
                            this.fireEvent('change', this.appName);
                        }
                    },
                    prefId: pref.id,
                    description: pref.get('description')
                };
                
                var options = pref.get('options');
                // NOTE: some prefs have no default and only one option (e.g. std email account)
                if (options.length > 1 || (options.length == 1 && options[0][0] !== '_default_')) {
                    Ext.apply(fieldDef, {
                        xtype: (this.adminMode ? 'lockCombo' : 'combo'),
                        store: pref.get('options'),
                        mode: 'local',
                        forceSelection: true,
                        allowBlank: false,
                        triggerAction: 'all'
                    });
                } else {
                    Ext.apply(fieldDef, {
                        xtype: (this.adminMode ? 'lockTextfield' : 'textfield'),
                        defaultValue: (options[0] && options[0][1]) ? options[0][1] : '',
                        isValid: function() {
                            // crude hack to guess type (prefs need DTD)
                            var type = this.defaultValue.match(/\(([0-9]*)\)$/) ? 'int' : 'string',
                                value = this.getValue();
                            
                            // default is always valid
                            if (value == '_default_') {
                                return true;
                            }
                            
                            if (type == 'int') {
                                return !! String(value).match(/\d+/);
                            }
                            
                            return true;
                        },
                        setValue: function(v) {
                            v = v == '_default_' ? this.defaultValue : v;
                            Ext.form.TextField.prototype.setValue.call(this, v);
                        },
                        getValue: function() {
                            var value = Ext.form.TextField.prototype.getValue.call(this);
                            return value == this.defaultValue ? '_default_' : value;
                        },
                        postBlur: function() {
                            var value = this.getValue();
                            if (value === '') {
                                this.setValue('_default_');
                            }
                        }
                    });
                }
                
                if (this.adminMode) {
                    // set lock (value forced => hiddenFieldData = '0')
                    fieldDef.hiddenFieldData = (pref.get('type') == 'forced') ? '0' : '1';
                    fieldDef.hiddenFieldId = pref.get('name') + '_writable';
                    // disable personal only fields (not quite sure why we get a string here in personal_only field)
                    fieldDef.disabled = (pref.get('personal_only') === '1' || pref.get('personal_only') === true);
                } else {
                    fieldDef.disabled = (pref.get('type') == 'forced');
                }
                
                try {
                    var fieldObj = Ext.ComponentMgr.create(fieldDef);
                    this.items.push(fieldObj);

                    // ugh a bit ugly
                    // what does that do??
                    pref.fieldObj = fieldObj;
                } catch (e) {
                    Tine.log.debug(e);
                    Tine.log.err('Unable to create preference field "' + pref.get('name') + '". Check definition!');
                    this.prefStore.remove(pref);
                }
            }, this);

        } else {
            this.html = '<div class="x-grid-empty">' + _('There are no preferences for this application.') + "</div>";
        }
        
        Ext.QuickTips.init();

        Tine.widgets.dialog.PreferencesPanel.superclass.initComponent.call(this);
    },
    
    /**
     * afterRender -> adds qtips to all elements
     * 
     * @private
     * 
     * @todo add qtip to label as well
     */
    afterRender: function() {
        Tine.widgets.dialog.PreferencesPanel.superclass.afterRender.call(this);
        
        // NOTE: server side translations have problems with quotes. Preferences with quotes
        //       in their description don't get translated. Thus we (re) translate them here
        //       as the js translations are much better
        var app = Tine.Tinebase.appMgr.get(this.appName),
            gt  = app ? app.i18n._.createDelegate(app.i18n) : _;
        
        if (this.items && this.items.items) {
            for (var i=0; i < this.items.items.length; i++) {
                var field = this.items.items[i];
                Ext.QuickTips.register({
                    target: field,
                    dismissDelay: 30000,
                    title: Ext.util.Format.htmlEncode(gt(field.fieldLabel)),
                    text: Ext.util.Format.htmlEncode(gt(field.description)),
                    width: 200
                });
            }
        }
    },
    
    /**
     * check validity for all panel items
     * 
     * @return {Boolean}
     */
    isValid: function() {
        var isValid = true;
        
        if (this.items && this.items.items) {
            var field;
            for (var i=0; i < this.items.items.length; i++) {
                field = this.items.items[i];
                if (! field.isValid()) {
                    field.markInvalid();
                    isValid = false;
                }
            }
        }
        
        return isValid;
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.dialog');

/**
 * Generic 'Import' dialog
 *
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.ImportDialog
 * @extends     Tine.widgets.dialog.EditDialog
 * @constructor
 * @param       {Object} config The configuration options.
 * 
 * TODO add app grid to show results when dry run is selected
 */
Tine.widgets.dialog.ImportDialog = Ext.extend(Tine.widgets.dialog.WizardPanel, {
    /**
     * @cfg {String} appName (required)
     */
    appName: null,
    
    /**
     * @cfg {String} modelName (required)
     */
    modelName: null, 
    
    /**
     * @cfg {String} defaultImportContainer
     */
    defaultImportContainer: null,
    
    /**
     * @property allowedFileExtensions
     * @type Array
     */
    allowedFileExtensions: null,
    
    /**
     * @property recordClass
     * @type Tine.Tinebase.data.Record
     */
    recordClass: null,
    
    /**
     * @property definitionsStore
     * @type Ext.data.JsonStore
     */
    definitionsStore: null,
    
    /**
     * @property selectedDefinition
     * @type Ext.data.Record
     */
    selectedDefinition: null,
    
    /**
     * @property exceptionStore
     * @type Ext.data.JsonStore
     */
    exceptionStore: null,
    
    /**
     * @property lastImportResponse
     * @type Objcet resutls of the last import request
     */
    lastImportResponse: null,
    
    // private config overrides
    windowNamePrefix: 'ImportWindow_',
    
    /**
     * init import wizard
     */
    initComponent: function() {

        Tine.log.debug('Tine.widgets.dialog.ImportDialog::initComponent this');
        Tine.log.debug(this);

        this.app = Tine.Tinebase.appMgr.get(this.appName);
        this.recordClass = Tine.Tinebase.data.RecordMgr.get(this.appName, this.modelName);

        this.allowedFileExtensions = [];

        // init definitions
        this.definitionsStore = new Ext.data.JsonStore({
            fields: Tine.Tinebase.Model.ImportExportDefinition,
            root: 'results',
            totalProperty: 'totalcount',
            remoteSort: false
        });

        if (Tine[this.appName].registry.get('importDefinitions')) {
            Ext.each(Tine[this.appName].registry.get('importDefinitions').results, function(defData) {
                var options = defData.plugin_options,
                    extension = options ? options.extension : null;
                
                defData.label = this.app.i18n._hidden(options && options.label ? options.label : defData.name);
                
                if (this.allowedFileExtensions.indexOf(extension) == -1) {
                    this.allowedFileExtensions = this.allowedFileExtensions.concat(extension);
                }
                
                this.definitionsStore.addSorted(new Tine.Tinebase.Model.ImportExportDefinition(defData, defData.id));
            }, this);
            this.definitionsStore.sort('label');
        }
        if (! this.selectedDefinition && Tine[this.appName].registry.get('defaultImportDefinition')) {
            this.selectedDefinition = this.definitionsStore.getById(Tine[this.appName].registry.get('defaultImportDefinition').id);
        }

        // init exception store
        this.exceptionStore = new Ext.data.JsonStore({
            mode: 'local',
            idProperty: 'index',
            fields: ['index', 'code', 'message', 'exception', 'resolveStrategy', 'resolvedRecord', 'isResolved']
        });

        this.items = [
            this.getFilePanel(),
            this.getOptionsPanel(),
            this.getConflictsPanel(),
            this.getSummaryPanel()
        ];

        Tine.widgets.dialog.ImportDialog.superclass.initComponent.call(this);
    },

    /**
     * close window on cancel
     */
    onCancelButton: function() {
        this.fireEvent('cancel', this, this.layout.activeItem);
        this.window.close();
    },

    /**
     * do import request
     * 
     * @param {Function} callback
     * @param {Object}   importOptions
     */
    doImport: function(callback, importOptions, clientRecordData) {
        Ext.Ajax.request({
            scope: this,
            timeout: 1800000, // 30 minutes
            callback: this.onImportResponse.createDelegate(this, [callback], true),
            params: {
                method: this.appName + '.import' + this.recordClass.getMeta('recordsName'),
                tempFileId: this.uploadButton.getTempFileId(),
                definitionId: this.definitionCombo.getValue(),
                importOptions: Ext.apply({
                    container_id: this.containerCombo.getValue(),
                    autotags: this.tagsPanel.getFormField().getValue()
                }, importOptions || {}),
                clientRecordData: clientRecordData
            }
        });
    },
    
    /**
     * called when import request sends response
     * 
     * @param {Object}   request
     * @param {Boolean}  success
     * @param {Object}   response
     * @param {Function} callback
     */
    onImportResponse: function(request, success, response, callback) {
        response = Ext.util.JSON.decode(response.responseText);
        
        Tine.log.debug('Tine.widgets.dialog.ImportDialog::onImportResponse server response');
        Tine.log.debug(response);
        
        this.lastImportResponse = response;
        
        // load exception store
        this.exceptionStore.loadData(response.exceptions);
        this.exceptionStore.filterBy(this.exceptionStoreFilter, this);
        
        // update conflict panel
//            var duplicatecount = response.duplicatecount || 0,
//                recordsName = this.app.i18n.n_(this.recordClass.getMeta('recordName'), this.recordClass.getMeta('recordsName'), duplicatecount);
//                
//            this.conflictsLabel.setText(String.format(this.conflictsLabel.rawText, duplicatecount, recordsName), false);
        if (this.exceptionStore.getCount()) {
            this.loadConflict(0);
        }
        
        // finlay apply callback
        if (Ext.isFunction(callback)) {
            callback.call(this, request, success, response);
        }
    },
    
    exceptionStoreFilter: function(record, id) {
        return record.get('code') == 629 && ! record.get('isResolved');
    },
    
    /********************************************************** FILE PANEL **********************************************************/
    
    /**
     * returns the file panel of this wizard (step 1)
     * 
     * @TODO restrict allowed extensions on definition selection OR
     *       restirct allowed definitions on file selection
     */
    getFilePanel: function() {
        if (this.filePanel) {
            return this.filePanel;
        }
        
        var def = this.selectedDefinition,
            description = def ? def.get('description') : '',
            options = def ? def.get('plugin_options') : null,
            example = options && options.example ? options.example : '';
            
        return {
            title: _('Choose File and Format'),
            layout: 'vbox',
            border: false,
            xtype: 'ux.displaypanel',
            frame: true,
            ref: '../filePanel',
            items: [{
                xtype: 'panel',
                baseCls: 'ux-subformpanel',
                title: _('Choose Import File'),
                height: 100,
                items: [{
                    xtype: 'label',
                    html: '<p>' + _('Please choose the file that contains the records you want to add to Tine 2.0').replace(/Tine 2\.0/g, Tine.title) + '</p><br />'
                }, {
                    xtype: 'tw.uploadbutton',
                    ref: '../../uploadButton',
                    text: String.format(_('Select file containing your {0}'), this.recordClass.getRecordsName()),
                    handler: this.onFileReady,
                    allowedTypes: this.allowedFileExtensions,
                    scope: this
                }]
            }, {
                xtype: 'panel',
                baseCls: 'ux-subformpanel',
                title: _('What should the file you upload look like?'),
                flex: 1,
                items: [/*{
                    xtype: 'label',
                    cls: 'tb-login-big-label',
                    html: _('What should the file you upload look like?') + '<br />'
                },*/ {
                    xtype: 'label',
                    html: '<p>' + _('Tine 2.0 does not understand all kind of files you might want to upload. You will have to manually adjust your file so Tine 2.0 can handle it.').replace(/Tine 2\.0/g, Tine.title) + '</p><br />'
                }, {
                    xtype: 'label',
                    html: '<p>' + _('Following you find a list of all supported import formats and a sample file, how Tine 2.0 expects your file to look like.').replace(/Tine 2\.0/g, Tine.title) + '</p><br />'
                }, {
                    xtype: 'label',
                    html: '<p>' + _('Please select the import format of the file you want to upload').replace(/Tine 2\.0/g, Tine.title) + '</p>'
                }, {
                    xtype: 'combo',
                    ref: '../../definitionCombo',
                    store: this.definitionsStore,
                    displayField:'label',
                    valueField:'id',
                    mode: 'local',
                    triggerAction: 'all',
                    editable: false,
                    allowBlank: false,
                    forceSelection: true,
                    width: 400,
                    value: this.selectedDefinition ? this.selectedDefinition.id : null,
                    listeners: {
                        scope: this,
                        'select': this.onDefinitionSelect
                    }
                }, {
                    xtype: 'label',
                    ref: '../../exampleLink',
                    html: example ? ('<p><a href="' + example + '">' + _('Download example file') + '</a></p>') : '<p>&nbsp;</p>'
                }, {
                    xtype: 'displayfield',
//                    fieldLabel: _('Import description'),
                    ref: '../../definitionDescription',
                    height: 70,
                    value: description,
                    cls: 'x-ux-display-background-border',
                    style: 'padding-left: 5px;'
                }]
            }],
            nextIsAllowed: (function() {
                return this.definitionCombo && this.definitionCombo.getValue() && this.uploadButton && this.uploadButton.fileRecord;
            }).createDelegate(this)
        };
    },
    
    onFileReady: function() {
        this.manageButtons();
    },
    
    /**
     * select handler of definition combo
     */
    onDefinitionSelect: function(combo, record, index) {
        var description = record.get('description'),
            options = record.get('plugin_options'),
            example = options && options.example ? options.example : '';
        
        this.selectedDefinition = record;
        
        this.definitionDescription.setValue(description);
        this.exampleLink.setText(example ? ('<p><a href="' + example + '">' + _('Download example file') + '</a></p>') : '<p>&nbsp;</p>', false);
        
        this.manageButtons();
    },
    
    /**
     * get options of the plugin from the currently selected definition
     */
    getImportPluginOptions: function() {
        var options = this.selectedDefinition ? this.selectedDefinition.get('plugin_options') : null;
            
        return options || {};
    },
    
    /********************************************************** OPTIONS PANEL **********************************************************/
    getOptionsPanel: function() {
        if (this.optionsPanel) {
            return this.optionsPanel;
        }
        
        return {
            title: _('Set Import Options'),
            layout: 'fit',
            border: false,
            xtype: 'form',
            frame: true,
            ref: '../optionsPanel',
            items: [{
                xtype: 'label',
                html: '<p>' + String.format(_('Select {0} to add you {1} to:'), this.recordClass.getContainerName(), this.recordClass.getRecordsName()) + '</p>'
            }, new Tine.widgets.container.selectionComboBox({
                id: this.app.appName + 'EditDialogContainerSelector',
                width: 300,
                ref: '../containerCombo',
                stateful: false,
                containerName: this.recordClass.getContainerName(),
                containersName: this.recordClass.getContainersName(),
                appName: this.appName,
                value: this.defaultImportContainer,
                requiredGrant: false
            }), new Tine.widgets.tags.TagPanel({
                app: this.appName,
                ref: '../tagsPanel',
                style: 'margin-top: 15px; border: 1px solid silver; border-top: none;',
                border: true,
                collapsible: false,
                height: 200
            })],
            
            listeners: {
                scope: this,
                show: function() {
                   var options = this.getImportPluginOptions();
                
                    if (options.autotags) {
                        var tags = options.autotags;
                        
                        Ext.each([].concat(tags), function(tag) {
                            tag.name = this.app.i18n._hidden(tag.name);
                            tag.description = this.app.i18n._hidden(tag.description);
                        }, this);
                        
                        this.tagsPanel.getFormField().setValue(tags);
                    }
                    
                    this.containerCombo.setValue(options.container_id ? options.container_id : this.defaultImportContainer);
                }
            },
            
            /**
             * check if next button is allowed
             */
            nextIsAllowed: (function() {
                return this.containerCombo && this.containerCombo.getValue();
            }).createDelegate(this),
            
            /**
             * next button handler for this panel
             */
            onNextButton: (function() {
                if (! this.checkMask) {
                    this.checkMask = new Ext.LoadMask(this.getEl(), {msg: _('Checking Import')});
                }
                
                this.checkMask.show();
            
                this.doImport(function(request, success, response) {
                    this.checkMask.hide();
                    
                    // jump to finish panel if no conflicts where detected
                    if (! response.duplicatecount) {
                        this.navigate(+2);
                    } else {
                        this.navigate(+1);
                    }
                }, {dryrun: true});
                
            }).createDelegate(this)
        }
    },
    
    
    /********************************************************** CONFLICT PANEL **********************************************************/
    
    getConflictsPanel: function() {
        if (this.conflictsPanel) {
            return this.conflictsPanel;
        }
        
        return {
            title: _('Resolve Conflicts'),
            layout: 'vbox',
            border: false,
            xtype: 'form',
            frame: true,
            ref: '../conflictsPanel',
            items: [/*{
                xtype: 'label',
                ref: '../conflictsLabel',
                rawText: '<p>' + _('There are {0} {1} that might already exist.') + '</p>',
                html: '<p></p>',
                height: 20
            },*/ {
                xtype: 'paging',
                ref: '../conflictPagingToolbar',
                pageSize: 1,
                beforePageText: _('Conflict'),
                firstText : _('First Conflict'),
                prevText : _('Previous Conflict'),
                nextText : _('Next Conflict'),
                lastText : _('Last Conflict'),
                store: this.exceptionStore,
                doLoad: this.loadConflict.createDelegate(this),
                onLoad: Ext.emptyFn,
                listeners: {afterrender: function(t){t.refresh.hide();}},
                items: [this.conflictIndexText = new Ext.Toolbar.TextItem({}), '->', {
                    text: _('Conflict is resolved'),
                    xtype: 'splitbutton',
                    scope: this,
                    handler: this.onResolveConflict,
                    menu: [{
                        text: _('Resolve all conflicts'),
                        scope: this,
                        handler: this.onResolveAllConflict
                    }]
                }]
            }, new Tine.widgets.dialog.DuplicateResolveGridPanel({
                flex: 1,
                ref: '../duplicateResolveGridPanel',
                header: false,
                app: this.app,
                store: new Tine.widgets.dialog.DuplicateResolveStore({
                    app: this.app,
                    recordClass: this.recordClass
                })
            })],
            listeners: {
                scope: this,
                show: function() {
                    if (! this.exceptionStore.isFiltered()) {
                        this.exceptionStore.filterBy(this.exceptionStoreFilter, this);
                        this.manageButtons();
                    }
                }
            },
            /**
             * check if next button is allowed
             */
            nextIsAllowed: (function() {
                var nextIsAllowed = true;
                
                // check if all conflicts are resolved
                this.exceptionStore.each(function(exception) {
                    if (! exception.get('isResolved')) {
                        nextIsAllowed = false;
                        return false;
                    }
                }, this);
                
                return nextIsAllowed;
                
            }).createDelegate(this)
        };
    },
    
    /**
     * mark current conflict resolved
     */
    onResolveConflict: function() {
        var index = this.conflictPagingToolbar.cursor,
            record = this.exceptionStore.getAt(index),
            resolveStore = this.duplicateResolveGridPanel.getStore(),
            resolveStrategy = resolveStore.resolveStrategy,
            resolveRecord = resolveStore.getResolvedRecord();
        
        // mark exception record resolved
        record.set('resolveStrategy', resolveStrategy);
        record.set('resolvedRecord', resolveRecord);
        record.set('isResolved', true);
        
        // load next conflict
        this.exceptionStore.filterBy(this.exceptionStoreFilter, this);
        this.manageButtons();
        
        this.loadConflict(this.exceptionStore.getCount() > index ? index : index-1);
        
        if (Ext.isFunction(arguments[0])) {
            return arguments[0].call(this);
        }
    },
    
    /**
     * resolve all conflicts using current strategy
     */
    onResolveAllConflict: function() {
        
        // @TODO: disable grid to spedup things?
        if (this.exceptionStore.getCount() > 0) {
            this.conflictMask.show();
            this.conflictMask.hidden = false;
            
            return this.onResolveConflict.defer(20, this, [this.onResolveAllConflict]);
        } else {
            this.conflictMask.hide();
            this.conflictMask.hidden = true;
        }
    },
    
    /**
     * load conflict with given index
     * 
     * @param {Number} index
     */
    loadConflict: function(index) {
        if (! this.conflictMask) {
            this.conflictMask = new Ext.LoadMask(this.getEl(), {msg: _('Processing Conflict Data'), hidden: true});
        }

        // give DOM the time to show loadMask
        if (this.conflictMask.hidden) {
            this.conflictMask.show();
            this.conflictMask.hidden = false;
            
            return this.loadConflict.defer(200, this, arguments);
        }
            
        var thisRecord = this.exceptionStore.getAt(this.conflictPagingToolbar.cursor),
            nextRecord = this.exceptionStore.getAt(index),
            resolveStore = this.duplicateResolveGridPanel.getStore();
            
        // preserv changes
        if (this.conflictPagingToolbar.cursor != index && thisRecord && resolveStore.getCount()) {
            thisRecord.set('resolvedRecord', resolveStore.getResolvedRecord());
            thisRecord.set('resolveStrategy', resolveStore.resolveStrategy);
        }
        
        if (nextRecord) {
            resolveStore.loadData(nextRecord.get('exception'), nextRecord.get('resolveStrategy') || resolveStore.resolveStrategy, nextRecord.get('resolvedRecord'));
        } else {
            resolveStore.removeAll();
            this.duplicateResolveGridPanel.getView().mainBody.update('<br />  ' + _('No conflict to resolve'));
            // defer navigation to complete this step before
            this.navigate.defer(200, this, [+1]);
        }
        
        
        // update paging toolbar
        var p = this.conflictPagingToolbar,
            ap = index+1,
            ps = this.exceptionStore.getCount();
            
        p.cursor = index;
        p.afterTextItem.setText(String.format(p.afterPageText, ps));
        p.inputItem.setValue(ap);
        p.first.setDisabled(ap == 1);
        p.prev.setDisabled(ap == 1);
        p.next.setDisabled(ap == ps);
        p.last.setDisabled(ap == ps);
        this.conflictIndexText.setText(nextRecord ? 
            String.format(_('(This is record {0} in your import file)'), nextRecord.get('index') + 1) :
            _('No conflict to resolve')
        );
        
        this.conflictMask.hide();
        this.conflictMask.hidden = true;
    },
    
    /********************************************************** SUMMARY PANEL **********************************************************/
    
    getSummaryPanel: function() {
        if (this.summaryPanel) {
            return this.summaryPanel;
        }
        var exceptionExpander = new Ext.ux.grid.RowExpander({
            tpl : new Ext.XTemplate('{[this.showClientRecord(values)]}', { showClientRecord: function(values) {
                if (values && values.exception && values.exception.clientRecord) {
                    // there is no generic detailsPanel retirval yet
//                    if (Ext.isObject(values.exception.clientRecord)) {
//                        var detailsPanel = new Tine.Addressbook.ContactGridDetailsPanel({});
//                        
//                        return detailsPanel.tpl.apply(values.exception.clientRecord);
//                    }
                    return Ext.util.Format.htmlEncode(Ext.encode(values.exception.clientRecord));
                } else {
                    return _('No Detail Informations');
                }
            }})
        });
        return {
            title: _('Summary'),
            border: false,
            xtype: 'ux.displaypanel',
            frame: true,
            ref: '../summaryPanel',
            autoScroll: true,
            items: [{
                height: 100,
                ref: '../summaryPanelInfo',
                border: false,
                layout: 'ux.display',
                layoutConfig: {
                    background: 'border'
                }
            }, {
                ref: '../summaryPanelFailures',
                baseCls: 'ux-arrowcollapse',
                cls: 'ux-arrowcollapse-plain',
                collapsible: true,
                hidden: true,
                flex: 1,
                title:'',
                items: [{
                    xtype: 'grid',
                    store: this.exceptionStore,
                    autoHeight: true,
                    plugins: exceptionExpander,
                    columns: [
                        exceptionExpander,
                        { id: 'index', header: _('Index'), width: 60, sortable: false, dataIndex: 'index'}, 
                        { id: 'failure', header: _('Failure'), width: 60, sortable: false, dataIndex: 'message'}
                    ],
                    autoExpandColumn: 'failure'
                }]
            }],
            listeners: {
                scope: this,
                show: this.onSummaryPanelShow
            },
            
            /**
             * finish button handler for this panel
             */
            onFinishButton: (function() {
                if (! this.importMask) {
                    this.importMask = new Ext.LoadMask(this.getEl(), {msg: String.format(_('Importing {0}'), this.recordClass.getRecordsName())});
                }
                this.importMask.show();
                
                // collect client data
                var clientRecordData = [];
                var importOptions = {};
                
                this.exceptionStore.clearFilter(true);
                this.exceptionStore.each(function(r) {
                    clientRecordData.push({
                        recordData: r.get('resolvedRecord').data,
                        resolveStrategy: r.get('resolveStrategy') || 'discard',
                        index: r.get('index')
                    });
                });
                
                this.doImport(function(request, success, response) {
                    this.importMask.hide();
                    
                    this.fireEvent('finish', this, this.layout.activeItem);
                    
                    if (Ext.isArray(response.exceptions) && response.exceptions.length > 0) {
                        // show errors and fence finish/back btn
                        this.backButton.setDisabled(true);
                        this.finishButton.setHandler(function() {this.window.close()}, this);
                        
                        this.summaryPanelInfo.hide();
                        this.exceptionStore.clearFilter(true);
                        
                        this.summaryPanelFailures.show();
                        this.summaryPanelFailures.setTitle(String.format(_('{0} records had failures and where discarded.'), response.exceptions.length));
                    } else {
                        this.window.close();
                    }
                }, importOptions, clientRecordData);
                
            }).createDelegate(this)
            
        }
    },
    
    /**
     * summary panel show handler
     */
    onSummaryPanelShow: function() {
        if (! this.summaryPanelInfo.rendered) {
            return this.onSummaryPanelShow.defer(100, this);
        }

        // calc metrics
        var rsp = this.lastImportResponse,
            totalcount = rsp.totalcount,
            failcount = 0,
            mergecount = 0,
            discardcount = 0;
            
        this.exceptionStore.clearFilter();
        this.exceptionStore.each(function(r) {
            var strategy = r.get('resolveStrategy');
            if (! strategy || !Ext.isString(strategy)) {
                failcount++;
            } else if (strategy == 'keep') {
                totalcount++;
            } else if (strategy.match(/^merge.*/)) {
                mergecount++;
            } else if (strategy == 'discard') {
                discardcount++;
            }
        }, this);
        
        var tags = this.tagsPanel.getFormField().getValue(),
            container = this.containerCombo.selectedContainer,
            info = [String.format(_('In total we found {0} records in your import file.'), rsp.totalcount + rsp.duplicatecount + rsp.failcount)];
            
            if (totalcount) {
                info.push(String.format(_('{0} of them will be added as new records into: "{1}".'), 
                    totalcount, 
                    Tine.Tinebase.common.containerRenderer(container).replace('<div', '<span').replace('</div>', '</span>')
                ));
            }
            
            if (mergecount + discardcount) {
                info.push(String.format(_('{0} of them where identified as duplicates.'), mergecount + discardcount));
                
                if (mergecount) {
                    info.push(String.format(_('From the identified duplicates {0} will be merged into the existing records.'), mergecount));
                }
                
                if (discardcount) {
                    info.push(String.format(_('From the identified duplicates {0} will be discarded.'), discardcount));
                }
            }
            
            if (Ext.isArray(tags) && tags.length) {
                var tagNames = [],
                    tagRecord = null;
                Ext.each(tags, function(tag) {
                    if (Ext.isString(tag)) {
                        tagRecord = this.tagsPanel.recordTagsStore.getById(tag);
                        tag = (tagRecord) ? tagRecord.data : null;
                    }
                    if (tag) {
                        tagNames.push(tag.name)
                    }
                }, this);
                info.push(String.format(_('All records will be tagged with: "{0}" so you can find them easily.'), tagNames.join(', ')));
            }
            
        this.summaryPanelInfo.update('<div style="padding: 5px;">' + info.join('<br />') + '</div>');
        
        // failures
        if (failcount) {
            this.exceptionStore.filterBy(function(record, id) {
                return ! (record.get('code') == 629 && ! record.get('isResolved'));
            });
            this.summaryPanelFailures.show();
            this.summaryPanelFailures.setTitle(String.format(_('{0} records have failures and will be discarded.'), failcount));
            
        }
    }
});

/**
 * credentials dialog popup / window
 */
Tine.widgets.dialog.ImportDialog.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: 800,
        height: 600,
        name: Tine.widgets.dialog.ImportDialog.windowNamePrefix + Ext.id(),
        contentPanelConstructor: 'Tine.widgets.dialog.ImportDialog',
        contentPanelConstructorConfig: config//,
//        modal: true
    });
    return window;
};
/**
 * Tine 2.0
 * 
 * @package     Tine
 * @subpackage  Widgets
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 */

Ext.ns('Tine.widgets', 'Tine.widgets.dialog');

/**
 * Generic 'Export' dialog
 *
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.ExportDialog
 * @extends     Tine.widgets.dialog.EditDialog
 * @constructor
 * @param       {Object} config The configuration options
 * 
 * TODO         add template for def combo (shows description, format?, ...)
 * 
 */
Tine.widgets.dialog.ExportDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
    
    /**
     * @cfg {String} appName
     */
    appName: null,
    
    /**
     * @private
     */
    windowNamePrefix: 'ExportWindow_',
    loadRecord: false,
    tbarItems: [],
    evalGrants: false,
    sendRequest: true,
    mode: 'local',
    
    //private
    initComponent: function(){
        this.app = Tine.Tinebase.appMgr.get(this.appName);
        
        this.recordClass = Tine.Tinebase.Model.ExportJob;
        this.saveAndCloseButtonText = _('Export');
        

        this.definitionsStore = new Ext.data.JsonStore({
            fields: Tine.Tinebase.Model.ImportExportDefinition,
            root: 'results',
            totalProperty: 'totalcount',
            idProperty: 'id',
            remoteSort: false
        });
        
        // check if initial data available
        if (Tine[this.appName].registry.get('exportDefinitions')) {
            Ext.each(Tine[this.appName].registry.get('exportDefinitions').results, function(defData) {
                var options = defData.plugin_options,
                    extension = options ? options.extension : null;
                
                defData.label = this.app.i18n._hidden(options && options.label ? options.label : defData.name);
                this.definitionsStore.addSorted(new Tine.Tinebase.Model.ImportExportDefinition(defData, defData.id));
            }, this);
            this.definitionsStore.sort('label');
        }
        
        Tine.widgets.dialog.ExportDialog.superclass.initComponent.call(this);
    },
    
    /**
     * executed after record got updated from proxy
     */
    onRecordLoad: function() {
        // interrupt process flow until dialog is rendered
        if (! this.rendered) {
            this.onRecordLoad.defer(250, this);
            return;
        }
        
        this.window.setTitle(String.format(_('Export {0} {1}'), this.record.get('count'), this.record.get('recordsName')));
    },

    /**
     * returns dialog
     */
    getFormItems: function() {
        if (this.record) {
            // remove all definitions that does not share the (grid) model / store.filter() did not do the job
            this.definitionsStore.each(function(record) {
                if (record.get('model') !== this.record.get('model')) {
                    this.definitionsStore.remove(record);
                }
            }, this);
        }
            
        return {
            bodyStyle: 'padding:5px;',
            buttonAlign: 'right',
            labelAlign: 'top',
            border: false,
            layout: 'form',
            defaults: {
                anchor: '100%'
            },
            items: [{
                xtype: 'combo',
                fieldLabel: _('Export definition'), 
                name:'export_definition_id',
                store: this.definitionsStore,
                displayField:'label',
                mode: 'local',
                triggerAction: 'all',
                editable: false,
                allowBlank: false,
                forceSelection: true,
                emptyText: _('Select Export Definition ...'),
                valueField: 'id',
                value: (this.definitionsStore.getCount() > 0) ? this.definitionsStore.getAt(0).id : null 
            }
            ]
        };
    },
    
    /**
     * apply changes handler
     */
    onApplyChanges: function(closeWindow) {
        var form = this.getForm();
        if (form.isValid()) {
            this.onRecordUpdate();
            
            // start download + pass definition id to export function
            var downloader = new Ext.ux.file.Download({
                params: {
                    method: this.record.get('exportFunction'),
                    requestType: 'HTTP',
                    filter: Ext.util.JSON.encode(this.record.get('filter')),
                    options: Ext.util.JSON.encode({
                        definitionId: this.record.get('export_definition_id')
                    })
                }
            }).start();
            this.window.close();
            
        } else {
            Ext.MessageBox.alert(_('Errors'), _('Please fix the errors noted.'));
        }
    }
});

/**
 * credentials dialog popup / window
 */
Tine.widgets.dialog.ExportDialog.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: 400,
        height: 150,
        name: Tine.widgets.dialog.ExportDialog.windowNamePrefix + Ext.id(),
        contentPanelConstructor: 'Tine.widgets.dialog.ExportDialog',
        contentPanelConstructorConfig: config,
        modal: true
    });
    return window;
};

Ext.ns('Tine.widgets.dialog');

Tine.widgets.dialog.MultiOptionsDialog = function(config) {
    
    Tine.widgets.dialog.MultiOptionsDialog.superclass.constructor.call(this, config);
    
    this.options = config.options || {};
    this.scope = config.scope || window;
};

/**
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.MultiOptionsDialog
 * @extends     Ext.FormPanel
 */
Ext.extend(Tine.widgets.dialog.MultiOptionsDialog, Ext.FormPanel, {
    /**
     * @cfg {Array} options
     * @see {Ext.fom.CheckBoxGroup}
     */   
    options: null,
    /**
     * @cfg {Object} scope
     */
    scope: null,
    /**
     * @cfg {String} questionText defaults to _('What would you like to do?')
     */
    questionText: null,
    /**
     * @cfg {String} invalidText defaults to _('You need to select an option!')
     */
    invalidText: null,
    /**
     * @cfg {Function} handler
     */
    handler: Ext.emptyFn,
    /**
     * @cfg {Boolean} allowCancel
     */
    allowCancel: false,
    
    windowNamePrefix: 'MultiOptionsDialog',
    bodyStyle:'padding:5px',
    layout: 'fit',
    border: false,
    cls: 'tw-editdialog',
    anchor:'100% 100%',
    deferredRender: false,
    buttonAlign: null,
    bufferResize: 500,
    
    initComponent: function() {
        // init buttons and tbar
        this.initButtons();
        
        this.itemsName = this.id + '-radioItems';
        
        // get items for this dialog
        this.items = [{
            layout: 'hbox',
            border: false,
            layoutConfig: {
                align:'stretch'
            },
            items: [{
                border: false,
                html: '<div class="x-window-dlg"><div class="ext-mb-icon ext-mb-question"></div></div>',
                flex: 0,
                width: 45
            }, {
                border: false,
                layout: 'fit',
                flex: 1,
                autoScroll: true,
                items: [{
                    xtype: 'label',
                    border: false,
                    cls: 'ext-mb-text',
                    html: this.questionText || _('What would you like to do?')
                }, {
                    xtype: 'radiogroup',
                    hideLabel: true,
                    itemCls: 'x-check-group-alt',
                    columns: 1,
                    name: 'optionGroup',
                    items: this.getItems()
                }]
            }]
        }];
        
        this.supr().initComponent.call(this);
    },
    
    /**
     * init buttons
     */
    initButtons: function() {
        this.fbar = ['->', {
            xtype: 'button',
            text: _('Ok'),
            minWidth: 70,
            scope: this,
            handler: this.onOk,
            iconCls: 'action_saveAndClose'
        }, {
            xtype: 'button',
            text: _('Cancel'),
            minWidth: 70,
            scope: this,
            hidden: !this.allowCancel,
            handler: this.onCancel,
            iconCls: 'action_cancel'
        }];
    },
    
    getItems: function() {
        var items = [];
        Ext.each(this.options, function(option) {
            items.push({
                checked: !! option.checked,
                fieldLabel: '',
                labelSeparator: '',
                boxLabel: option.text,
                name: this.itemsName,
                inputValue: option.name
            });
        }, this);
        
        return items;
    },
    
    onOk: function() {
        var field = this.getForm().findField('optionGroup');
        var selecedRadio = field.getValue();
        
        var option = selecedRadio ? selecedRadio.getGroupValue() : null;
        
        if (! option) {
            field.markInvalid(this.invalidText || _('You need to select an option!'));
            return;
        }
        this.handler.call(this.scope, option);
        this.window.close();
    },
    
    onCancel: function() {
        this.handler.call(this.scope, 'cancel');
        this.window.close();
    }
});

/**
 * grants dialog popup / window
 */
Tine.widgets.dialog.MultiOptionsDialog.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: config.width || 400,
        height: config.height || 150,
        closable: false,
        name: Tine.widgets.dialog.MultiOptionsDialog.windowNamePrefix + Ext.id(),
        contentPanelConstructor: 'Tine.widgets.dialog.MultiOptionsDialog',
        contentPanelConstructorConfig: config,
        modal: true
    });
    return window;
};

Ext.ns('Tine.widgets.dialog');

Tine.widgets.dialog.FileListDialog = function(config) {
    
    Tine.widgets.dialog.FileListDialog.superclass.constructor.call(this, config);
    
    this.options = config.options || {};
    this.scope = config.scope || window;
};

/**
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.FileListDialog
 * @extends     Ext.FormPanel
 */
Ext.extend(Tine.widgets.dialog.FileListDialog, Ext.FormPanel, {
    /**
     * @cfg {Array} options
     * @see {Ext.fom.CheckBoxGroup}
     */   
    options: null,
    /**
     * @cfg {Object} scope
     */
    scope: null,
    /**
     * @cfg {String} questionText defaults to _('What would you like to do?')
     */
    questionText: null,
    /**
     * @cfg {String} invalidText defaults to _('You need to select an option!')
     */
    invalidText: null,
    /**
     * @cfg {Function} handler
     */
    handler: Ext.emptyFn,
    /**
     * @cfg {Boolean} allowCancel
     */
    allowCancel: false,
    
    windowNamePrefix: 'FileListDialog',
    bodyStyle:'padding:5px',
    layout: 'fit',
    border: false,
    cls: 'tw-editdialog',
    anchor:'100% 100%',
    deferredRender: false,
    buttonAlign: null,
    bufferResize: 500,
    
    initComponent: function() {
        // init buttons and tbar
        this.initButtons();
        
        this.itemsName = this.id + '-fileList';
        
        // get items for this dialog
        this.items = [{
            layout: 'hbox',
            border: false,
            layoutConfig: {
                align:'stretch'
            },
            items: [{
                border: false,
                layout: 'fit',
                flex: 1,
                autoScroll: true,
                items: [{
                    xtype: 'label',
                    border: false,
                    cls: 'ext-mb-text',
                    html: this.text
                }]
            }]
        }];
        
        this.supr().initComponent.call(this);
    },
    
    /**
     * init buttons
     */
    initButtons: function() {
        this.fbar = ['->', 
        {
            xtype: 'button',
            text: _('No'),
            minWidth: 70,
            scope: this,
            handler: this.onCancel,
            iconCls: 'action_cancel'
        },
        {
            xtype: 'button',
            text: _('Yes'),
            minWidth: 70,
            scope: this,
            handler: this.onOk,
            iconCls: 'action_applyChanges'
        }];
    },
    
    onOk: function() {
        this.handler.call(this.scope, 'yes');
        this.window.close();
    },
    
    onCancel: function() {
        this.handler.call(this.scope, 'no');
        this.window.close();
    }
});

/**
 * grants dialog popup / window
 */
Tine.widgets.dialog.FileListDialog.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: config.width || 400,
        height: config.height || 150,
        closable: false,
        name: Tine.widgets.dialog.FileListDialog.windowNamePrefix + Ext.id(),
        contentPanelConstructor: 'Tine.widgets.dialog.FileListDialog',
        contentPanelConstructorConfig: config,
        modal: true
    });
    return window;
};
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.dialog');

/**
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.DuplicateMergeDialog
 * @extends     Ext.FormPanel
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * 
 * @param {Object} config The configuration options.
 */

Tine.widgets.dialog.DuplicateMergeDialog = Ext.extend(Ext.FormPanel, {
    
    appName : null,
    app: null,
    modelName: null,
    
    recordClass: null,
    recordProxy: null,
    loadMask: null,
    
    layout : 'fit',
    border : false,
    cls : 'tw-editdialog',    

    labelAlign : 'top',

    anchor : '100% 100%',
    deferredRender : false,
    buttonAlign : null,
    bufferResize : 500,
    
    /**
     * init component
     */
    initComponent: function() {

        if (!this.app) {
            this.app = Tine.Tinebase.appMgr.get(this.appName);
        }

        this.recordClass = Tine.Tinebase.data.RecordMgr.get(this.appName, this.modelName);
        this.selections = Ext.decode(this.selections);
        this.records = [];
        
        this.recordProxy = Tine[this.appName][this.recordClass.getMeta('recordName').toLowerCase() + 'Backend'];
        
        if(!this.recordProxy) {
            return;
        }
        
        Ext.each(this.selections, function(rec) {
            this.records.push(this.recordProxy.recordReader({responseText: Ext.encode(rec)}));
        }, this );
        
        Tine.log.debug('initComponent: appName: ', this.appName);
        Tine.log.debug('initComponent: app: ', this.app);

        // init actions
        this.initActions();

        // get items for this dialog
        this.items = this.getFormItems();

        Tine.widgets.dialog.DuplicateMergeDialog.superclass.initComponent.call(this);
    },
    /**
     * initializes the actions for the buttons
     */
    initActions: function() {
        this.action_cancel = new Ext.Action({
            text : _('Cancel'),
            minWidth : 70,
            scope : this,
            handler : this.onCancel,
            iconCls : 'action_cancel'
        });

        this.action_update = new Ext.Action({
            text : _('OK'),
            minWidth : 70,
            scope : this,
            handler : this.onUpdate,
            iconCls : 'action_saveAndClose'
        });
        
        this.fbar = [ '->', this.action_cancel, this.action_update ];
    },
    
    /**
     * is called to resolve the records
     */
    onUpdate: function() {
        var store = this.gridPanel.getStore(),
            updateRecord = store.getResolvedRecord(),
            removeRecords = [],
            allRecords = store.duplicates.concat([store.clientRecord]),
            strategy = this.gridPanel.actionCombo.getValue();
        
        Tine.log.debug('Tine.widgets.dialog.DuplicateMergeDialog::onUpdate() - strategy:');
        Tine.log.debug(this.gridPanel.actionCombo.getValue());
        
        Tine.log.debug('Tine.widgets.dialog.DuplicateMergeDialog::onUpdate() - allRecords:');
        Tine.log.debug(allRecords);
        
        if (strategy === 'mergeTheirs') {
            // we need to use the first id in update record
            updateRecord.id = allRecords[0].id;
        } else {
            // we need to use the second id (clientRecord) in update record
            updateRecord.id = allRecords[1].id;
        }
        updateRecord.data.id = updateRecord.id;

        Tine.log.debug('Tine.widgets.dialog.DuplicateMergeDialog::onUpdate() - updateRecord:');
        Tine.log.debug(updateRecord);
        
        Ext.each(allRecords, function(rec) {
            if (rec.id != updateRecord.id) {
                removeRecords.push(rec);
            }
        });

        Tine.log.debug('Tine.widgets.dialog.DuplicateMergeDialog::onUpdate() - removeRecords:');
        Tine.log.debug(removeRecords);
        
        this.loadMask = new Ext.LoadMask(this.getEl(), {msg: _('Merging Records...')});
        this.loadMask.show();
        
        this.recordProxy.saveRecord(updateRecord, {
            scope: this,
            success: function(newRecord) {
                this.removeDuplicates(removeRecords);
            },
            failure: function(failure) {
                this.onFailure('update', failure);
            }
        });
    },
    
    /**
     * remove duplicate records, when update of the new record succeeded
     * @param {Array} removeRecords
     */
    removeDuplicates: function(removeRecords) {
        this.recordProxy.deleteRecords(removeRecords, {
            scope: this,
            success: function() {
                this.onSuccess();
            },
            failure: function(failure) {
                this.onFailure('remove', failure);
            }
        });
    },
    
    /**
     * is called when a failure on saving the merge result or deleting the duplicate happens
     * @param {} step
     * @param {} failure
     */
    onFailure: function(step, failure) {
        if(step == 'update') {
             Tine.Tinebase.ExceptionHandler.handleRequestException(failure, function() { if (this.loadMask) this.loadMask.hide(); }, this);
        } else {
             Ext.MessageBox.alert(_('Merge Failed'), String.format(_('The merge succeeded, but the duplicate {0} could not be deleted.'), this.recordClass.getRecordName()), function() { Tine.Tinebase.ExceptionHandler.handleRequestException(failure); if (this.loadMask) this.loadMask.hide();}, this);
        }
    },
    /**
     * returns the form items of this panel
     * @return {}
     */
    getFormItems: function() {
        return {
            layout: 'border',
            items: [{
                region: 'center',
                layout: 'fit',
                items: [new Tine.widgets.dialog.DuplicateResolveGridPanel({
                    ref: '../../gridPanel',
                    app: this.app,
                    recordClass: this.recordClass,
                    store: new Tine.widgets.dialog.DuplicateResolveStore({
                        recordClass: this.recordClass,
                        defaultResolveStrategy: 'mergeMine',
                        app: this.app,
                        data: {
                            clientRecord: this.records.shift(),
                            duplicates: this.records
                        },
                        // TODO we might add some grants handling here to show the user if she can't edit, update or delete the records
                        // strategy can't be set to 'keep' here
                        checkEditGrant: Ext.emptyFn
                    }),
                    listeners: {
                        afterrender: function() {
                            var recordsName = this.recordClass.getRecordsName(),
                                recordName = this.recordClass.getRecordName();
                
                            // change actionComboListEls
                            this.actionCombo.store = new Ext.data.ArrayStore({
                                id: 0,
                                fields: ['value', 'text'],
                                data: [
                                    ['mergeMine', String.format(_('Merge {0}, prefer First'), recordsName)],
                                    ['mergeTheirs', String.format(_('Merge {0}, prefer Second'), recordsName)]
                                ]
                            });
                            this.actionCombo.setWidth(400);
                            this.onStoreLoad();
                
                            // change title
                            this.setTitle(String.format(_('Merge {0}'), recordsName));
                            
                            // change column headers
                            this.getColumnModel().setColumnHeader(this.getColumnModel().getIndexById('clientValue'), String.format(_('First {0}'), recordName));
                            this.getColumnModel().setColumnHeader(this.getColumnModel().getIndexById('value0'), String.format(_('Second {0}'), recordName));
                            this.getColumnModel().setColumnHeader(this.getColumnModel().getIndexById('finalValue'), String.format(_('Final {0}'), recordName));
                        }
                    }
                    })]
            }]
        };
    },
    
    onSuccess: function() {
        this.window.fireEvent('contentschange');
        this.onCancel();
    },
    
    onCancel: function() {
        this.window.purgeListeners();
        this.window.close();
    }
    
});

Tine.widgets.dialog.DuplicateMergeDialog.getWindow = function(config) {
    return Tine.WindowFactory.getWindow({
        width: 800,
        height: 600,
        name: Tine.widgets.dialog.ImportDialog.windowNamePrefix + Ext.id(),
        contentPanelConstructor: 'Tine.widgets.dialog.DuplicateMergeDialog',
        contentPanelConstructorConfig: config
    });
};/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.dialog');

/**
 * 
 * @namespace   Tine.widgets.dialog
 * @class       Tine.widgets.dialog.DuplicateResolveGridPanel
 * @extends     Ext.grid.EditorGridPanel
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @constructor
 * @param {Object} config The configuration options.
 */
Tine.widgets.dialog.DuplicateResolveGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     * instance of the app object (required)
     */
    app: null,

    // private config overrides
    cls: 'tw-editdialog',
    border: false,
    layout: 'fit',
    enableColumnMove: false,
    stripeRows: true,
    trackMouseOver: false,
    clicksToEdit:1,
    enableHdMenu : false,
    viewConfig : {
        forceFit:true
    },

    initComponent: function() {

        this.title = _('The record you try to add might already exist.');

        this.view = new Ext.grid.GroupingView({
            forceFit:true,
            hideGroupedColumn: true,
            groupTextTpl: '{group}'
        });

        this.initColumnModel();
        this.initToolbar();

        this.store.on('load', this.onStoreLoad, this);
        this.store.on('strategychange', this.onStoreLoad, this);
        this.on('cellclick', this.onCellClick, this);
        this.on('afterrender', this.onAfterRender, this);
        Tine.widgets.dialog.DuplicateResolveGridPanel.superclass.initComponent.call(this);
    },

    onAfterRender: function() {
        // apply initial strategy
        this.onStoreLoad();
    },

    /**
     * adopt final value to the one selected
     */
    onCellClick: function(grid, rowIndex, colIndex, e) {
        var dataIndex = this.getColumnModel().getDataIndex(colIndex),
            resolveRecord = this.store.getAt(rowIndex);

        if (resolveRecord && dataIndex && dataIndex.match(/clientValue|value\d+/)) {
            resolveRecord.set('finalValue', resolveRecord.get(dataIndex));

            var celEl = this.getView().getCell(rowIndex, this.getColumnModel().getIndexById('finalValue'));
            if (celEl) {
                Ext.fly(celEl).highlight();
            }
        }
    },

    /**
     * called when the store got new data
     */
    onStoreLoad: function() {
        var strategy = this.store.resolveStrategy;

        this.actionCombo.setValue(strategy);
        this.applyStrategy(strategy);
    },

    /**
     * select handler of action combo
     */
    onActionSelect: function(combo, record, idx) {
        var strategy = record.get('value');

        this.applyStrategy(strategy);
        this.store.applyStrategy(strategy);
    },

    /**
     * apply an action (generate final data)
     * - mergeTheirs:   merge keep existing values (discards client record)
     * - mergeMine:     merge, keep client values (discards client record)
     * - discard:       discard client record
     * - keep:          keep client record (create duplicate)
     * 
     * @param {Ext.data.Store} store with field records (DuplicateResolveModel)
     * @param {Sting} strategy
     */
    applyStrategy: function(strategy) {
        var cm = this.getColumnModel(),
            view = this.getView();

        if (cm) {
            cm.setHidden(cm.getIndexById('clientValue'), strategy == 'discard');
            cm.setHidden(cm.getIndexById('finalValue'), strategy == 'keep');

            if (view && view.grid) {
                this.getView().refresh();
            }
        }
    },

    /**
     * init our column model
     */
    initColumnModel: function() {
        var valueRendererDelegate = this.valueRenderer.createDelegate(this);

        this.cm = new Ext.grid.ColumnModel([{
            header: _('Field Group'), 
            width:50, 
            sortable: true, 
            dataIndex:'group', 
            id: 'group', 
            menuDisabled:true
        }, {
            header: _('Field Name'), 
            width:50, 
            sortable: true, 
            dataIndex:'i18nFieldName', 
            id: 'i18nFieldName', 
            menuDisabled:true
        }, {
            header: _('My Value'), 
            width:50, 
            resizable:false, 
            dataIndex: 'clientValue', 
            id: 'clientValue', 
            menuDisabled:true, 
            renderer: valueRendererDelegate
        }, {
            header: _('Existing Value'), 
            width:50, 
            resizable:false, 
            dataIndex: 'value' + this.store.duplicateIdx, 
            id: 'value' + this.store.duplicateIdx, 
            menuDisabled:true, 
            renderer: valueRendererDelegate
        }, {
            header: _('Final Value'), 
            width:50, 
            resizable:false, 
            dataIndex: 'finalValue', 
            id: 'finalValue', 
            menuDisabled:true, 
            renderer: valueRendererDelegate
        }]);

    },

    /**
     * init the toolbar
     */
    initToolbar: function() {
        this.tbar = [{
            xtype: 'label',
            text: _('Action:') + ' '
        }, {
            xtype: 'combo',
            ref: '../actionCombo',
            typeAhead: true,
            width: 250,
            triggerAction: 'all',
            lazyRender:true,
            mode: 'local',
            valueField: 'value',
            displayField: 'text',
            value: this.store.resolveStrategy,
            store: new Ext.data.ArrayStore({
                id: 0,
                fields: ['value', 'text'],
                data: [
                    ['mergeTheirs', _('Merge, keeping existing details')],
                    ['mergeMine',   _('Merge, keeping my details')],
                    ['discard',     _('Keep existing record and discard mine')],
                    ['keep',        _('Keep both records')]
                ]
            }),
            listeners: {
                scope: this, 
                select: this.onActionSelect
            }
        }];
    },

    /**
     * interceptor for all renderers
     * - manage colors
     * - pick appropriate renderer
     */
    valueRenderer: function(value, metaData, record, rowIndex, colIndex, store) {
        var fieldName = record.get('fieldName'),
            dataIndex = this.getColumnModel().getDataIndex(colIndex),
            renderer = Tine.widgets.grid.RendererManager.get(this.app, this.store.recordClass, fieldName, Tine.widgets.grid.RendererManager.CATEGORY_GRIDPANEL);

        // color management
        if (dataIndex && dataIndex.match(/clientValue|value\d+/) && !this.store.resolveStrategy.match(/(keep|discard)/)) {
            var action = record.get('finalValue') == value ? 'keep' : 'discard';
            metaData.css = 'tine-duplicateresolve-' + action + 'value';
        }
        
        return renderer.apply(this, arguments);
    }
});

/**
 * @class Tine.widgets.dialog.DuplicateResolveModel
 * A specific {@link Ext.data.Record} type that represents a field/clientValue/doublicateValues/finalValue set and is made to work with the
 * {@link Tine.widgets.dialog.DuplicateResolveGridPanel}.
 * @constructor
 */
Tine.widgets.dialog.DuplicateResolveModel = Ext.data.Record.create([
    {name: 'fieldName', type: 'string'},
    {name: 'fieldDef', type: 'fieldDef'},
    {name: 'group', type: 'string'},
    {name: 'i18nFieldName', type: 'string'},
    'clientValue', 'value0' , 'value1' , 'value2' , 'value3' , 'value4', 'finalValue'
]);

Tine.widgets.dialog.DuplicateResolveStore = Ext.extend(Ext.data.GroupingStore, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     * instance of the app object (required)
     */
    app: null,

    /**
     * @cfg {Ext.data.Record} recordClass
     * record definition class  (required)
     */
    recordClass: null,

    /**
     * @cfg {Ext.data.DataProxy} recordProxy
     */
    recordProxy: null,

    /**
     * @cfg {Object/Record} clientRecord
     */
    clientRecord: null,

    /**
     * @cfg {Array} duplicates
     * array of Objects or Records
     */
    duplicates: null,

    /**
     * @cfg {String} resolveStrategy
     * default resolve action
     */
    resolveStrategy: null,

    /**
     * @cfg {String} defaultResolveStrategy
     * default resolve action
     */
    defaultResolveStrategy: 'mergeTheirs',

    // private config overrides
    idProperty: 'fieldName',
    fields: Tine.widgets.dialog.DuplicateResolveModel,

    groupField: 'group',
//    groupOnSort: true,
//    remoteGroup: false,
    sortInfo: {field: 'group', oder: 'ASC'},

    constructor: function(config) {
        var initialData = config.data;
        delete config.data;

        this.reader = new Ext.data.JsonReader({
            idProperty: this.idProperty,
            fields: this.fields
        });

        Tine.widgets.dialog.DuplicateResolveStore.superclass.constructor.apply(this, arguments);

        if (! this.recordProxy && this.recordClass) {
            this.recordProxy = new Tine.Tinebase.data.RecordProxy({
                recordClass: this.recordClass
            });
        }

        // forece dublicate 0 atm.
        this.duplicateIdx = 0;

        if (initialData) {
            this.loadData(initialData);
        }
    },

    loadData: function(data, resolveStrategy, finalRecord) {
        // init records
        this.clientRecord = this.createRecord(data.clientRecord);

        this.duplicates = data.duplicates;
        Ext.each([].concat(this.duplicates), function(duplicate, idx) {this.duplicates[idx] = this.createRecord(this.duplicates[idx]);}, this);

        this.resolveStrategy = resolveStrategy || this.defaultResolveStrategy;

        if (finalRecord) {
            finalRecord = this.createRecord(finalRecord);
        }

        var fieldDefinitions = this.recordClass.getFieldDefinitions(),
            cfDefinitions = Tine.widgets.customfields.ConfigManager.getConfigs(this.app, this.recordClass, true);

        var recordsToAdd = [];
        Ext.each(fieldDefinitions.concat(cfDefinitions), function(field) {
            if (field.isMetaField || field.omitDuplicateResolving) return;

            var fieldName = field.name,
                fieldGroup = field.uiconfig ? field.uiconfig.group : field.group,
                recordData = {
                    fieldName: fieldName,
                    fieldDef: field,
                    i18nFieldName: field.label ? this.app.i18n._hidden(field.label) : this.app.i18n._hidden(fieldName),
                    clientValue: Tine.Tinebase.common.assertComparable(this.clientRecord.get(fieldName))
                };

            recordData.group = fieldGroup ? this.app.i18n._hidden(fieldGroup) : recordData.i18nFieldName;
            Ext.each([].concat(this.duplicates), function(duplicate, idx) {recordData['value' + idx] =  Tine.Tinebase.common.assertComparable(this.duplicates[idx].get(fieldName));}, this);

            var record = new Tine.widgets.dialog.DuplicateResolveModel(recordData, fieldName);

            if (finalRecord) {
                if (finalRecord.modified && finalRecord.modified.hasOwnProperty(fieldName)) {
//                    Tine.log.debug('Tine.widgets.dialog.DuplicateResolveStore::loadData ' + fieldName + 'changed from  ' + finalRecord.modified[fieldName] + ' to ' + finalRecord.get(fieldName));
                    record.set('finalValue', finalRecord.modified[fieldName]);

                }

                record.set('finalValue', finalRecord.get(fieldName));
            }

            recordsToAdd.push(record);
        }, this);

        this.insert(0, recordsToAdd);
        
        if (! finalRecord) {
            this.applyStrategy(this.resolveStrategy);
        }

        this.sortData();
        this.fireEvent('load', this);
    },

    /**
     * custom sorter
     * 
     * @param {String} f (ignored atm.)
     * @param {String} direction
     */
    sortData: function(f, direction) {
        direction = direction || 'ASC';
        var groupConflictScore = {};
            
        this.each(function(r) {
            var group = r.get('group'),
                myValue = String(r.get('clientValue')).replace(/^undefined$|^null$|^\[\]$/, ''),
                theirValue = String(r.get('value' + this.duplicateIdx)).replace(/^undefined$|^null$|^\[\]$/, '');
            
            if (! groupConflictScore.hasOwnProperty(group)) {
                groupConflictScore[group] = 990;
            }
            
            if (myValue || theirValue) {
                groupConflictScore[group] -= 1;
            }
            
            if (myValue != theirValue) {
                groupConflictScore[group] -= 10;
            }
            
        }, this);
        
        this.data.sort('ASC', function(r1, r2) {
            var g1 = r1.get('group'),
                v1 = String(groupConflictScore[g1]) + g1,
                g2 = r2.get('group'),
                v2 = String(groupConflictScore[g2]) + g2;
                
            return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
        });
    },

    /**
     * apply an strategy (generate final data)
     * - mergeTheirs:   merge keep existing values (discards client record)
     * - mergeMine:     merge, keep client values (discards client record)
     * - discard:       discard client record
     * - keep:          keep client record (create duplicate)
     * 
     * @param {Sting} strategy
     */
    applyStrategy: function(strategy) {
        Tine.log.debug('Tine.widgets.dialog.DuplicateResolveStore::applyStrategy() - action: ' + strategy);
        
        this.resolveStrategy = strategy;
        this.checkEditGrant();

        this.each(function(resolveRecord) {
            var theirs = resolveRecord.get('value' + this.duplicateIdx),
                mine = resolveRecord.get('clientValue'),
                location = this.resolveStrategy === 'keep' ? 'mine' : 'theirs';

            // undefined or empty theirs value -> keep mine
            if (this.resolveStrategy == 'mergeTheirs' && ['', 'null', 'undefined', '[]'].indexOf(String(theirs)) > -1) {
                location = 'mine';
            }

            // only keep mine if its not undefined or empty
            if (this.resolveStrategy == 'mergeMine' && ['', 'null', 'undefined', '[]'].indexOf(String(mine)) < 0) {
                location = 'mine';
            }

            // special merge for tags
            // TODO generalize me
            if (resolveRecord.get('fieldName') == 'tags') {
                resolveRecord.set('finalValue', Tine.Tinebase.common.assertComparable([].concat(this.resolveStrategy != 'discard' ? mine : []).concat(this.resolveStrategy != 'keep' ? theirs : [])));
            } else {
                resolveRecord.set('finalValue', location === 'mine' ? mine : theirs);
            }
            
            Tine.log.debug('Tine.widgets.dialog.DuplicateResolveStore::applyStrategy() - resolved record field: ' + resolveRecord.get('fieldName'));
            Tine.log.debug(resolveRecord);
        }, this);
        
        this.commitChanges();
    },
    
    checkEditGrant: function() {
        var grant = ! this.recordClass.getMeta('containerProperty') ? true : this.duplicates[this.duplicateIdx].get('container_id') ? this.duplicates[this.duplicateIdx].get(this.recordClass.getMeta('containerProperty')).account_grants['editGrant'] : false;

        // change strategy from merge to keep if user has no rights to merge
        if (this.resolveStrategy.match(/^merge/) && ! grant) {
            Tine.log.info('Tine.widgets.dialog.DuplicateResolveStore::checkEditGrant() - user has no editGrant, changing strategy to keep');
            this.resolveStrategy = 'keep';
            this.fireEvent('strategychange', this, this.resolveStrategy);
        }
    },

    /**
     * returns record with conflict resolved data
     */
    getResolvedRecord: function() {
        var record = (this.resolveStrategy == 'keep' ? this.clientRecord : this.duplicates[this.duplicateIdx]).copy();

        this.each(function(resolveRecord) {
            var fieldName = resolveRecord.get('fieldName'),
                finalValue = resolveRecord.get('finalValue'),
                modified = resolveRecord.modified || {};

            // also record changes
            if (modified.hasOwnProperty('finalValue')) {
                Tine.log.debug('Tine.widgets.dialog.DuplicateResolveStore::getResolvedRecord ' + fieldName + ' changed from ' + modified.finalValue + ' to ' + finalValue);
                record.set(fieldName, Tine.Tinebase.common.assertComparable(modified.finalValue));
            }

            record.set(fieldName, Tine.Tinebase.common.assertComparable(finalValue));

        }, this);

        Tine.log.debug('Tine.widgets.dialog.DuplicateResolveStore::getResolvedRecord() resolved record:');
        Tine.log.debug(record);
        
        return record;
    },

    /**
     * create record from data
     * 
     * @param {Object} data
     * @return {Record}
     */
    createRecord: function(data) {
        return Ext.isFunction(data.beginEdit) ? data : this.recordProxy.recordReader({responseText: Ext.encode(data)});
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

/**
 * central renderer manager
 * - get renderer for a given field
 * - register renderer for a given field
 * 
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.RendererManager
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @singleton
 */
Tine.widgets.grid.RendererManager = function() {
    var renderers = {};
    
    return {
        /**
         * const for category gridPanel
         */
        CATEGORY_GRIDPANEL: 'gridPanel',
        
        /**
         * const for category displayPanel
         */
        CATEGORY_DISPLAYPANEL: 'displayPanel',
        
        /**
         * default renderer - quote content
         */
        defaultRenderer: function(value) {
            return value ? Ext.util.Format.htmlEncode(value) : '';
        },
        
        /**
         * get renderer of well known field names
         * 
         * @param {String} fieldName
         * @return Function/null
         */
        getByFieldname: function(fieldName) {
            var renderer = null;
            
            if (fieldName == 'tags') {
                renderer = Tine.Tinebase.common.tagsRenderer;
            } else if (fieldName == 'notes') {
                // @TODO
                renderer = function(value) {return value ? _('has notes') : '';};
            } else if (fieldName == 'relations') {
                // @TODO
                renderer = function(value) {return value ? _('has relations') : '';};
            } else if (fieldName == 'customfields') {
                // @TODO
                // we should not come here!
            } else if (fieldName == 'container_id') {
                renderer = Tine.Tinebase.common.containerRenderer;
            }
            
            return renderer;
        },
        
        /**
         * get renderer by data type
         * 
         * @param {String} appName
         * @param {Record/String} modelName
         * @param {String} fieldName
         * @return Function
         */
        getByDataType: function(appName, modelName, fieldName) {
            var renderer = null,
                recordClass = Tine.Tinebase.data.RecordMgr.get(appName, modelName),
                fieldDefinition = recordClass ? recordClass.getField(fieldName) : null,
                fieldType = fieldDefinition ? fieldDefinition.type : 'auto';
                
            switch(fieldType) {
                case 'date':
                    renderer = Tine.Tinebase.common.dateRenderer;
                    break;
                case 'boolean':
                    renderer = Tine.Tinebase.common.booleanRenderer;
                    break;
                case 'keyField':
                    var keyFieldName = fieldDefinition.keyFieldConfigName;
                    renderer = Tine.Tinebase.widgets.keyfield.Renderer.get(appName, keyFieldName);
                    break;
                default: 
                    renderer = this.defaultRenderer;
                    break;
            }
            
            return renderer;
        },
        
        /**
         * returns renderer for given field
         * 
         * @param {String/Application} appName
         * @param {Record/String} modelName
         * @param {String} fieldName
         * @param {String} category {gridPanel|displayPanel} optional.
         */
        get: function(appName, modelName, fieldName, category) {
            var appName = Ext.isString(appName) ? appName : appName.appName,
                modelName = Ext.isFunction(modelName) ? modelName.getMeta('modelName') : modelName,
                categoryKey = [appName, modelName, fieldName, category].join('_'),
                genericKey = [appName, modelName, fieldName].join('_');
                
            // check for registered renderer
            var renderer = renderers[categoryKey] ? renderers[categoryKey] : renderers[genericKey];
            
            // check for common names
            if (! renderer) {
                renderer = this.getByFieldname(fieldName);
            }
            
            // check for known datatypes
            if (! renderer) {
                renderer = this.getByDataType(appName, modelName, fieldName);
            }
            
            return renderer ? renderer : this.defaultRenderer;
        },
        
        /**
         * register renderer for given field
         * 
         * @param {String/Application} appName
         * @param {Record/String} modelName
         * @param {String} fieldName
         * @param {Function} renderer
         * @param {String} category {gridPanel|displayPanel} optional.
         */
        register: function(appName, modelName, fieldName, renderer, category) {
            var appName = Ext.isString(appName) ? appName : appName.appName,
                modelName = Ext.isFunction(modelName) ? modelName.getMeta('modelName') : modelName,
                categoryKey = [appName, modelName, fieldName, category].join('_'),
                genericKey = [appName, modelName, fieldName].join('_');
                
            renderers[category ? categoryKey : genericKey] = renderer;
        }
    };
}();/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.Tinebase.widgets', 'Tine.widgets.grid');

/**
 * grid details panel
 * 
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.DetailsPanel
 * @extends     Ext.Panel
 * 
 * <p>Grid Details Panel</p>
 * <p><pre>
 * </pre></p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * @param       {Object} config
 * @constructor
 * Create a new Tine.widgets.grid.DetailsPanel
 */
Tine.widgets.grid.DetailsPanel = Ext.extend(Ext.Panel, {
    /**
     * @cfg {Number} defaultHeight
     * default Heights
     */
    defaultHeight: 125,
    
    /**
     * @property grid
     * @type Tine.widgets.grid.GridPanel
     */
    grid: null,

    /**
     * @property record
     * @type Tine.Tinebase.data.Record
     */
    record: null,
    
    /**
     * @property defaultInfosPanel holds panel for default information
     * @type Ext.Panel
     */
    defaultInfosPanel: null,
    
    /**
     * @property singleRecordPanel holds panel for single record details
     * @type Ext.Panel
     */
    singleRecordPanel: null,
    
    /**
     * @property multiRecordsPanel holds panel for multi selection aggregates/information
     * @type Ext.Panel
     */
    multiRecordsPanel: null,
            
    /**
     * @private
     */
    border: false,
    autoScroll: true,
    layout: 'card',
    activeItem: 0,
    
    /**
     * get panel for default information
     * 
     * @return {Ext.Panel}
     */
    getDefaultInfosPanel: function() {
        if (! this.defaultInfosPanel) {
            this.defaultInfosPanel = new Ext.Panel(this.defaults);
        }
        return this.defaultInfosPanel;
    },
    
    /**
     * get panel for single record details
     * 
     * @return {Ext.Panel}
     */
    getSingleRecordPanel: function() {
        if (! this.singleRecordPanel) {
            this.singleRecordPanel = new Ext.Panel(this.defaults);
        }
        return this.singleRecordPanel;
    },
    
    /**
     * get panel for multi selection aggregates/information
     * 
     * @return {Ext.Panel}
     */
    getMultiRecordsPanel: function() {
        if (! this.multiRecordsPanel) {
            this.multiRecordsPanel = new Ext.Panel(this.defaults);
        }
        return this.multiRecordsPanel;
    },
        
    /**
     * inits this details panel
     */
    initComponent: function() {
        
        this.items = [
            this.getDefaultInfosPanel(),
            this.getSingleRecordPanel(),
            this.getMultiRecordsPanel()
        ];
        
        // NOTE: this defaults overwrites configs in already instanciated configs -> see docu
        Ext.each(this.items, function(item) {
            Ext.applyIf(item, {
                border: false,
                autoScroll: true,
                layout: 'fit'
            });
        }, this);
        
        Tine.widgets.grid.DetailsPanel.superclass.initComponent.apply(this, arguments);
    },
    
    /**
     * update template
     * 
     * @param {Tine.Tinebase.data.Record} record
     * @param {Mixed} body
     */
    updateDetails: function(record, body) {
        this.tpl.overwrite(body, record.data);
    },
    
    /**
     * show default template
     * 
     * @param {Mixed} body
     */
    showDefault: function(body) {
        if (this.defaultTpl) {
            this.defaultTpl.overwrite(body);
        }
    },
    
    /**
     * show template for multiple rows
     * 
     * @param {Ext.grid.RowSelectionModel} sm
     * @param {Mixed} body
     */
    showMulti: function(sm, body) {
        if (this.multiTpl) {
            this.multiTpl.overwrite(body);
        }
    },
    
    /**
     * bind grid to details panel
     * 
     * @param {Tine.widgets.grid.GridPanel} grid
     */
    doBind: function(grid) {
        this.grid = grid;
        
        /*
        grid.getSelectionModel().on('selectionchange', function(sm) {
            if (this.updateOnSelectionChange) {
                this.onDetailsUpdate(sm);
            }
        }, this);
        */
        
        grid.store.on('load', function(store) {
            this.onDetailsUpdate(grid.getSelectionModel());
        }, this);
    },
    
    /**
     * update details panel
     * 
     * @param {Ext.grid.RowSelectionModel} sm
     */
    onDetailsUpdate: function(sm) {
        var count = sm.getCount();
        if (count === 0 || sm.isFilterSelect) {
            if(this.layout && Ext.isFunction(this.layout.setActiveItem)) {
                this.layout.setActiveItem(this.getDefaultInfosPanel());
            }
            this.showDefault(this.getDefaultInfosPanel().body);
            this.record = null;
        } else if (count === 1) {
            this.layout.setActiveItem(this.getSingleRecordPanel());
            this.record = sm.getSelected();
            this.updateDetails(this.record, this.getSingleRecordPanel().body);
        } else if (count > 1) {
            this.layout.setActiveItem(this.getMultiRecordsPanel());
            this.record = sm.getSelected();
            this.showMulti(sm, this.getMultiRecordsPanel().body);
        }
    },
    
    /**
     * get load mask
     * 
     * @return {Ext.LoadMask}
     */
    getLoadMask: function() {
        if (! this.loadMask) {
            this.loadMask = new Ext.LoadMask(this.el);
        }
        
        return this.loadMask;
    },
    
    /**
     * Wraps the items with default layout
     * 
     * @param {Array} items
     * @return {Object}
     */
    wrapPanel: function(items, labelWidth) {
        return {
            layout: 'fit',
            border: false,
            items: [{
                layout: 'vbox',
                border: false,
                layoutConfig: {
                    align:'stretch'
                },
                items: [{
                    layout: 'hbox',
                    flex: 1,
                    border: false,
                    layoutConfig: {
                        padding:'5',
                        align:'stretch'
                    },
                    defaults:{
                        margins:'0 5 0 0'
                    },
                    items: [{
                        flex: 2,
                        layout: 'ux.display',
                        labelWidth: labelWidth,
                        padding: 10,
                        layoutConfig: {
                            background: 'solid',
                            margins: '0 5 0 0'
                        },
                        items: items
                    }]
                }]
            }]
        }
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * TODO         add year to 'inweek' filter?
 */
Ext.ns('Tine.widgets.grid');

/**
 * Model of filter
 * 
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterModel
 * @extends     Ext.util.Observable
 * @constructor
 */
Tine.widgets.grid.FilterModel = function(config) {
    Ext.apply(this, config);
    Tine.widgets.grid.FilterModel.superclass.constructor.call(this);
    
    this.addEvents(
      /**
       * @event filtertrigger
       * is fired when user request to update list by filter
       * @param {Tine.widgets.grid.FilterToolbar}
       */
      'filtertrigger'
    );
    
    this.initComponent();
};

Ext.extend(Tine.widgets.grid.FilterModel, Ext.util.Observable, {
    /**
     * @cfg {String} label 
     * label for the filter
     */
    label: '',
    
    /**
     * @cfg {String} field
     * name of th field to filter
     */
    field: '',
    
    /**
     * @cfg {String} valueType
     * type of value
     */
    valueType: 'string',
    
    /**
     * @cfg {String} defaultValue
     * default value
     */
    defaultValue: null,
    
    /**
     * @cfg {Array} operators
     * valid operators
     */
    operators: null,
    
    /**
     * @cfg {String} defaultOperator
     * name of the default operator
     */
    defaultOperator: null,
    
    /**
     * @cfg {Array} customOperators
     * define custom operators
     */
    customOperators: null,
    
    /**
     * @cfg {Ext.data.Store|Array} 
     * used by combo valueType
     */
    store: null,
    
    /**
     * @cfg {String} displayField
     * used by combo valueType
     */
    displayField: null,
    
    /**
     * @cfg {String} valueField
     * used by combo valueType
     */
    valueField: null,
    filterValueWidth: 200,
    
    /**
     * @private
     */
    initComponent: function() {
        this.isFilterModel = true;
        
        if (! this.operators) {
            this.operators = [];
        }
        
        
        if (this.defaultOperator === null) {
            switch (this.valueType) {
                
                case 'date':
                    this.defaultOperator = 'within';
                    break;
                case 'account':
                case 'group':
                case 'user':
                case 'bool':
                case 'number':
                case 'percentage':
                case 'combo':
                case 'country':
                    this.defaultOperator = 'equals';
                    break;
                case 'string':
                default:
                    this.defaultOperator = 'contains';
                    break;
            }
        }
        
        if (this.defaultValue === null) {
            switch (this.valueType) {
                case 'customfield':
                case 'string':
                    this.defaultValue = '';
                    break;
                case 'bool':
                    this.defaultValue = '1';
                    break;
                case 'percentage':
                    this.defaultValue = '0';
                    break;
                case 'date':
                case 'account':
                case 'group':
                case 'user':
                case 'number':
                case 'country':
                default:
                    break;
            }
        }
    },
    
    onDestroy: Ext.emptyFn,
    
    /**
     * operator renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    operatorRenderer: function (filter, el) {
        var operatorStore = new Ext.data.JsonStore({
            fields: ['operator', 'label'],
            data: [
                {operator: 'contains',   label: _('contains')},
                {operator: 'regex',      label: _('reg. exp.')},
                {operator: 'equals',     label: _('is equal to')},
                {operator: 'greater',    label: _('is greater than')},
                {operator: 'less',       label: _('is less than')},
                {operator: 'not',        label: _('is not')},
                {operator: 'in',         label: _('one of')},
                {operator: 'notin',      label: _('none of')},
                {operator: 'before',     label: _('is before')},
                {operator: 'after',      label: _('is after')},
                {operator: 'within',     label: _('is within')},
                {operator: 'inweek',     label: _('is in week no.')},
                {operator: 'startswith', label: _('starts with')},
                {operator: 'endswith',   label: _('ends with')},
                {operator: 'definedBy',  label: _('defined by')}
            ].concat(this.getCustomOperators() || []),
            remoteSort: false,
            sortInfo: {
                field: 'label',
                direction: 'ASC'
            }
        });

        // filter operators
        if (this.operators.length == 0) {
            switch (this.valueType) {
                case 'string':
                    this.operators.push('contains', 'equals', 'startswith', 'endswith', 'not', 'in');
                    break;
                case 'customfield':
                    this.operators.push('contains', 'equals', 'startswith', 'endswith', 'not');
                    break;
                case 'date':
                    this.operators.push('equals', 'before', 'after', 'within', 'inweek');
                    break;
                case 'number':
                case 'percentage':
                    this.operators.push('equals', 'greater', 'less');
                    break;
                default:
                    this.operators.push(this.defaultOperator);
                    break;
            }
        }
        
        if (this.operators.length > 0) {
            operatorStore.each(function(operator) {
                if (this.operators.indexOf(operator.get('operator')) < 0 ) {
                    operatorStore.remove(operator);
                }
            }, this);
        }
        
        if (operatorStore.getCount() > 1) {
            var operator = new Ext.form.ComboBox({
                filter: filter,
                width: 80,
                id: 'tw-ftb-frow-operatorcombo-' + filter.id,
                mode: 'local',
                lazyInit: false,
                emptyText: _('select a operator'),
                forceSelection: true,
                typeAhead: true,
                triggerAction: 'all',
                store: operatorStore,
                displayField: 'label',
                valueField: 'operator',
                value: filter.get('operator') ? filter.get('operator') : this.defaultOperator,
                tpl: '<tpl for="."><div class="x-combo-list-item tw-ftb-operator-{operator}">{label}</div></tpl>',
                renderTo: el
            });
            operator.on('select', function(combo, newRecord, newKey) {
                if (combo.value != combo.filter.get('operator')) {
                    this.onOperatorChange(combo.filter, combo.value);
                }
            }, this);
            
            operator.on('blur', function(combo) {
                if (combo.value != combo.filter.get('operator')) {
                    this.onOperatorChange(combo.filter, combo.value);
                }
            }, this);
            
        } else if (this.operators[0] == 'freeform') {
            var operator = new Ext.form.TextField({
                filter: filter,
                width: 100,
                emptyText: this.emptyTextOperator || '',
                value: filter.get('operator') ? filter.get('operator') : '',
                renderTo: el
            });
        } else {
            var operator = new Ext.form.Label({
                filter: filter,
                width: 100,
                style: {margin: '0px 10px'},
                getValue: function() { return operatorStore.getAt(0).get('operator'); },
                text : operatorStore.getAt(0).get('label'),
                renderTo: el,
                setValue: Ext.emptyFn
            });
        }
        
        return operator;
    },
    
    /**
     * get custom operators
     * 
     * @return {Array}
     */
    getCustomOperators: function() {
        return this.customOperators || [];
    },
    
    /**
     * called on operator change of a filter row
     * @private
     */
    onOperatorChange: function(filter, newOperator) {
        filter.set('operator', newOperator);
        filter.set('value', '');
        
        // for date filters we need to rerender the value section
        if (this.valueType == 'date') {
            switch (newOperator) {
                case 'within':
                    filter.numberfield.hide();
                    filter.datePicker.hide();
                    filter.withinCombo.show();
                    filter.formFields.value = filter.withinCombo;
                    break;
                case 'inweek':
                    filter.withinCombo.hide();
                    filter.datePicker.hide();
                    filter.numberfield.show();
                    filter.formFields.value = filter.numberfield;
                    break;
                default:
                    filter.withinCombo.hide();
                    filter.numberfield.hide();
                    filter.datePicker.show();
                    filter.formFields.value = filter.datePicker;
            }
        }
    },
    
    /**
     * value renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    valueRenderer: function(filter, el) {
        var value,
            fieldWidth = this.filterValueWidth,
            commonOptions = {
                filter: filter,
                width: fieldWidth,
                id: 'tw-ftb-frow-valuefield-' + filter.id,
                renderTo: el,
                value: filter.data.value ? filter.data.value : this.defaultValue
            };
        
        switch (this.valueType) {
            case 'date':
                value = this.dateValueRenderer(filter, el);
                break;
            case 'percentage':
                value = new Ext.ux.PercentCombo(Ext.apply(commonOptions, {
                    listeners: {
                        'specialkey': function(field, e) {
                             if(e.getKey() == e.ENTER){
                                 this.onFiltertrigger();
                             }
                        },
                        'select': this.onFiltertrigger,
                        scope: this
                    }
                }));
                break;
            case 'user':
                value = new Tine.Addressbook.SearchCombo(Ext.apply(commonOptions, {
                    listWidth: 350,
                    emptyText: _('Search Account ...'),
                    userOnly: true,
                    name: 'organizer',
                    nameField: 'n_fileas',
                    useAccountRecord: true,
                    listeners: {
                        'specialkey': function(field, e) {
                             if(e.getKey() == e.ENTER){
                                 this.onFiltertrigger();
                             }
                        },
                        'select': this.onFiltertrigger,
                        scope: this
                    }
                }));
                break;
            case 'bool':
                value = new Ext.form.ComboBox(Ext.apply(commonOptions, {
                    mode: 'local',
                    forceSelection: true,
                    triggerAction: 'all',
                    store: [
                        [0, Locale.getTranslationData('Question', 'no').replace(/:.*/, '')], 
                        [1, Locale.getTranslationData('Question', 'yes').replace(/:.*/, '')]
                    ],
                    listeners: {
                        'specialkey': function(field, e) {
                             if(e.getKey() == e.ENTER){
                                 this.onFiltertrigger();
                             }
                        },
                        'select': this.onFiltertrigger,
                        scope: this
                    }
                }));
                break;
            case 'combo':
                var comboConfig = Ext.apply(commonOptions, {
                    mode: 'local',
                    forceSelection: true,
                    triggerAction: 'all',
                    store: this.store,
                    listeners: {
                        'specialkey': function(field, e) {
                             if(e.getKey() == e.ENTER){
                                 this.onFiltertrigger();
                             }
                        },
                        'select': this.onFiltertrigger,
                        scope: this
                    }
                });
                if (this.displayField !== null && this.valueField !== null) {
                    comboConfig.displayField = this.displayField;
                    comboConfig.valueField = this.valueField;
                }
                value = new Ext.form.ComboBox(comboConfig);
                break;
            case 'country':
                value = new Tine.widgets.CountryCombo(Ext.apply(commonOptions, {
                }));
                break;
            case 'customfield':
            case 'string':
            case 'number':
            default:
                value = new Ext.ux.form.ClearableTextField(Ext.apply(commonOptions, {
                    emptyText: this.emptyText,
                    listeners: {
                        scope: this,
                        specialkey: function(field, e){
                            if(e.getKey() == e.ENTER){
                                this.onFiltertrigger();
                            }
                        }
                    }
                }));
                break;
        }
        
        return value;
    },
    
    /**
     * called on value change of a filter row
     * @private
     */
    onValueChange: function(filter, newValue) {
        filter.set('value', newValue);
    },
    
    /**
     * render a date value
     * 
     * we place a picker and a combo in the dom element and hide the one we don't need yet
     */
    dateValueRenderer: function(filter, el) {
        var operator = filter.get('operator') ? filter.get('operator') : this.defaultOperator;
        
        var valueType = 'datePicker';
        switch (operator) {
            case 'within':
                valueType = 'withinCombo';
                break;
            case 'inweek':
                valueType = 'numberfield';
                break;
        }
        
        var pastOps = [
            ['dayThis',         _('today')], 
            ['dayLast',         _('yesterday')], 
            ['weekThis',        _('this week')], 
            ['weekLast',        _('last week')],
            ['weekBeforeLast',  _('the week before last')],
            ['monthThis',       _('this month')],
            ['monthLast',       _('last month')],
            ['quarterThis',     _('this quarter')],
            ['quarterLast',     _('last quarter')],
            ['yearThis',        _('this year')],
            ['yearLast',        _('last year')]
        ];
        
        var futureOps = [
            ['dayNext',         _('tomorrow')], 
            ['weekNext',        _('next week')], 
            ['monthNext',       _('next month')],
            ['quarterNext',     _('next quarter')],
            ['yearNext',        _('next year')]
        ];
        
        var comboOps = this.pastOnly ? pastOps : futureOps.concat(pastOps);
        var comboValue = 'weekThis';
        if (filter.data.value && filter.data.value.toString().match(/^[a-zA-Z]+$/)) {
            comboValue = filter.data.value.toString();
        } else if (this.defaultValue && this.defaultValue.toString().match(/^[a-zA-Z]+$/)) {
            comboValue = this.defaultValue.toString();
        }
        
        filter.withinCombo = new Ext.form.ComboBox({
            hidden: valueType != 'withinCombo',
            filter: filter,
            width: this.filterValueWidth,
            value: comboValue,
            renderTo: el,
            mode: 'local',
            lazyInit: false,
            forceSelection: true,
            typeAhead: true,
            triggerAction: 'all',
            store: comboOps,
            listeners: {
                'specialkey': function(field, e) {
                     if(e.getKey() == e.ENTER){
                         this.onFiltertrigger();
                     }
                },
                'select': this.onFiltertrigger,
                scope: this
            }
        });

        var pickerValue = '';
        if (Ext.isDate(filter.data.value)) {
            pickerValue = filter.data.value;
        } else if (Ext.isDate(Date.parseDate(filter.data.value, Date.patterns.ISO8601Long))) {
            pickerValue = Date.parseDate(filter.data.value, Date.patterns.ISO8601Long);
        } else if (Ext.isDate(this.defaultValue)) {
            pickerValue = this.defaultValue;
        }
        
        filter.datePicker = new Ext.form.DateField({
            hidden: valueType != 'datePicker',
            filter: filter,
            width: this.filterValueWidth,
            value: pickerValue,
            renderTo: el,
            listeners: {
                'specialkey': function(field, e) {
                     if(e.getKey() == e.ENTER){
                         this.onFiltertrigger();
                     }
                },
                'select': this.onFiltertrigger,
                scope: this
            }
        });
        
        filter.numberfield = new Ext.form.NumberField({
            hidden: valueType != 'numberfield',
            filter: filter,
            width: this.filterValueWidth,
            value: pickerValue,
            renderTo: el,
            minValue: 1,
            maxValue: 52,
            maxLength: 2,   
            allowDecimals: false,
            allowNegative: false,
            listeners: {
                scope: this,
                specialkey: function(field, e){
                    if(e.getKey() == e.ENTER){
                        this.onFiltertrigger();
                    }
                }
            }
        });
        
        // upps, how to get a var i only know the name of???
        return filter[valueType];
    },
    
    /**
     * @private
     */
    onFiltertrigger: function() {
        this.fireEvent('filtertrigger', this);
    }
});

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterRegistry
 * @singleton
 */
Tine.widgets.grid.FilterRegistry = function() {
    var filters = {};
    
    return {
        register: function(appName, modelName, filter) {
            var key = appName + '.' + modelName;
            if (! filters[key]) {
                filters[key] = [];
            }
            
            filters[key].push(filter);
        },
        
        get: function(appName, modelName) {
            if (Ext.isFunction(appName.getMeta)) {
                modelName = appName.getMeta('modelName');
                appName = appName.getMeta('appName');
            }
            
            var key = appName + '.' + modelName;
            
            return filters[key] || [];
        }
    };
}();
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.widgets.grid');

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterPlugin
 * @extends     Ext.util.Observable
 * <p>Base class for all grid filter plugins.</p>
 * @constructor
 */
Tine.widgets.grid.FilterPlugin = function(config) {
    config = config || {};
    Ext.apply(this, config);
    
    this.addEvents(
        /**
         * @event change
         * Fired when the filter changed.
         * @param {Tine.widgets.grid.FilterPlugin} this
         */
        'change'
    );
    
    Tine.widgets.grid.FilterPlugin.superclass.constructor.call(this);
};

Ext.extend(Tine.widgets.grid.FilterPlugin, Ext.util.Observable, {
    /**
     * @property {Ext.grid.GridPanel} grid
     */
    grid: null,
    
    /**
     * @property {Ext.data.Store} store
     */
    store: null,
    
    /**
     * @property {String} xtype
     */
    xtype: 'filterplugin',
    
    /**
     * main method which must return the filter object of this filter
     * 
     * @return {Object}
     */
    getValue: Ext.emptyFn,
    
    /**
     * returns gird panel this filterplugin is bound to/for
     * 
     * @return {Ext.Panel}
     */
    getGridPanel: function() {
        return this.grid;
    },
    
    /**
     * main method which must set the filter from given data
     * 
     * @param {Array} all filters
     */
    setValue: Ext.emptyFn,
    
    /**
     * plugin method of Ext.grid.GridPanel
     * 
     * @param {Ext.grid.GridPanel} grid
     */
    init: function(grid) {
        this.grid = grid;
        this.store = grid.store;
        this.doBind();
    },
    
    /**
     * binds this plugin to the grid store
     */
    doBind: function() {
        this.store.on('beforeload', this.onBeforeLoad, this);
        this.store.on('load', this.onLoad, this);
    },
    
    /**
     * fires our change event
     */
    onFilterChange: function() {
        
        if (this.getGridPanel() && typeof this.getGridPanel().getView === 'function') {
            this.getGridPanel().getView().isPagingRefresh = true;
        }
        
        if (this.store) {
            this.store.load({});
        }
        
        this.fireEvent('change', this);
    },
    
    /**
     * called before store loads
     */
    onBeforeLoad: function(store, options) {
        options = options || {};
        options.params = options.params || {};
        options.params.filter = options.params.filter ? options.params.filter : [];
        
        var value = this.getValue();
        if (value && Ext.isArray(options.params.filter)) {
            value = Ext.isArray(value) ? value : [value];
            for (var i=0; i<value.length; i++) {
                options.params.filter.push(value[i]);
            }
        }
    },
    
    /**
     * called after store data loaded
     */
    onLoad: function(store, options) {
        if (Ext.isArray(store.proxy.jsonReader.jsonData.filter)) {
            
            // filter plugin has to 'pick' its records
            this.setValue(store.proxy.jsonReader.jsonData.filter);
        }
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.widgets.grid');

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterButton
 * @extends     Ext.Button
 * <p>Toggle Button to be used as filter</p>
 * @constructor
 */
Tine.widgets.grid.FilterButton = function(config) {
    config = config || {};
    Ext.apply(this, config);
    
    Tine.widgets.grid.FilterButton.superclass.constructor.call(this);
    Ext.applyIf(this, new Tine.widgets.grid.FilterPlugin());
};

Ext.extend(Tine.widgets.grid.FilterButton, Ext.Button, {
    /**
     * @cfg {String} field the filed to filter
     */
    field: null,
    /**
     * @cfg {String} operator operator of filter (defaults 'equals')
     */
    operator: 'equals',
    /**
     * @cfg {Bool} invert true if loging should be inverted (defaults false)
     */
    invert: false,
    
    /**
     * @private only toggle actions make sense as filters!
     */
    enableToggle: true,
    
    /**
     * @private
     */
    getValue: function() {
        return {field: this.field, operator: this.operator, value: this.invert ? !this.pressed : this.pressed};
    },
    
    /**
     * @private
     */
    setValue: function(filters) {
        for (var i=0; i<filters.length; i++) {
            if (filters[i].field == this.field) {
                this.toggle(this.invert ? !filters[i].value : !!filters[i].value);
                break;
            }
        }
    },
    
    /**
     * @private
     */
    handler: function() {
        this.onFilterChange();
    }
});

Ext.reg('wdgt.filterbutton', Tine.widgets.grid.FilterButton);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.widgets.grid');

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.ExportButton
 * @extends     Ext.Button
 * <p>export button</p>
 * @constructor
 */
Tine.widgets.grid.ExportButton = function(config) {
    config = config || {};
    Ext.apply(this, config);
    config.handler = this.doExport.createDelegate(this);
    
    Tine.widgets.grid.ExportButton.superclass.constructor.call(this, config);
};

Ext.extend(Tine.widgets.grid.ExportButton, Ext.Action, {
    /**
     * @cfg {String} icon class
     */
    iconCls: 'action_export',
    /**
     * @cfg {String} format of export (default: csv)
     */
    format: 'csv',
    /**
     * @cfg {String} export function (for example: Timetracker.exportTimesheets)
     */
    exportFunction: null,
    /**
     * @cfg {Tine.widgets.grid.FilterSelectionModel} sm
     */
    sm: null,
    /**
     * @cfg {Tine.widgets.grid.GridPanel} gridPanel
     * use this alternativly to sm
     */
    gridPanel: null,
    /**
     * @cfg {Boolean} showExportDialog
     */
    showExportDialog: false,
    
    /**
     * do export
     */
    doExport: function() {
        // get selection model
        if (!this.sm) {
            this.sm = this.gridPanel.grid.getSelectionModel();
        }
        
        // return if no rows are selected
        if (this.sm.getCount() === 0) {
            return false;
        }
        
        var filterSettings = this.sm.getSelectionFilter();
        
        if (this.showExportDialog) {
            var gridRecordClass = this.gridPanel.recordClass,
                model = gridRecordClass.getMeta('appName') + '_Model_' + gridRecordClass.getMeta('modelName');
                
            Tine.widgets.dialog.ExportDialog.openWindow({
                appName: this.gridPanel.app.appName,
                record: new Tine.Tinebase.Model.ExportJob({
                    filter: filterSettings,
                    format: this.format,
                    exportFunction: this.exportFunction,
                    count: this.sm.getCount(),
                    recordsName: this.gridPanel.i18nRecordsName,
                    model: model
                })
            });
        } else {
            this.startDownload(filterSettings);
        }
    },
    
    /**
     * start download
     * 
     * @param {Object} filterSettings
     */
    startDownload: function(filterSettings) {
        var downloader = new Ext.ux.file.Download({
            params: {
                method: this.exportFunction,
                requestType: 'HTTP',
                filter: Ext.util.JSON.encode(filterSettings),
                options: Ext.util.JSON.encode({
                    format: this.format
                })
            }
        }).start();
    }
});

/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterToolbar
 * @extends     Ext.Panel
 * 
 * <br>Usage:<br>
     <pre><code>
     tb = new Tine.widgets.grid.FilterToolbar({
         filterModels: [
            {label: 'Full Name', field: 'n_fn', defaultOperator: 'contains'},
            {label: 'Container', field: 'container_id', operatorRenderer: function() {...}, valueRenderer: function() {...}},
            {label: 'Contact', field: 'quicksearch'}
         ],
         defaultFilter: 'quicksearch',
         filters: [
            {field: 'n_fn', operator: 'contains', value: 'Smith'},
            {field: 'container_id', operator: 'equals', value: 4}
        ]
     });
    </code></pre>
 * @constructor
 * @param {Object} config
 */
Tine.widgets.grid.FilterToolbar = function(config) {
    Ext.apply(this, config);
    Tine.widgets.grid.FilterToolbar.superclass.constructor.call(this);
    
    // become filterPlugin
    Ext.applyIf(this, new Tine.widgets.grid.FilterPlugin());
    
    this.childSheets = {};
};

/**
 * Filter registry
 * @type Object
 */
Tine.widgets.grid.FilterToolbar.FILTERS = {};

Ext.extend(Tine.widgets.grid.FilterToolbar, Ext.Panel, {
    
    /**
     * @cfg {Array} array of filter models (possible filters in this toolbar)
     */
    filterModels: null,
    
    /**
     * @cfg {String} fieldname of default filter
     */
    defaultFilter: null,
    
    /**
     * @cfg {Tine.Tinebase.data.Record} model this filter is for
     */
    recordClass: null,
    
    /**
     * @cfg {Bool} allowSaving (defaults to false)
     */
    allowSaving: false,
    
    /**
     * @cfg {Bool} neverAllowSaving (defaults to false)
     */
    neverAllowSaving: false,

    /**
     * @cfg {Bool} showSearchButton (defaults to true)
     */
    showSearchButton: true,
    
    filterFieldWidth: 240,
    filterValueWidth: 200,
    
    /**
     * @cfg {String} row prefix (defaults to _('Show'))
     */
    rowPrefix: null,
    
    /**
     * @property childSheets
     * @type Object
     */
    childSheets: null,
    
    /**
     * @property isActive
     * @type Boolean
     */
    isActive: true,
    
    header: false,
    border: false,
    monitorResize: true,
    region: 'north',
    layout: 'fit',
    //split: true,
    
    record: Ext.data.Record.create([
        {name: 'field'},
        {name: 'operator'},
        {name: 'value'}
    ]),
    
    frowIdPrefix: 'tw-ftb-frowid-',
    
    /**
     * @private
     */
    initTemplates : function() {
        var ts = this.templates || {};
        if(!ts.master) {
            ts.master = new Ext.Template(
                '<div class="tw-filtertoolbar x-toolbar x-small-editor" hidefocus="true">',
                    '<table style="width: auto;" border="0" cellpadding="0" cellspacing="0">',
                         '{tbody}', 
                     '</table>',
                '</div>'
            );
        }
        if(!ts.filterrow){
            ts.filterrow = new Ext.Template(
                '<tr id="{id}" class="fw-ftb-frow">',
                    '<td class="tw-ftb-frow-pbutton"></td>',
                    '<td class="tw-ftb-frow-mbutton"></td>',
                    '<td class="tw-ftb-frow-prefix">{prefix}</td>',
                    '<td class="tw-ftb-frow-field" width="' + this.filterFieldWidth + 'px">{field}</td>',
                    '<td class="tw-ftb-frow-operator" width="90px" >{operator}</td>',
                    '<td class="tw-ftb-frow-value" width="' + this.filterValueWidth + 'px">{value}</td>',
                    '<td class="tw-ftb-frow-searchbutton"></td>',
                    //'<td class="tw-ftb-frow-deleteallfilters"></td>',
                    //'<td class="tw-ftb-frow-savefilterbutton"></td>',
                '</tr>'
            );
        }
        
        for(var k in ts){
            var t = ts[k];
            if(t && typeof t.compile == 'function' && !t.compiled){
                t.disableFormats = true;
                t.compile();
            }
        }

        this.templates = ts;
    },
    
    /**
     * @private
     */
    initActions: function() {
        this.actions = {
            addFilterRow: new Ext.Button({
                //disabled: true,
                tooltip: _('add new filter'),
                iconCls: 'action_addFilter',
                scope: this,
                handler: this.addFilter
            }),
            removeAllFilters: new Ext.Button({
                tooltip: _('reset all filters'),
                iconCls: 'action_delAllFilter',
                scope: this,
                handler: this.deleteAllFilters
            }),
            startSearch: new Ext.Button({
                text: _('start search'),
                iconCls: 'action_startFilter',
                scope: this,
                handler: function() {
                    this.onFiltertrigger();
                }
            }),
            saveFilter: new Ext.Button({
                tooltip: _('save as favorite'),
                iconCls: 'action_saveFilter',
                handler: this.onSaveFilter.createDelegate(this)
            })
        };

        this.action_loadFilter = new Ext.Action({
            text: _('Load a favorite'),
            hidden: true,
            iconCls: 'action-tinebase-favorite',
            scope: this,
            handler: this.onLoadFilter
        });
    },
    
    /**
     * @private
     */
    onRender: function(ct, position) {
        Tine.widgets.grid.FilterToolbar.superclass.onRender.call(this, ct, position);
        
        // only get app and enable saving if this.store is available (that is not the case in the activities panel)
        // at this point the plugins are initialised
        if (! this.app && this.store) {
            this.app = Tine.Tinebase.appMgr.get(this.store.proxy.recordClass.getMeta('appName'));
        }
        // automatically enable saving
        if (! this.neverAllowSaving && this.app && this.app.getMainScreen() && typeof this.app.getMainScreen().getWestPanel == 'function' && this.app.getMainScreen().getWestPanel().hasFavoritesPanel) {
            this.allowSaving = true;
        }
        
        // render static table
        this.renderTable();
        
        
        // render each filter row into table
        this.filterStore.each(function(filter) {
            this.renderFilterRow(filter);
        }, this);
        
        // render static action buttons
        for (action in this.actions) {
            this.actions[action].hidden = true;
            this.actions[action].render(this.bwrap);
        }
        
        // wrap search button an set it always mouse-overed
        this.searchButtonWrap = this.actions.startSearch.getEl().wrap();
        this.searchButtonWrap.addClass('x-btn-over');
        
        // arrange static action buttons
        this.onFilterRowsChange();
        
        if (! this.parentSheet) {
            this.tbar = new Ext.Toolbar({
                items: [],
                hidden: true
            });
            this.tbar.render(this.el, 0);
            
//            // tab bar to start or filters
//            this.tabBar = new Ext.Panel({
//                cls: 'tw-ftb-criteria-tabs',
//                border: false,
//                height: Ext.isGecko ? 22 : 20,
//                items: new Ext.TabPanel({
//                    border: false,
//                    plain: true,
//                    activeItem: 1,
//                    items: [{title: 'combination', html:''}, {title: 'criteria 1', html:''}, {title: 'criteria 2', html: ''}, {iconCls: 'action_add', title: '&nbsp;', html: ''}]
//                })
//            });
//            this.tabBar.render(this.el, 0);
        }
        
        this.breadCrumb = new Ext.Action({
            text: (this.parentSheet ? '&gt;&nbsp;&nbsp;&nbsp;' : '') + this.title,
            scope: this,
            handler: function() {
                this.setActiveSheet(this);
            }
        });
    },
    
    generateTitle: function() {
        var title = this.id;
        
        if (this.recordClass) {
            var app = Tine.Tinebase.appMgr.get(this.recordClass.getMeta('appName')),
                recordName = this.recordClass.getMeta('recordName'),
                recordsName = this.recordClass.getMeta('recordsName');
                
            title = app.i18n.n_hidden(recordName, recordsName, 50);
        }
        
        return title;
    },
    
    /**
     * is persiting this filterPanel is allowed
     * 
     * @return {Boolean}
     */
    isSaveAllowed: function() {
        return this.allowSaving;
    },
    
    /**
     * called when the title of this panel changes
     * 
     * @param {Ext.Panel} panel
     * @param {String} title
     */
    onTitleChange: function(panel, title) {
        if (this.breadCrumb) {
            this.breadCrumb.setText((this.parentSheet ? '&gt;&nbsp;&nbsp;&nbsp;' : '') + title);
        }
    },
    
    /**
     * save filterset as persisten filter
     */
    onSaveFilter: function() {
        this.app.getMainScreen().getWestPanel().getFavoritesPanel().saveFilter();
    },
    
    onLoadFilter: function() {
        Ext.Msg.alert('sorry', 'not yet implemented');
    },
    
    /**
     * renders static table
     * @private
     */
    renderTable: function() {
        var ts = this.templates;
        var tbody = '';
        
        this.filterStore.each(function(filter){
            tbody += ts.filterrow.apply({
                id: this.frowIdPrefix + filter.id
            });
        }, this);
        
        this.tableEl = ts.master.overwrite(this.bwrap, {tbody: tbody}, true);
    },
    
    /**
     * renders the filter specific stuff of a single filter row
     * 
     * @param {Ext.data.Record} el representing a filter tr tag
     * @private
     */
    renderFilterRow: function(filter) {
        filter.formFields = {};
        var filterModel = this.getFilterModel(filter);
        
        if (! filterModel) {
            Tine.log.warn('Tine.widgets.grid.FilterToolbar::renderFilterRow no filterModel found');
            Tine.log.warn(filter);
            this.filterStore.remove(filter);
            return;
        }
        var fRow = this.bwrap.child('tr[id='+ this.frowIdPrefix + filter.id + ']');
        
        // field
        filter.formFields.field = new Ext.form.ComboBox({
            filter: filter,
            width: this.filterFieldWidth,
            minListWidth: 240, // will be ignored if width is heigher
            resizable: true,
            id: 'tw-ftb-frow-fieldcombo-' + filter.id,
            mode: 'local',
            lazyInit: false,
            emptyText: _('select a field'),
            forceSelection: true,
            typeAhead: true,
            triggerAction: 'all',
            store: this.fieldStore,
            displayField: 'label',
            valueField: 'field',
            value: filterModel.field,
            renderTo: fRow.child('td[class=tw-ftb-frow-field]'),
            validator: this.validateFilter.createDelegate(this),
            tpl: '<tpl for="."><div class="x-combo-list-item tw-ftb-field-{field}">{label}</div></tpl>'
        });
        filter.formFields.field.setValue = filter.formFields.field.setValue.createInterceptor(function() {
            // NOTE: as this.fieldStore is a shared store we need to clear the filter before we use it,
            //       otherwise the record (e.g. query) might not be in the store
            this.fieldStore.clearFilter();
        }, this);
        
        filter.formFields.field.on('select', function(combo, newRecord, newKey) {
            if (combo.value != combo.filter.get('field')) {
                this.onFieldChange(combo.filter, combo.value);
            }
        }, this);
        
        filter.formFields.field.on('blur', function(combo) {
            if (combo.value != combo.filter.get('field')) {
                this.onFieldChange(combo.filter, combo.value);
            }
        }, this);
        
        // operator
        filter.formFields.operator = filterModel.operatorRenderer(filter, fRow.child('td[class^=tw-ftb-frow-operator]'));
        
        // value
        filter.formFields.value = filterModel.valueRenderer(filter, fRow.child('td[class^=tw-ftb-frow-value]'));
        
        filter.deleteRowButton = new Ext.Button({
            id: 'tw-ftb-frow-deletebutton-' + filter.id,
            tooltip: _('Delete this filter'),
            filter: filter,
            iconCls: 'action_delThisFilter',
            renderTo: fRow.child('td[class=tw-ftb-frow-mbutton]'),
            scope: this,
            handler: function(button) {
                this.deleteFilter(button.filter);
            }
        });
    },
    
    /**
     * validate if type ahead is in our filter store
     * @return {Bool}
     */
    validateFilter: function(value) {
        return this.fieldStore.query('label', value).getCount() != 0;
    },
    
    /**
     * @private
     */
    arrangeButtons: function() {

        var numFilters = this.filterStore.getCount();
        var firstId = this.filterStore.getAt(0).id;
        var lastId = this.filterStore.getAt(numFilters-1).id;
        
        this.filterStore.each(function(filter){
            var tr = this.bwrap.child('tr[id='+ this.frowIdPrefix + filter.id + ']');
            
            // prefix
            tr.child('td[class=tw-ftb-frow-prefix]').dom.innerHTML = _('and');
            //filter.deleteRowButton.setVisible(filter.id != lastId);
                
            if (filter.id == lastId) {
                // move add filter button
                tr.child('td[class=tw-ftb-frow-pbutton]').insertFirst(this.actions.addFilterRow.getEl());
                this.actions.addFilterRow.show();
                // move start search button
                tr.child('td[class=tw-ftb-frow-searchbutton]').insertFirst(this.searchButtonWrap);
                if (this.showSearchButton) {
                    this.actions.startSearch.show();
                }
                // move delete all filters
                // tr.child('td[class=tw-ftb-frow-deleteallfilters]').insertFirst(this.actions.removeAllFilters.getEl());
                this.actions.removeAllFilters.setVisible(numFilters > 1);
                // move save filter button
                // tr.child('td[class=tw-ftb-frow-savefilterbutton]').insertFirst(this.actions.saveFilter.getEl());
                this.actions.saveFilter.setVisible(this.allowSaving && numFilters > 1);
            }
            
            if (filter.id == firstId) {
                tr.child('td[class=tw-ftb-frow-prefix]').dom.innerHTML = this.rowPrefix;
                
                // hack for the save/delete all btns which are now in the first row
                //if (Ext.isSafari) {
                    this.actions.removeAllFilters.getEl().applyStyles('float: left');
                //} else {
                //    this.actions.saveFilter.getEl().applyStyles('display: inline');
                //    this.actions.removeAllFilters.getEl().applyStyles('display: inline');
                //}
                
                tr.child('td[class=tw-ftb-frow-searchbutton]').insertFirst(this.actions.saveFilter.getEl());
                tr.child('td[class=tw-ftb-frow-searchbutton]').insertFirst(this.actions.removeAllFilters.getEl());
                
                //tr.child('td[class=tw-ftb-frow-pmbutton]').insertFirst(this.actions.removeAllFilters.getEl());
                //this.actions.removeAllFilters.setVisible(numFilters > 1);
            }
        }, this);
    },
    
    doLayout: function() {
        if (typeof this.layout.layout == 'function') {
            Tine.widgets.grid.FilterToolbar.superclass.doLayout.apply(this, arguments);
        }
        
        if (this.rendered) {
            this.arrangeButtons();
            
            this.filterStore.each(function(filter){
                for (var formItemName in filter.formFields) {
                    if (filter.formFields[formItemName] && typeof filter.formFields[formItemName].syncSize == 'function') {
                        filter.formFields[formItemName].setWidth(filter.formFields[formItemName].width);
                        if (filter.formFields[formItemName].wrap) {
                            filter.formFields[formItemName].wrap.setWidth(filter.formFields[formItemName].width);
                        }
                        filter.formFields[formItemName].syncSize();
                    }
                }
            }, this);
        }
    },
    
    /**
     * called  when a filter action is to be triggered (start new search)
     * @private
     */
    onFiltertrigger: function() {
        if (! this.supressEvents) {
            this.onFilterChange();
        }
    },
    
    /**
     * called on field change of a filter row
     * @private
     */
    onFieldChange: function(filter, newField) {
        var oldFilterModel = this.getFilterModel(filter),
            oldOperator = filter.formFields.operator.getValue(),
            oldValue    = filter.formFields.value.getValue();
        
        // only use old operator/value for textfields
        var f = filter.formFields.value;
        if (typeof f.selectText != 'function' || typeof f.doQuery == 'function') {
            oldValue = '';
        }
        
        if (oldFilterModel && Ext.isFunction(oldFilterModel.onDestroy)) {
            oldFilterModel.onDestroy(filter);
        }
        
        // NOTE: removeMode got introduced on ext3.1 but is not docuemented
        //       'childonly' is no ext mode, we just need something other than 'container'
        filter.formFields.operator.removeMode = 'childsonly';
        filter.formFields.value.removeMode = 'childsonly';
        
        filter.formFields.operator.destroy();
        delete filter.formFields.operator;
        filter.formFields.value.destroy();
        delete filter.formFields.value;
        
        var filterModel = this.getFilterModel(newField);
        var fRow = this.bwrap.child('tr[id='+ this.frowIdPrefix + filter.id + ']');
        
        var opEl = fRow.child('td[class^=tw-ftb-frow-operator]');
        var valEl = fRow.child('td[class^=tw-ftb-frow-value]');
        
        filter.set('field', newField);
        filter.set('operator', '');
        filter.set('value', '');
        
        filter.formFields.operator = filterModel.operatorRenderer(filter, opEl);
        filter.formFields.value = filterModel.valueRenderer(filter, valEl);
        
        // only use old operator/value for textfields
        var f = filter.formFields.value;
        if (oldValue && typeof f.selectText == 'function' && typeof f.doQuery != 'function') {
            var o = filter.formFields.operator;
            
            if (typeof o.findRecord == 'function') {
                if (typeof o.setValue == 'function' && o.findRecord(o.valueField, oldOperator)) {
                    o.setValue(oldOperator);
                }
                filter.formFields.value.setValue(oldValue);
                filter.formFields.value.selectText.defer(50, filter.formFields.value);
            }
        }
    },
    
    /**
     * @private
     */
    initComponent: function() {
        this.title = this.generateTitle();
        this.on('titlechange', this.onTitleChange, this);
        
        Tine.widgets.grid.FilterToolbar.superclass.initComponent.call(this);
        
        this.on('show', function() {
            this.doLayout();
        }, this);
        
        if (this.rowPrefix === null) {
            this.rowPrefix = _('Show');
        }
        
        this.initTemplates();
        this.initActions();
        
        // init filters
        this.filters = Ext.isArray(this.filters) ? this.filters : [];
        if (this.filters.length < 1) {
            this.filters = [{field: this.defaultFilter}];
        }
        this.filterStore = new Ext.data.JsonStore({
            fields: this.record,
            data: this.filters
        });

        // init filter models
        this.filterModelMap = {};
        var filtersFields = [];
        
        if (this.recordClass && !this.filterModels) {
            this.filterModels = this.recordClass.getFilterModel();
        }
        
        
        if (this.recordClass) {
            // concat registered filters
            this.filterModels = this.filterModels.concat(Tine.widgets.grid.FilterRegistry.get(this.recordClass));
            
            // auto add foreign record filter on demand
            var foreignRecordFilter = this.createFilterModel({filtertype: 'foreignrecord', ownRecordClass: this.recordClass, isGeneric: true});
            if (foreignRecordFilter.operatorStore.getCount() > 0) {
                this.filterModels.push(foreignRecordFilter);
            }
            
            // auto add self id filter
            this.ownRecordFilterModel = this.createFilterModel({filtertype: 'ownrecord', ownRecordClass: this.recordClass});
            this.filterModels.push(this.ownRecordFilterModel);
//            filtersFields.push(this.ownRecordFilterModel);
        }
        
        
        for (var i=0; i<this.filterModels.length; i++) {
            var config = this.filterModels[i],
                fm = this.createFilterModel(config);
            
            this.filterModelMap[fm.field] = fm;
            filtersFields.push(fm);
            
            fm.on('filtertrigger', this.onFiltertrigger, this);
            
            // insert subfilters
            if (Ext.isFunction(fm.getSubFilters)) {
                Ext.each(fm.getSubFilters(), function(sfm) {
                    sfm.superFilter = fm;
                    
                    // :<field> indicates a left hand filter
                    sfm.field = ':' + sfm.field;
                    
                    this.filterModelMap[sfm.field] = sfm;
                    filtersFields.push(sfm);
                    sfm.on('filtertrigger', this.onFiltertrigger, this);
                }, this);
            }
        }
        
//        // auto add self id filter
//        if (this.recordClass) {
//            this.ownRecordFilterModel = this.createFilterModel({filtertype: 'ownrecord', ownRecordClass: this.recordClass});
//            filtersFields.push(this.ownRecordFilterModel);
//        }
            
        // init filter selection
        this.fieldStore = new Ext.data.JsonStore({
            fields: ['field', 'label'],
            data: filtersFields,
            remoteSort: false,
            sortInfo: {
                field: 'label',
                direction: 'ASC'
            }
        });
    },
    
    /**
     * called when a filter row gets added/deleted
     * @private
     */
    onFilterRowsChange: function() {
        if (! this.supressEvents) {
            this.ownerCt.layout.layout();
        }
        this.doLayout();
    },
    
    createFilterModel: function(config) {
        if (config && config.isFilterModel) {
            return config;
        }
        
        // push this filtertoolbar in config
        config.ftb = this;
        
        if (config.filtertype) {
            // filter from reg
            return new Tine.widgets.grid.FilterToolbar.FILTERS[config.filtertype](config);
        } else {
            return new Tine.widgets.grid.FilterModel(config);
        }
    },
    
    /**
     * returns filterModel
     * 
     * @param {String} fieldName
     * @return {Tine.widgets.grid.FilterModel}
     */
    getFilterModel: function(field) {
        var fieldName = Ext.isFunction(field.get) ? field.get('field') : field;
  
        if (! fieldName && field.data && field.data.condition) {
            return this.ownRecordFilterModel;
        }
        
        return this.filterModelMap[fieldName];
    },
    
    /**
     * adds a new filer row
     */
    addFilter: function(filter) {
        if (! filter || arguments[1]) {
            filter = new this.record({
                field: this.defaultFilter
            });
        }
        this.filterStore.add(filter);

        var fRow = this.templates.filterrow.insertAfter(this.bwrap.child('tr[class=fw-ftb-frow]:last'),{
            id: 'tw-ftb-frowid-' + filter.id
        }, true);
        
        this.renderFilterRow(filter);
        
        this.onFilterRowsChange();
        
        /*
        if (!this.supressEvents) {
            this.onFiltertrigger();
        }
        */
        return filter;
    },
    
    /**
     * resets a filter
     * @param {Ext.Record} filter to reset
     */
    resetFilter: function(filter) {
        
    },
    
    /**
     * deletes a filter
     * @param {Ext.Record} filter to delete
     */
    deleteFilter: function(filter) {
        var fRow = this.bwrap.child('tr[id=tw-ftb-frowid-' + filter.id + ']');
        //var isLast = this.filterStore.getAt(this.filterStore.getCount()-1).id == filter.id;
        var isLast = this.filterStore.getCount() == 1,
            filterModel = this.getFilterModel(filter);
            
        this.filterStore.remove(this.filterStore.getById(filter.id));
        filter.formFields.field.destroy();
        filter.formFields.operator.destroy();
        filter.formFields.value.destroy();
        
        if (filterModel && Ext.isFunction(filterModel.onDestroy)) {
            filterModel.onDestroy(filter);
        }
        
        if (isLast) {
            // add a new first row
            var firstFilter = this.addFilter();
            
            // save buttons somewhere
            for (action in this.actions) {
                this.actions[action].hide();
                this.bwrap.insertFirst(action == 'startSearch' ? this.searchButtonWrap : this.actions[action].getEl());
            }
        }
        fRow.remove();
        
        this.onFilterRowsChange();
        
        if (!this.supressEvents) {
            this.onFiltertrigger();
        }
    },
    
    /**
     * deletes all filters
     */
    deleteAllFilters: function() {
        this.supressEvents = true;
        
        this.filterStore.each(function(filter) {
            this.deleteFilter(filter);
        },this);
        
        this.supressEvents = false;
        this.onFiltertrigger();
        this.onFilterRowsChange();
    },
    
    getValue: function() {
        var filters = [];
        
        this.filterStore.each(function(filter) {
            var filterModel = this.getFilterModel(filter),
                line = Ext.isFunction(filterModel.getFilterData) ? filterModel.getFilterData(filter) : this.getFilterData(filter);
            
            if (line.field && Ext.isString(line.field) &&  line.field.match(/:/)) {
                var parts = line.field.split(':');
                
                // customfield handling
                if (parts[0] == 'customfield') {
                    filters.push({field: 'customfield', operator: line.operator, value: {cfId: parts[1], value: line.value}});
                }
                
                else if (filterModel && filterModel.superFilter) {
                    // if we are a childfilter of a recordclass of the subfilter, just place the subfilter
                    if (this.parentSheet && this.parentSheet.recordClass == filterModel.superFilter.foreignRecordClass) {
                        filters.push(line);
                        return;
                    }
                    
                    // check if filter of type superfilter is present
                    if (this.filterStore.find('field', filterModel.superFilter.field) < 0) {
                        
                        // create one
                        // @TODO HIDE? -> see ForeignRecordFilter line ~300
                        var superFilter = this.addFilter(new this.record({field: filterModel.superFilter.field, operator: 'definedBy'}));
                        line = this.getFilterData(superFilter);
                        filters.push(line);
                    }
                }
            } else {
                filters.push(line);
            }
        }, this);
        
        return filters;
    },
    
    /**
     * @static
     * @param {filterRecord} filter
     */
    getFilterData: function(filter) {
        var line = {};
                
        for (var formfield in filter.formFields) {
            line[formfield] = filter.formFields[formfield].getValue();
        }
        
        // fill data with filter record data in case form field not exist (not rendered)
        filter.fields.each(function(field)  {
            var name = field.name;
            if (! line.hasOwnProperty(name)) {
                line[name] = filter.get(name);
            }
        }, this);
        
        // append id
        line.id = filter.id;

        return line;
    },
    
    /**
     * set filterData in a filter
     * 
     * @static
     * @param {filterRecord} filter
     * @param {Object} filterData
     * @return {filterRecord}
     */
    setFilterData: function(filter, filterData) {
        filter.beginEdit();
        
        Ext.each(['field', 'operator', 'value'], function(t) {
            filter.set(t, filterData[t]);
            if (Ext.isFunction(filter.formFields[t].setValue)) {
                filter.formFields[t].setValue(filterData[t]);
            }
        }, this);
        
        filter.endEdit();
        
        return filter;
    },
    
    setValue: function(filters) {
        this.supressEvents = true;
        
        var oldFilters = [];
        this.filterStore.each(function(filterRecord) {oldFilters.push(filterRecord);}, this);
        
        var filterModel, filterData, filterRecord;
        
        // custom fields handling
        for (var i=0; i<filters.length; i++) {
            filterData = filters[i];
            if (filterData.value && filterData.value.cfId) {
                filters[i].field = filterData.field + ':' + filterData.value.cfId;
                filters[i].value = filterData.value.value;
            }
        }
        
        for (var i=0; i<filters.length; i++) {
            filterRecord = null;
            filterData = filters[i];
            filterModel = filterData.condition ? this.ownRecordFilterModel : this.filterModelMap[filterData.field];
            
            if (filterModel) {
                if (filterData.id) {
                    filterRecord = this.filterStore.getById(filterData.id);
                }
                
                // refresh existing filters
                if (filterRecord) {
                    Ext.isFunction(filterModel.setFilterData) ? filterModel.setFilterData(filterRecord, filterData) : this.setFilterData(filterRecord, filterData);
                    oldFilters.remove(filterRecord);
                } 
                
                // add non existing filters only if they where not created implicitly by server
                else if (! filterData.implicit) {
                    filterRecord = new this.record(filterData, filterData.id);
                    this.addFilter(filterRecord);
                }
            }
            
        }
        
        // remove unused filters
        Ext.each(oldFilters, function(filterRecord) {
            this.deleteFilter(filterRecord);
        }, this);
        
        this.supressEvents = false;
        this.onFilterRowsChange();
    },
    
    /**
     * gets filter data of all filter plugins
     * 
     * NOTE: As we can't find all filter plugins directly we need a litte hack 
     *       to get their data
     *       
     *       We register ourselve as latest beforeload.
     *       In the options.filter we have the filters then.
     */
    getAllFilterData: function() {
        this.store.on('beforeload', this.storeOnBeforeload, this);
        this.store.load();
        this.store.un('beforeload', this.storeOnBeforeload, this);
        
        return this.allFilterData;
    },
    
    storeOnBeforeload: function(store, options) {
        this.allFilterData = options.params.filter;
        this.store.fireEvent('exception');
        return false;
    },
    
    onDestroy: function() {
        if (this.parentSheet) {
            this.parentSheet.removeFilterSheet(this);
        }
    },
    
    /***** sheets functions *****/
    addFilterSheet: function(ftb) {
        ftb.ownerCt = this.ownerCt;
        ftb.parentSheet = this;
        this.childSheets[ftb.id] = ftb;
        
        ftb.onFilterChange = this.onFilterChange.createDelegate(this);
    },
    
    removeFilterSheet: function(ftb) {
        if (! this.childSheets[ftb.id].destroying) {
            this.childSheets[ftb.id].destroy();
        }
        delete this.childSheets[ftb.id];
        
        if (ftb == this.activeSheet) {
            this.setActiveSheet.defer(100, this, [this]);
        }
    },
    
    setActiveSheet: function(sheet) {
        var ftb = Ext.isString(sheet) ? (sheet = this.id ? this : this.childSheets[sheet]) : sheet,
            rootSheet = this.getRootSheet(),
            parentSheets = sheet.getParentSheets();
        
        if (! ftb.rendered) {
            ftb.render(this.el);
        }
        
        this.bwrap[ftb == this ? 'show' : 'hide']();
        
        for( var ftbId in this.childSheets) {
            if (this.childSheets.hasOwnProperty(ftbId)) {
                if (ftb == this.childSheets[ftbId]) {
                    this.childSheets[ftbId].show();
                    this.childSheets[ftbId].setActiveSheet(sheet);
                } else {
                    this.childSheets[ftbId].hide();
                }
            }
        }
        
        // show toolbar on demand
        if (ftb != rootSheet) {
            rootSheet.tbar.removeAll();
            Ext.each(parentSheets, function(sheet) {
                sheet.breadCrumb.enable();
                rootSheet.tbar.add(sheet.breadCrumb);
            }, this);
            ftb.breadCrumb.disable();
            rootSheet.tbar.add(ftb.breadCrumb, '->', ftb.action_loadFilter);
            rootSheet.tbar.show();
            rootSheet.tbar.doLayout();
        } else {
            rootSheet.tbar.hide();
        }
        
        this.activeSheet = ftb;
        this.onFilterRowsChange();
    },
    
    getParentSheets: function(parents) {
        parents = parents || [];
        
        if (this.parentSheet) {
            parents.unshift(this.parentSheet);
            return this.parentSheet.getParentSheets(parents);
        }
        
        return parents;
    },
    
    getRootSheet: function() {
        return this.parentSheet ? this.parentSheet.getRootSheet() : this;
    }
});

Ext.reg('tinewidgetsfiltertoolbar', Tine.widgets.grid.FilterToolbar);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

Tine.widgets.grid.FilterStructureTreePanel = Ext.extend(Ext.tree.TreePanel, {
    
    /**
     * @cfg {Tine.widgets.grid.FilterPanel} filterPanel
     */
    filterPanel: null,
    
    cls: 'tw-ftb-filterstructure-treepanel',
    autoScroll: true,
    border: false,
    useArrows: true,
    collapsible: true,
    collapsed: true,
    baseCls: 'ux-arrowcollapse',
    animCollapse: true,
    titleCollapse:true,
    rootVisible: false,
    
//    draggable : true,
                        
//    rootVisible: false,
    
    initComponent: function() {
        this.title = [
            '<span ext:qtip="',
            Ext.util.Format.htmlEncode(_('Show records that match to one of the following filters')),
            '">',
            Ext.util.Format.htmlEncode(_('or alternatively')),
            '</span>'
        ].join('');
        
//        this.tbar = [{
//            text: '+',
//            scope: this,
//            handler: this.onAddFilterPanel
//        }];
        
        this.root = {
            path: '/',
//            iconCls: this.filterPanel.activeFilterPanel.app ? this.filterPanel.activeFilterPanel.app.getIconCls() : '',
            expanded: true,
//            text: _('or alternatively'),
//            qtip: _('Show records that match to one of the following filters'),
            children: [
                this.createNode(this.filterPanel.activeFilterPanel),
                {id: 'addFilterPanel', text: _('Add alternative filter'), iconCls: 'action_add', leaf: true}
            ]
        };
        
        this.contextMenu = new Ext.menu.Menu({
            items: [{
                text: _('Remove Filter'),
                iconCls: 'action_remove',
                scope: this,
                handler: this.onCtxRemove
            }]
        });
        
        this.on('click', this.onClick, this);
        this.on('beforeclick', this.onBeforeClick, this);
        this.on('contextmenu', this.onContextMenu, this);
        this.on('checkchange', this.onCheckChange, this);
        this.on('afterrender', this.onAfterRender, this);
        this.on('insert', this.onNodeInsert, this);
        this.on('textchange', this.onNodeTextChange, this);
        this.on('expandnode', this.filterPanel.manageHeight, this.filterPanel);
        this.on('collapsenode', this.filterPanel.manageHeight, this.filterPanel);
        this.on('collapse', this.filterPanel.manageHeight, this.filterPanel);
        this.on('expand', this.filterPanel.manageHeight, this.filterPanel);
        this.filterPanel.on('filterpaneladded', this.onFilterPanelAdded, this);
        this.filterPanel.on('filterpanelremoved', this.onFilterPanelRemoved, this);
        this.filterPanel.on('filterpanelactivate', this.onFilterPanelActivate, this);
        this.filterPanel.activeFilterPanel.on('titlechange', this.onFilterPanelTitleChange, this);
        
        this.editor = new Ext.tree.TreeEditor(this);
        
        Tine.widgets.grid.FilterStructureTreePanel.superclass.initComponent.call(this);
    },
    
    onContextMenu: function(node, e) {
        if (this.getRootNode().childNodes.length > 2 && node.id != 'addFilterPanel') {
            this.contextMenu.contextNode = node;
            this.contextMenu.showAt(e.getXY());
        }
    },
    
    onCtxRemove: function() {
        if (this.contextMenu.contextNode) {
            this.filterPanel.removeFilterPanel(this.contextMenu.contextNode.id);
        }
    },
    
    onAfterRender: function() {
        this.onFilterPanelActivate(this.filterPanel, this.filterPanel.activeFilterPanel);
//        this.getRootNode().collapse();
    },
    
    /**
     * called when the text of a noded changed
     * 
     * @param {Ext.tree.TreeNode} node
     * @param {String} text
     * @param {String} oldText
     */
    onNodeTextChange: function(node, text, oldText) {
        if (node.attributes && node.attributes.filterPanel) {
            node.attributes.filterPanel.setTitle(text);
        }
    },
    
    /**
     * called when a node got inserted to this tree
     * 
     * @param {Ext.tree.TreePanel} tree
     * @param {Ext.tree.TreeNode} parent
     * @param {Ext.tree.TreeNode} node
     */
    onNodeInsert: function(tree, parent, node) {
        
        var clickEl = Ext.EventObject.getTarget('.x-tree-node-el', 10),
            isUserActionInsert = clickEl && Ext.fly(clickEl, '_treeEvents').getAttribute('tree-node-id', 'ext') == 'addFilterPanel' ? true : false,
            nodeCount = this.getRootNode().childNodes.length;
        
        // expand this panel if more than 2 childnodes (initial + add)
        if (nodeCount > 2 && this.collapsed) {
            this.expand();
        }
            
        // start editor for new node (if initialted by user)
        if (isUserActionInsert) {
            this.editor.triggerEdit.defer(100, this.editor, [node]);
        }
    },
    
    /**
     * called when a filterPanel gets added
     * 
     * @param {Tine.widgets.grid.filterPanel} filterToolbar
     * @param {Tine.widgets.grid.filterToolbar} filterPanel
     */
    onFilterPanelAdded: function(filterToolbar, filterPanel) {
//        this.getRootNode().appendChild(this.createNode(filterPanel));
//        var newNode = this.createNode(filterPanel);
//        newNode
        this.getRootNode().insertBefore(this.createNode(filterPanel), this.getNodeById('addFilterPanel'));
        filterPanel.on('titlechange', this.onFilterPanelTitleChange, this);
    },
    
    /**
     * called when a filterPanel gets removed
     * 
     * @param {Tine.widgets.grid.filterPanel} filterToolbar
     * @param {Tine.widgets.grid.filterToolbar} filterPanel
     */
    onFilterPanelRemoved: function(filterToolbar, filterPanel) {
        var node = this.getNodeById(filterPanel.id);
        delete node.filterPanel;
        if (node) {
            node.remove();
        }
    },
    
    /**
     * called when a filterPanel gets activated
     * 
     * @param {Tine.widgets.grid.filterPanel} filterToolbar
     * @param {Tine.widgets.grid.filterToolbar} filterPanel
     */
    onFilterPanelActivate: function(filterToolbar, filterPanel) {
        var node = this.getNodeById(filterPanel.id);
        if (node) {
            this.suspendEvents();
            node.select();
            this.resumeEvents();
        }
    },
    
    /**
     * called when a filterPanel title changed
     * 
     * @param {Tine.widgets.grid.filterToolbar} filterPanel
     * @param {String} text
     */
    onFilterPanelTitleChange: function(filterPanel, text) {
        var node = this.getNodeById(filterPanel.id);
        if (node) {
            this.suspendEvents();
            node.setText(text);
            this.resumeEvents();
        }
    },
    
    onBeforeClick: function(node) {
        if (node.id == 'addFilterPanel') {
            this.onAddFilterPanel();
            return false;
        }
    },
    
    onAddFilterPanel: function() {
        var filterPanel = this.filterPanel.addFilterPanel();
        this.filterPanel.setActiveFilterPanel(filterPanel);
    },
    
    onCheckChange: function(node, checked) {
        node.attributes.filterPanel.isActive = checked;
    },
    
    onClick: function(node) {
        if (node.attributes && node.attributes.filterPanel) {
            this.filterPanel.setActiveFilterPanel(node.attributes.filterPanel);
        }
    },
    
    createNode: function(filterPanel) {
        // mark filterPanel active
        filterPanel.isActive = true;
        
        return {
            checked: true,
            editable: true,
            id: filterPanel.id,
            filterPanel: filterPanel,
            leaf: true,
            text: filterPanel.title ? filterPanel.title : filterPanel.id,
            iconCls: filterPanel.app ? filterPanel.app.getIconCls() : 'leaf'
        };
    }
});/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

Tine.widgets.grid.FilterPanel = function(config) {
    this.filterToolbarConfig = config;
    
    // @TODO find quickfilter plugin an pick quickFilterField and criteriaIgnores from it
    this.criteriaIgnores = config.criteriaIgnores || [
        {field: 'container_id', operator: 'equals', value: {path: '/'}},
        {field: 'query',        operator: 'contains',    value: ''}
    ];
    
    // the plugins won't work there
    delete this.filterToolbarConfig.plugins;
    
    // apply some filterPanel configs
    Ext.each(['onFilterChange', 'getAllFilterData'], function(p) {
        if (config.hasOwnProperty(p)) {
            this[p] = config[p];
        }
    }, this);
    
    // become filterPlugin
    Ext.applyIf(this, new Tine.widgets.grid.FilterPlugin());
//    this.filterToolbarConfig.filterPluginCmp = this;
    
    this.filterPanels = [];
    
    this.addEvents(
        /**
         * @event filterpaneladded
         * Fires when a filterPanel is added
         * @param {Tine.widgets.grid.FilterPanel} this
         * @param {Tine.widgets.grid.FilterToolbar} the filterPanel added
         */
        'filterpaneladded',
        
        /**
         * @event filterpanelremoved
         * Fires when a filterPanel is removed
         * @param {Tine.widgets.grid.FilterPanel} this
         * @param {Tine.widgets.grid.FilterToolbar} the filterPanel removed
         */
        'filterpanelremoved',
        
        /**
         * @event filterpanelactivate
         * Fires when a filterPanel is activated
         * @param {Tine.widgets.grid.FilterPanel} this
         * @param {Tine.widgets.grid.FilterToolbar} the filterPanel activated
         */
        'filterpanelactivate'
    );
    Tine.widgets.grid.FilterPanel.superclass.constructor.call(this, {});
};

Ext.extend(Tine.widgets.grid.FilterPanel, Ext.Panel, {
    /**
     * @cfg {String} quickFilterField
     * 
     * name of quickfilter filed in filter definitions
     */
    quickFilterField: 'query',
    
    /**
     * @cfg {Array} criterias to ignore
     */
    criteriaIgnores: null,
    
    /**
     * @cfg {String} moreFiltersActiveText
     */
    moreFiltersActiveText: 'Attention: There are more filters active!', //_('Attention: There are more filters active!')
    
    /**
     * @property activeFilterPanel
     * @type Tine.widgets.grid.FilterToolbar
     */
    activeFilterPanel: null,
    
    /**
     * @property filterPanels map filterPanelId => filterPanel
     * @type Object
     */
    filterPanels: null,
    
    /**
     * @property criteriaCount
     * @type Number
     */
    criteriaCount: 0,
    
    cls: 'tw-ftb-filterpanel',
    layout: 'border',
    border: false,

//    height: 100,
    
    initComponent: function() {
        
        var filterPanel = this.addFilterPanel();
        this.activeFilterPanel = filterPanel;
        
        this.initQuickFilterField();
        
        this.items = [{
            region: 'east',
            width: 200,
            border: false,
            layout: 'fit',
            split: true,
            items: [new Tine.widgets.grid.FilterStructureTreePanel({filterPanel: this})]
        }, {
            region: 'center',
            border: false,
            layout: 'card',
            activeItem: 0,
            items: [filterPanel],
            autoScroll: false,
            listeners: {
                scope: this,
                afterlayout: this.manageHeight
            }
        }];
        
        Tine.widgets.grid.FilterPanel.superclass.initComponent.call(this);
    },
    
    /**
     * is persiting this filterPanel is allowed
     * 
     * @return {Boolean}
     */
    isSaveAllowed: function() {
        return this.activeFilterPanel.allowSaving;
    },

    getAllFilterData: Tine.widgets.grid.FilterToolbar.prototype.getAllFilterData,
    storeOnBeforeload: Tine.widgets.grid.FilterToolbar.prototype.storeOnBeforeload,
    
    manageHeight: function() {
        if (this.rendered) {
            var tbHeight = this.activeFilterPanel.getHeight(),
                northHeight = this.layout.north ? this.layout.north.panel.getHeight() : 0,
                eastHeight = this.layout.east && this.layout.east.panel.getEl().child('ul') ? (this.layout.east.panel.getEl().child('ul').getHeight() + 28) : 0,
                height = Math.min(Math.max(eastHeight, tbHeight + northHeight), 120);
            
            this.setHeight(height);
            
            // manage scrolling
            if (this.layout.center && tbHeight > 120) {
                this.layout.center.panel.el.child('div[class^="x-panel-body"]', true).scrollTop = 1000000;
                this.layout.center.panel.el.child('div[class^="x-panel-body"]', false).applyStyles('overflow-y: auto');
            }
            if (this.layout.east && eastHeight > 120) {
                this.layout.east.panel.el.child('div[class^="x-panel-body"]', true).scrollTop = 1000000;
            }
            this.ownerCt.layout.layout();
        }
    },
    
    onAddFilterPanel: function() {
        var filterPanel = this.addFilterPanel();
        this.setActiveFilterPanel(filterPanel);
    },
    
    addFilterPanel: function(config) {
        config = config || {};
//        config.title = config.title ? config.title : String.format(_('Criteria {0}'), ++this.criteriaCount);
        
        var filterPanel = new Tine.widgets.grid.FilterToolbar(Ext.apply({}, this.filterToolbarConfig, config));
        filterPanel.onFilterChange = this.onFilterChange.createDelegate(this);
        
        this.filterPanels[filterPanel.id] = filterPanel;
        this.criteriaCount++;
        
        if (this.criteriaCount > 1 && filterPanel.title == filterPanel.generateTitle()) {
            filterPanel.setTitle(filterPanel.title + ' ' + this.criteriaCount);
        }
        this.fireEvent('filterpaneladded', this, filterPanel);
        return filterPanel;
    },
    
    /**
     * remove filter panel
     * 
     * @param {mixed} filterPanel
     */
    removeFilterPanel: function(filterPanel) {
        filterPanel = Ext.isString(filterPanel) ? this.filterPanels[filterPanel] : filterPanel;
        
        if (! this.filterPanels[filterPanel.id].destroying) {
            this.filterPanels[filterPanel.id].destroy();
        }
        
        delete this.filterPanels[filterPanel.id];
        this.criteriaCount--;
        
        this.fireEvent('filterpanelremoved', this, filterPanel);
    },
    
    setActiveFilterPanel: function(filterPanel) {
        filterPanel = Ext.isString(filterPanel) ? this.filterPanels[filterPanel] : filterPanel;
        this.activeFilterPanel = filterPanel;
        
        this.layout.center.panel.add(filterPanel);
        this.layout.center.panel.layout.setActiveItem(filterPanel.id);
        
        filterPanel.doLayout();
        if (filterPanel.activeSheet) {
         // solve layout problems (#6332)
            filterPanel.setActiveSheet(filterPanel.activeSheet);
        }
        this.manageHeight.defer(100, this);
        
        this.fireEvent('filterpanelactivate', this, filterPanel);
    },
    
    // NOTE: their is no spechial filterPanel, each filterpanel could be closed  at any time
    //       ?? what does this mean for quickfilterplugin???
    //       -> we cant mirror fileds or need to mirror the field from the active tbar
    //       -> mhh, better deactivate as soon as we have more than one tbar
    //       -> don't sync, but fetch with this wrapper!
    initQuickFilterField: function() {
        var stateful = !! this.filterToolbarConfig.recordClass;
        // autogenerate stateId
        if (stateful) {
            var stateId = this.filterToolbarConfig.recordClass.getMeta('appName') + '-' + this.filterToolbarConfig.recordClass.getMeta('recordName') + '-FilterToolbar-QuickfilterPlugin';
        }
        
        this.quickFilter = new Ext.ux.SearchField({
            width: 300,
            enableKeyEvents: true
        });
        
        this.quickFilter.onTrigger1Click = this.quickFilter.onTrigger1Click.createSequence(this.onQuickFilterClear, this);
        this.quickFilter.onTrigger2Click = this.quickFilter.onTrigger2Click.createSequence(this.onQuickFilterTrigger, this);
        
        this.criteriaText = new Ext.Panel({
            border: 0,
//            hidden: true,
            html: '',
            bodyStyle: {
                border: 0,
                background: 'none', 
                'text-align': 'left', 
                'line-height': '11px'
            }
        });
        
        this.detailsToggleBtn = new Ext.Button({
            style: {'margin-top': '2px'},
            enableToggle: true,
            text: _('show details'), //'Advanced Search'
            tooltip: _('Always show advanced filters'),
            scope: this,
            handler: this.onDetailsToggle,
            stateful: stateful,
            stateId : stateful ? stateId : null,
            getState: function() {
                return {detailsButtonPressed: this.pressed};
            },
            applyState: function(state) {
                if (state.detailsButtonPressed) {
                    this.toggle(state.detailsButtonPressed);
                }
            },
            stateEvents: ['toggle'],
            listeners: {
                scope: this,
                render: function() {
                    // limit width of this.criteriaText
                    this.criteriaText.setWidth(this.quickFilterGroup.getWidth() - this.detailsToggleBtn.getWidth());
                    this.onDetailsToggle(this.detailsToggleBtn);
                }
            }
        });
    },
    
    /**
     * called when the (external) quick filter is cleared
     */
    onQuickFilterClear: function() {
        this.quickFilter.reset();
        this.activeFilterPanel.onFiltertrigger.call(this.activeFilterPanel);
        
    },
    
    /**
     * called when the (external) filter triggers filter action
     */
    onQuickFilterTrigger: function() {
        this.activeFilterPanel.onFiltertrigger.call(this.activeFilterPanel);
    },
    
    /**
     * called when the details toggle button gets toggled
     * 
     * @param {Ext.Button} btn
     */
    onDetailsToggle: function(btn) {
        this[btn.pressed ? 'show' : 'hide']();
        this.quickFilter.setDisabled(btn.pressed);
        this.manageCriteriaText();
        
        if (btn.pressed) {
            var quickFilterValue = this.quickFilter.getValue(),
                quickFilter;
                
            this.quickFilter.setValue('');
            
            if (quickFilterValue) {
                // find quickfilterrow
                this.activeFilterPanel.filterStore.each(function(filter) {
                    if (filter.get('field') == this.quickFilterField && ! filter.get('value')) {
                        quickFilter = filter;
                        quickFilter.set('value', quickFilterValue);
                        quickFilter.formFields.value.setValue(quickFilterValue);
                        return false;
                    }
                }, this);
                
                if (! quickFilter) {
                    quickFilter = this.activeFilterPanel.addFilter(new this.activeFilterPanel.record({field: this.quickFilterField, value: quickFilterValue}));
                }
            }
            
        }
        
        this.activeFilterPanel.doLayout();
        this.manageHeight();
    },
    
    /**
     * manages the criteria text
     */
    manageCriteriaText: function() {
        var moreCriterias = false,
            filterPanelCount = 0,
            criterias = [];
            
        // count filterPanels
        for (var id in this.filterPanels) {if (this.filterPanels.hasOwnProperty(id)) {filterPanelCount++;}}
        
        if (! filterPanelCount > 1) {
            moreCriterias = true;
            
        } else {
            // not more filters only if we hove one filterPanel & only one queryFilter in it (or implicit filters)
            this.activeFilterPanel.filterStore.each(function(filter) {
                var f = this.activeFilterPanel.getFilterData(filter);
                
                for (var i=0, criteria, ignore; i<this.criteriaIgnores.length; i++) {
                    criteria = this.criteriaIgnores[i];
                    ignore = true;
                    
                    for (var p in criteria) {
                        if (criteria.hasOwnProperty(p)) {
                            if (Ext.isString(criteria[p]) || Ext.isEmpty(f[p]) ) {
                                ignore &= f.hasOwnProperty(p) && f[p] === criteria[p];
                            } else {
                                for (var pp in criteria[p]) {
                                    if (criteria[p].hasOwnProperty(pp)) {
                                        ignore &= f.hasOwnProperty(p) && typeof f[p].hasOwnProperty == 'function' && f[p].hasOwnProperty(pp) && f[p][pp] === criteria[p][pp];
                                    }
                                }
                            }
                        }
                    }
                    
                    if (ignore) {
                        // don't judge them as criterias
                        return;
                    }
                }
                
                if (this.activeFilterPanel.filterModelMap[f.field]) {
                    criterias.push(this.activeFilterPanel.filterModelMap[f.field].label);
                } else {
                    // no idea how to get the filterplugin for non ftb itmes
                    criterias.push(f.field);
                }
            }, this);
            moreCriterias = criterias.length > 0;
        }
        
        moreCriterias = this.hidden ? moreCriterias : false;
        
        if (this.criteriaText && this.criteriaText.rendered) {
            this.criteriaText.update(moreCriterias ? _(this.moreFiltersActiveText) : '');
        }
    },
    
    /**
     * gets the (extra) quick filter toolbar items
     * 
     * @return {Ext.ButtonGroup}
     */
    getQuickFilterField: function() {
        if (! this.quickFilterGroup) {
            this.quickFilterGroup = new Ext.ButtonGroup({
                columns: 1,
                items: [
                    this.quickFilter, {
                        xtype: 'toolbar',
                        style: {border: 0, background: 'none'},
                        items: [this.criteriaText, '->', this.detailsToggleBtn]
                    }
                ]
            });
        }
        
        return this.quickFilterGroup;
    },
    
    getQuickFilterPlugin: function() {
        return this;
    },
    
    getValue: function() {
        var quickFilterValue = this.quickFilter.getValue(),
            filters = [];
        
        if (quickFilterValue) {
            filters.push({field: this.quickFilterField, operator: 'contains', value: quickFilterValue, id: 'quickFilter'});
            
            // add implicit / ignored fields (important e.g. for container_id)
            Ext.each(this.criteriaIgnores, function(criteria) {
                if (criteria.field != this.quickFilterField) {
                    var filterIdx = this.activeFilterPanel.filterStore.find('field', criteria.field),
                        filter = filterIdx >= 0  ? this.activeFilterPanel.filterStore.getAt(filterIdx) : null,
                        filterModel = filter ? this.activeFilterPanel.getFilterModel(filter) : null;
                    
                    if (filter) {
                        filters.push(Ext.isFunction(filterModel.getFilterData) ? filterModel.getFilterData(filter) : this.activeFilterPanel.getFilterData(filter));
                    }
                }
                
            }, this);
            
            return filters;
        }
        
        for (var id in this.filterPanels) {
            if (this.filterPanels.hasOwnProperty(id) && this.filterPanels[id].isActive) {
                filters.push({'condition': 'AND', 'filters': this.filterPanels[id].getValue(), 'id': id, label: this.filterPanels[id].title});
            }
        }
        
        // NOTE: always trigger a OR condition, otherwise we sould loose inactive FilterPanles
        //return filters.length == 1 ? filters[0].filters : [{'condition': 'OR', 'filters': filters}];
        return [{'condition': 'OR', 'filters': filters}];
    },
    
    setValue: function(value) {
        
        // NOTE: value is always an array representing a filterGroup with condition AND (server limitation)!
        //       so we need to route "alternate criterias" (OR on root level) through this filterGroup for transport
        //       and scrape them out here -> this also means we whipe all other root level filters (could only be implicit once)
        var alternateCriterias = false;
        Ext.each(value, function(filterData) {
            if (filterData.condition && filterData.condition == 'OR') {
                value = filterData.filters;
                alternateCriterias = true;
                return false;
            }
        }, this);
        
        
        if (! alternateCriterias) {
            // reset criterias
//            this.criteriaCount = 0;
//            this.activeFilterPanel.setTitle(String.format(_('Criteria {0}'), ++this.criteriaCount));
            this.activeFilterPanel.setTitle(this.activeFilterPanel.generateTitle());
            for (var id in this.filterPanels) {
                if (this.filterPanels.hasOwnProperty(id)) {
                    if (this.filterPanels[id] != this.activeFilterPanel) {
                        this.removeFilterPanel(this.filterPanels[id]);
                    }
                }
            }
            
            this.activeFilterPanel.setValue(value);
            
            var quickFilterValue = this.activeFilterPanel.filterStore.getById('quickFilter');
            if (quickFilterValue) {
                this.quickFilter.setValue(quickFilterValue.get('value'));
                this.activeFilterPanel.supressEvents = true;
                this.activeFilterPanel.deleteFilter(quickFilterValue);
                this.activeFilterPanel.supressEvents = false;
            }
        } 
        
        // OR condition on root level
        else {
            var keepFilterPanels = [],
                activeFilterPanel = this.activeFilterPanel;
            
            Ext.each(value, function(filterData) {
                var filterPanel;
                
                // refresh existing filter panel
                if (filterData.id && this.filterPanels.hasOwnProperty(filterData.id)) {
                    filterPanel = this.filterPanels[filterData.id];
                }
                
                // create new filterPanel
                else {
                    filterPanel = this.addFilterPanel({id: filterData.id});
                    this.setActiveFilterPanel(filterPanel);
                }
                
                filterPanel.setValue(filterData.filters);
                keepFilterPanels.push(filterPanel.id);
                
                if (filterData.label) {
                    filterPanel.setTitle(Ext.util.Format.htmlEncode(filterData.label));
                }
                
            }, this);
            
            // (re)activate filterPanel
            this.setActiveFilterPanel(keepFilterPanels.indexOf(activeFilterPanel.id) > 0 ? activeFilterPanel : keepFilterPanels[0]);
            
            
            // remove unused panels
            for (var id in this.filterPanels) {
                if (this.filterPanels.hasOwnProperty(id) && keepFilterPanels.indexOf(id) < 0 && this.filterPanels[id].isActive == true) {
                    this.removeFilterPanel(id);
                }
            }
            
        }
        
        this.manageCriteriaText();
    }
});/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * TODO         container filter should extend this
 */
Ext.ns('Tine.widgets.grid');

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.PickerFilter
 * @extends     Tine.widgets.grid.FilterModel
 */
Tine.widgets.grid.PickerFilter = Ext.extend(Tine.widgets.grid.FilterModel, {
    /**
     * @property Tine.Tinebase.Application app
     */
    app: null,
    
    /**
     * @cfg field
     * @type String
     */
    field: '',

    /**
     * @cfg defaultOperator
     * @type String
     */
    defaultOperator: 'in',

    /**
     * @cfg defaultValue
     * @type String
     */
    defaultValue: '',

    /**
     * @cfg label
     * @type String
     */
    label: '',

    /**
     * @cfg filterValueWidth
     * @type Integer
     */
    filterValueWidth: 200,

    /**
     * @cfg multiselectField
     */
    multiselectFieldConfig: null,
    
    /**
     * record picker
     * 
     * @type Tine.Tinebase.widgets.form.RecordPickerComboBox
     */
    picker: null,
    
    /**
     * record class
     * 
     * @type Tine.Tinebase.data.Record
     */
    recordClass: null,
    
    /**
     * @private
     */
    initComponent: function() {
        this.operators = this.operators || ['equals', 'not', 'in', 'notin'];
        
        // TODO invent a picker registry
        if (this.picker === null) {
            this.picker = (this.recordClass == Tine.Addressbook.Model.Contact) ?  Tine.Addressbook.SearchCombo : Tine.Tinebase.widgets.form.RecordPickerComboBox;
        }
        this.multiselectFieldConfig = this.multiselectFieldConfig || {
            selectionWidget: new this.picker ({
                app: this.app,
                recordClass: this.recordClass,
                blurOnSelect: true
            }),
            recordClass: this.recordClass,
            isSelectionVisible: function() {
                return this.selectionWidget.isExpanded();
            }
        };
        
        Tine.widgets.grid.PickerFilter.superclass.initComponent.call(this);
    },
    
    /**
     * called on operator change of a filter row
     * @private
     * 
     * TODO keep value widget if old operator / new operator use the same widget?
     */
    onOperatorChange: function(filter, newOperator, keepValue) {
        var oldOperator = filter.formFields.operator.getValue(),
            oldValues = [].concat(filter.formFields.value.getValue()),
            oldValuesStore = filter.formFields.value.store,
            el = Ext.select('tr[id=' + this.ftb.frowIdPrefix + filter.id + '] td[class^=tw-ftb-frow-value]', this.ftb.el).first();
        
        // make sure we have the old value record datas
        Ext.each(oldValues, function(value, idx) {
            try {
                if (Ext.isPrimitive(value)) {
                    oldValues[idx] = oldValuesStore.getById(value).data
                }
            } catch (e) {
                Tine.log.warn('Tine.widgets.grid.PickerFilter::onOperatorChange could not get record data');
            }
        }, this);
        
        Tine.widgets.grid.PickerFilter.superclass.onOperatorChange.apply(this, arguments);
        
        // NOTE: removeMode got introduced on ext3.1 but is not documented
        //       'childonly' is no ext mode, we just need something other than 'container'
        if (filter.formFields.value && Ext.isFunction(filter.formFields.value.destroy)) {
            filter.formFields.value.removeMode = 'childsonly';
            filter.formFields.value.destroy();
            delete filter.formFields.value;
        }
        
        filter.formFields.value = this.valueRenderer(filter, el);
        
        // retain value(s) on operator change (single -> multi, multi -> multi)
        if (['in', 'notin'].indexOf(newOperator) > -1) {
            filter.formFields.value.setValue(oldValues);
        }
    },
    
    /**
     * value renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    valueRenderer: function(filter, el) {
        if (filter.formFields.value) {
            return filter.formFields.value;
        }

        var operator = filter.get('operator') ? filter.get('operator') : this.defaultOperator,
            value;
            
        Tine.log.debug('Tine.widgets.grid.PickerFilter::valueRenderer() - Creating new value field for ' + operator + ' operator.')

        switch(operator) {
            case 'equals':
            case 'not':
                value = this.getPicker(filter, el);
                break;
            default:
                value = this.getPickerGridLayerCombo(filter, el);
        }

        value.on('specialkey', function(field, e){
             if (e.getKey() == e.ENTER){
                 this.onFiltertrigger();
             }
        }, this);
        value.on('select', this.onFiltertrigger, this);
        
        return value;
    },
    
    /**
     * get record picker
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     * 
     */
    getPicker: function(filter, el) {
        Tine.log.debug('Tine.widgets.grid.PickerFilter::getPicker()');
        
        var result = new this.picker ({
            app: this.app,
            recordClass: this.recordClass,
            filter: filter,
            blurOnSelect: true,
            width: this.filterValueWidth,
//            listWidth: 500,
//            listAlign: 'tr-br',
            id: 'tw-ftb-frow-valuefield-' + filter.id,
            value: filter.data.value ? filter.data.value : this.defaultValue,
            renderTo: el
        });
        
        return result;
    },

    /**
     * get picker grid layer combo
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     * 
     */
    getPickerGridLayerCombo: function(filter, el) {
        return new Tine.widgets.grid.PickerFilterValueField(Ext.apply({
            app: this.app,
            filter: filter,
            width: this.filterValueWidth,
            id: 'tw-ftb-frow-valuefield-' + filter.id,
            value: filter.data.value ? filter.data.value : this.defaultValue,
            renderTo: el
        }, this.multiselectFieldConfig));
    }
});

Tine.widgets.grid.FilterToolbar.FILTERS['tinebase.pickerfilter'] = Tine.widgets.grid.PickerFilter;

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.PickerFilterValueField
 * @extends     Ext.ux.form.LayerCombo
 */
Tine.widgets.grid.PickerFilterValueField = Ext.extend(Ext.ux.form.LayerCombo, {
    layerHeight: 200,
    hideButtons: false,
    formConfig: {
        labelAlign: 'left',
        labelWidth: 30
    },
    labelField: 'name',
    recordClass: null,
    valueStore: null,

    selectionWidget: null,
    labelRenderer: Ext.emptyFn,
    
    /**
     * init
     */
    initComponent: function() {
        this.on('beforecollapse', this.onBeforeCollapse, this);
        if (! this.store) {
            this.store = new Ext.data.SimpleStore({
                fields: this.recordClass
            });
        }
        
        Tine.widgets.grid.PickerFilterValueField.superclass.initComponent.call(this);
    },
    
    /**
     * get form values
     * 
     * @return {Array}
     */
    getFormValue: function() {
        var values = [];

        this.store.each(function(record) {
            values.push(record.data);
        }, this);
        
        return values;
    },
    
    /**
     * get items
     * 
     * @return {Array}
     */
    getItems: function() {
        var me = this,
            items = [];

        this.initSelectionWidget();
        
        this.pickerGridPanel = new Tine.widgets.grid.PickerGridPanel({
            height: this.layerHeight || 'auto',
            recordClass: this.recordClass,
            store: this.store,
            autoExpandColumn: this.labelField,
            getColumnModel: this.getColumnModel.createDelegate(this),
            initActionsAndToolbars: function() {
                Tine.widgets.grid.PickerGridPanel.prototype.initActionsAndToolbars.call(this);
                this.tbar = new Ext.Toolbar({
                    layout: 'fit',
                    items: [ me.selectionWidget ]
                });
            }
        });
        
        items.push(this.pickerGridPanel);
        
        return items;
    },
    
    /**
     * init selection widget
     */
    initSelectionWidget: function() {
        this.selectionWidget.on('select', this.onRecordSelect, this);
    },
    
    /**
     * @return Ext.grid.ColumnModel
     */
    getColumnModel: function() {
        var labelColumn = {id: this.labelField, header: String.format(_('Selected  {0}'), this.recordClass.getMeta('recordsName')), dataIndex: this.labelField};
        if (this.labelRenderer != Ext.emptyFn) {
            labelColumn.renderer = this.labelRenderer;
        }
        
        return new Ext.grid.ColumnModel({
            defaults: {
                sortable: false
            },
            columns:  [ labelColumn ]
        });
    },
    
    /**
     * record select
     * 
     * @param {String} field
     * @param {Object} recordData
     */
    onRecordSelect: function(field, recordData) {
        this.addRecord(recordData);
        this.selectionWidget.suspendEvents();
        this.selectionWidget.clearValue();
        this.selectionWidget.resumeEvents();
    },
    
    /**
     * adds record from selection widget to store
     * 
     * @param {Object} recordData
     */
    addRecord: function(recordData) {
        Tine.log.debug('Tine.widgets.grid.PickerFilterValueField::addRecord()');
        Tine.log.debug(recordData);
        
        var data = (recordData.data) ? recordData.data : recordData.attributes ? recordData.attributes : recordData;
        
        var existingRecord = this.store.getById(recordData.id);
        if (! existingRecord) {
            this.store.add(new this.recordClass(data));
            
        } else {
            var idx = this.store.indexOf(existingRecord);
            var row = this.pickerGridPanel.getView().getRow(idx);
            Ext.fly(row).highlight();
        }
        
        if (this.selectionWidget.selectPanel) {
            this.selectionWidget.selectPanel.close();
        }
    },
    
    /**
     * @param {String} value
     * @return {Ext.form.Field} this
     */
    setValue: function(value) {
        value = Ext.isArray(value) ? value : [value];
        
        Tine.log.debug('Tine.widgets.grid.PickerFilterValueField::setValue()');
        Tine.log.debug(value);
        
        var recordText = [];
        this.currentValue = [];
        
        this.store.removeAll();
        var record, id, text;
        for (var i=0; i < value.length; i++) {
            text = this.getRecordText(value[i]);
            if (text && text !== '') {
                recordText.push(text);
            }
        }
        
        this.setRawValue(recordText.join(', '));
        
        return this;
    },
    
    /**
     * get text from record defined by value (id or something else)
     * 
     * @param {String|Object} value
     * @return {String}
     */
    getRecordText: function(value) {
        var id = (Ext.isString(value)) ? value : value.id,
            record = (this.valueStore) ? this.valueStore.getById(id) : ((! Ext.isString(value)) ? new this.recordClass(value, id) : null);
            
        Tine.log.debug('Tine.widgets.grid.PickerFilterValueField::getRecordText()');
        Tine.log.debug(record);
            
        if (! record) {
            return '';
        }
        
        // always copy/clone record because it can't exist in 2 different stores
        this.store.add(record.copy());
        this.currentValue.push(record.id);
        
        var text = record.get(this.labelField);
        text = (this.labelRenderer != Ext.emptyFn) ? this.labelRenderer(text) : text;
        
        return text;
    },
    
    /**
     * cancel collapse if ctx menu or record selection is shown
     * 
     * @return Boolean
     */
    onBeforeCollapse: function() {
        var result = true;
        
        if (this.pickerGridPanel) {
            var contextMenuVisible = this.pickerGridPanel.contextMenu && ! this.pickerGridPanel.contextMenu.hidden,
                selectionVisible = this.isSelectionVisible();
            result = ! (contextMenuVisible || selectionVisible);
        }
        
        Tine.log.debug('Tine.widgets.grid.PickerFilterValueField::onBeforeCollapse() - collapse: ' + result);
        
        return result;
    },
    
    /**
     * is selection visible ?
     * - overwrite this when extending to make sure that the selection widget is no longer visible on collapse
     * 
     * @return {Boolean}
     */
    isSelectionVisible: function() {
        return false;
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterToolbarQuickFilterPlugin
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * This plugin provides an external filter field (quickfilter) as a plugin of a filtertoolbar.
 * The filtertoolbar itself will be hidden no filter is set.
 * 
 * @example
 <pre><code>
    // init quickfilter as plugin of filtertoolbar
    this.quickSearchFilterToolbarPlugin = new Tine.widgets.grid.FilterToolbarQuickFilterPlugin();
    this.filterToolbar = new Tine.widgets.grid.FilterToolbar({
        filterModels: Tine.Addressbook.Model.Contact.getFilterModel(),
        defaultFilter: 'query',
        filters: [],
        plugins: [
            this.quickSearchFilterToolbarPlugin
        ]
    });
    
    // put quickfilterfield in a toolbar
    this.tbar = new Ext.Toolbar({
        '->',
        this.quickSearchFilterToolbarPlugin.getQuickFilterField()
    })
</code></pre>
 */
Tine.widgets.grid.FilterToolbarQuickFilterPlugin = function(config) {
    config = config || {};
    
    this.criteriaIgnores = config.criteriaIgnores || [
        {field: 'container_id', operator: 'equals', value: {path: '/'}},
        {field: 'query',        operator: 'contains',    value: ''}
    ];
        
    Ext.apply(this, config);
};

Tine.widgets.grid.FilterToolbarQuickFilterPlugin.prototype = {
    /**
     * @cfg {String} quickFilterField
     * 
     * name of quickfilter filed in filter definitions
     */
    quickFilterField: 'query',
    
    /**
     * filter toolbar we are plugin of
     * 
     * @type {Tine.widgets.grid.FilterToolbar} ftb
     */
    ftb: null,
    
    /**
     * external quick filter field
     * 
     * @type {Ext.ux.searchField} quickFilter
     */
    quickFilter: null,
    
    /**
     * filter row of filter toolbar where a quickfilter is set
     * 
     * @type {Ext.data.record}
     */
    quickFilterRow: null,
    
    /**
     * @cfg {Array} criterias to ignore
     */
    criteriaIgnores: null,
    
    /**
     * bind value field of this.quickFilterRow to sync process
     */
    bind: function() {
        this.quickFilterRow.formFields.value.on('keyup', this.syncField, this);
        this.quickFilterRow.formFields.value.on('change', this.syncField, this);
    },
    
    /**
     * gets the (extra) quick filter toolbar items
     * 
     * @return {Ext.ButtonGroup}
     */
    getQuickFilterField: function() {
        if (! this.quickFilterGroup) {
            this.quickFilterGroup = new Ext.ButtonGroup({
                columns: 1,
                items: [
                    this.quickFilter, {
                        xtype: 'toolbar',
                        style: {border: 0, background: 'none'},
                        items: [this.criteriaText, '->', this.detailsToggleBtn]
                    }
                ]
            });
        }
        
        return this.quickFilterGroup;
    },
    
    getQuickFilterPlugin: function() {
        return this;
    },
    
    /**
     * gets the quick filter field from the filtertoolbar which is in sync with
     * the (extra) quick filter field. 
     */
    getQuickFilterRowField: function() {
        if (! this.quickFilterRow) {
            // NOTE: at this point there is no query filter in the filtertoolbar
            var filter = new this.ftb.record({field: this.quickFilterField, value: this.quickFilter.getValue()});
            this.ftb.addFilter(filter);
        }
        
        return this.quickFilterRow;
    },
    
    /**
     * called by filtertoolbar in plugin init process
     * 
     * @param {Tine.widgets.grid.FilterToolbar} ftb
     */
    init: function(ftb) {
        this.ftb = ftb;
        this.ftb.renderFilterRow = this.ftb.renderFilterRow.createSequence(this.onAddFilter, this);
        this.ftb.onFieldChange   = this.ftb.onFieldChange.createSequence(this.onFieldChange, this);
        this.ftb.deleteFilter    = this.ftb.deleteFilter.createInterceptor(this.onBeforeDeleteFilter, this);
        this.ftb.setValue        = this.ftb.setValue.createSequence(this.onSetValue, this);
        this.ftb.onRender        = this.ftb.onRender.createSequence(this.onRender, this);
        
        
        //this.ftb.onFilterRowsChange = this.ftb.onFilterRowsChange.createInterceptor(this.onFilterRowsChange, this);
        this.ftb.getQuickFilterField = this.getQuickFilterField.createDelegate(this);
        this.ftb.getQuickFilterPlugin = this.getQuickFilterPlugin.createDelegate(this);
        
        this.quickFilter = new Ext.ux.SearchField({
            width: 300,
            enableKeyEvents: true
        });
        
        this.quickFilter.onTrigger1Click = this.quickFilter.onTrigger1Click.createSequence(this.onQuickFilterClear, this);
        this.quickFilter.onTrigger2Click = this.quickFilter.onTrigger2Click.createSequence(this.onQuickFilterTrigger, this);
        
        this.quickFilter.on('keyup', this.syncField, this);
        this.quickFilter.on('change', this.syncField, this);
        
        this.criteriaText = new Ext.Panel({
            border: 0,
            html: '',
            bodyStyle: {
                border: 0,
                background: 'none', 
                'text-align': 'left', 
                'line-height': '11px'
            }
        });
        
        var stateful = !! this.ftb.recordClass;
        // autogenerate stateId
        if (stateful) {
            var stateId = this.ftb.recordClass.getMeta('appName') + '-' + this.ftb.recordClass.getMeta('recordName') + '-FilterToolbar-QuickfilterPlugin';
        }
        
        this.detailsToggleBtn = new Ext.Button({
            style: {'margin-top': '2px'},
            enableToggle: true,
            text: _('show details'),
            tooltip: _('Always show advanced filters'),
            scope: this,
            handler: this.onDetailsToggle,
            stateful: stateful,
            stateId : stateful ? stateId : null,
            getState: function() {
                return {detailsButtonPressed: this.pressed};
            },
            applyState: function(state) {
                if (state.detailsButtonPressed) {
                    this.toggle(state.detailsButtonPressed);
                }
            },
            stateEvents: ['toggle'],
            listeners: {
                scope: this,
                render: function() {
                    // limit width of this.criteriaText
                    this.criteriaText.setWidth(this.quickFilterGroup.getWidth() - this.detailsToggleBtn.getWidth());
                }
            }
        });
        
        this.ftb.hide();
    },
    
    /**
     * called when a filter is added to the filtertoolbar
     * 
     * @param {Ext.data.Record} filter
     */
    onAddFilter: function(filter) {
        if (filter.get('field') == this.quickFilterField && ! this.quickFilterRow) {
            this.quickFilterRow = filter;
            this.bind();
            
            // preset quickFilter with filterrow value
            this.syncField(filter.formFields.value);
        }
        
    },
    
    /**
     * called when the details toggle button gets toggled
     * 
     * @param {Ext.Button} btn
     */
    onDetailsToggle: function(btn) {
        this.ftb[btn.pressed ? 'show' : 'hide']();
        
        // cares for resizing
        this.ftb.onFilterRowsChange();
    },
    
    /**
     * called when a filter field of the filtertoolbar changes
     */
    onFieldChange: function(filter, newField) {
        if (filter == this.quickFilterRow) {
            this.onBeforeDeleteFilter(filter);
        }
        
        if (newField == this.quickFilterField) {
            this.onAddFilter(filter);
        }
    },
    
    /**
     * called when the filterrows of the filtertoolbar changes
     * 
     * we detect the hidestatus of the filtertoolbar
     *
    onFilterRowsChange: function() {
        this.ftb.searchButtonWrap.removeClass('x-btn-over');
        
        if (this.ftb.filterStore.getCount() <= 1 
            && this.ftb.filterStore.getAt(0).get('field') == this.quickFilterField
            && !this.ftb.filterStore.getAt(0).formFields.value.getValue()
            && !this.detailsToggleBtn.pressed) {
            
            this.ftb.hide();
        } else {
            this.ftb.show();
        }
    },
    */
    
    /**
     * called before a filter row is deleted from filtertoolbar
     * 
     * @param {Ext.data.Record} filter
     */
    onBeforeDeleteFilter: function(filter) {
        if (filter == this.quickFilterRow) {
            this.quickFilter.setValue('');
            this.unbind();
            delete this.quickFilterRow;
            
            // look for an other quickfilterrow
            this.ftb.filterStore.each(function(f) {
                if (f != filter && f.get('field') == this.quickFilterField ) {
                    this.onAddFilter(f);
                    return false;
                }
            }, this);
        }
    },
    
    /**
     * called when the (external) quick filter is cleared
     */
    onQuickFilterClear: function() {
        this.ftb.deleteFilter(this.quickFilterRow);
        this.quickFilter.reset();
    },
    
    /**
     * called when the (external) filter triggers filter action
     */
    onQuickFilterTrigger: function() {
        this.ftb.onFiltertrigger.call(this.ftb);
        this.ftb.onFilterRowsChange.call(this.ftb);
    },
    
    /**
     * called after onRender is called for the filter toolbar
     * 
     * @param {Array} filters
     */
    onRender: function() {
        this.onDetailsToggle(this.detailsToggleBtn);
    },
    
    /**
     * called after setValue is called for the filter toolbar
     * 
     * @param {Array} filters
     */
    onSetValue: function(filters) {
        this.setCriteriaText(filters);
    },
    
    /**
     * sets this.criteriaText according to filters
     * 
     * @param {Array} filters
     */
    setCriteriaText: function(filters) {
        var text = '' , 
            criterias = [];
        
        Ext.each(filters, function(f) {
            for (var i=0, criteria, ignore; i<this.criteriaIgnores.length; i++) {
                criteria = this.criteriaIgnores[i];
                ignore = true;
                
                for (var p in criteria) {
                    if (criteria.hasOwnProperty(p)) {
                        if (Ext.isString(criteria[p]) || Ext.isEmpty(f[p]) ) {
                            ignore &= f.hasOwnProperty(p) && f[p] === criteria[p];
                        } else {
                            for (var pp in criteria[p]) {
                                if (criteria[p].hasOwnProperty(pp)) {
                                    ignore &= f.hasOwnProperty(p) && typeof f[p].hasOwnProperty == 'function' && f[p].hasOwnProperty(pp) && f[p][pp] === criteria[p][pp];
                                }
                            }
                        }
                    }
                }
                
                if (ignore) {
                    // don't judge them as criterias
                    return;
                }
            }
            
            if (this.ftb.filterModelMap[f.field]) {
                criterias.push(this.ftb.filterModelMap[f.field].label);
            } else {
                // no idea how to get the filterplugin for non ftb itmes
                criterias.push(f.field);
            }
        }, this);
        
        
        if (! Ext.isEmpty(criterias)) {
            text = String.format(Tine.Tinebase.translation.ngettext('Your view is limited by {0} criteria:', 'Your view is limited by {0} criterias:', criterias.length), criterias.length) + 
                   '<br />' +
                   '&nbsp;' + criterias.join(', ');
        }
            
        this.criteriaText.update(text);
    },
    
    /**
     * syncs field contents of this.quickFilterRow and this.quickFilter
     * 
     * @param {Ext.EventObject} e
     * @param {Ext.form.Field} field
     */
    syncField: function(field) {
        if (field == this.quickFilter) {
            this.getQuickFilterRowField().formFields.value.setValue(this.quickFilter.getValue());
        } else {
            this.quickFilter.setValue(this.quickFilterRow.formFields.value.getValue());
        }
    },
    
    /**
     * unbind value field of this.quickFilterRow from sync process
     */
    unbind: function() {
        this.quickFilterRow.formFields.value.un('keyup', this.syncField, this);
        this.quickFilterRow.formFields.value.un('change', this.syncField, this);
    }
};
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

/**
 * a row selection model capable to return filters
 * @constructor
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterSelectionModel
 * @extends     Ext.grid.RowSelectionModel
 */
Tine.widgets.grid.FilterSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
    /**
     * @cfg {Ext.data.Store}
     */
    store: null,
    
    /**
     * @cfg {Tine.widgets.grid.GridPanel}
     */
    gridPanel: null,
    
    /**
     * @property {Bool}
     */
    isFilterSelect: false,
    
    /**
     * Returns filterData representing current selection
     * 
     * @return {Array} filterData
     */
    getSelectionFilter: function() {
        if(! this.isFilterSelect) {
            var filter = this.getFilterOfRowSelection();
        } else {
            /* cruide hack lets save it as comment, maybe we need it some time ;-)
            var opts = {}
            for (var i=0; i<this.store.events.beforeload.listeners.length; i++) {
                if (this.store.events.beforeload.listeners[i].scope.xtype == 'filterplugin') {
                    this.store.events.beforeload.listeners[i].fireFn.call(this.store.events.beforeload.listeners[i].scope, this.store, opts);
                }
            }
            //console.log(opts.params.filter);
            return opts.params.filter;
            */
            var filter = this.getAllFilterData();
        }
        return filter;
    },
    
    /**
     * get current selection as collection
     * 
     * @return {Ext.util.MixedCollection}
     */
    getSelectionsCollection: function() {
        return this.selections;
    },
    
    /**
     * returns ids of currently visible page
     * 
     * @return {Array}
     */
    getCurrentPageIds: function() {
        var ids = [];
        this.store.each(function(record) {
            ids.push(record.id);
        }, this);
        
        return ids;
    },
    
    /**
     * gets filter data of all filter plugins
     * 
     * NOTE: As we can't find all filter plugins directly we need a litte hack 
     *       to get their data
     *       
     *       We register ourselve as latest beforeload.
     *       In the options.filter we have the filters then.
     */
    getAllFilterData: function() {
        this.store.on('beforeload', this.storeOnBeforeload, this);
        this.store.load();
        this.store.un('beforeload', this.storeOnBeforeload, this);
        
        return this.allFilterData;
    },
    
    storeOnBeforeload: function(store, options) {
        this.allFilterData = options.params.filter;
        this.store.fireEvent('exception');
        return false;
    },
    
    /**
     * selects all rows 
     * @param {Bool} onlyPage select only rows from current page
     * @return {Void}
     */
    selectAll: function(onlyPage) {
        this.isFilterSelect = !onlyPage;
        
        Tine.widgets.grid.FilterSelectionModel.superclass.selectAll.call(this);
    },
    
    /**
     * @private
     */
    onRefresh : function(){
        this.clearSelections(true);
        Tine.widgets.grid.FilterSelectionModel.superclass.onRefresh.call(this);
    },
    
    /**
     * @private
     */
    onRemove : function(v, index, r){
        this.clearSelections(true);
        Tine.widgets.grid.FilterSelectionModel.superclass.onRemove.call(this, v, index, r);
    },
    
    /**
     * Deselects a row.
     * @param {Number} row The index of the row to deselect
     */
    deselectRow : function(index, preventViewNotify) {
        this.isFilterSelect = false;
        Tine.widgets.grid.FilterSelectionModel.superclass.deselectRow.call(this, index, preventViewNotify);
    },
    
    /**
     * Clears all selections.
     */
    clearSelections: function(silent) {
        this.suspendEvents();
        this.isFilterSelect = false;
        
        Tine.widgets.grid.FilterSelectionModel.superclass.clearSelections.call(this);
        
        this.resumeEvents();
        if (! silent) {
            this.fireEvent('selectionchange', this);
        }
    },
    
    /**
     * toggle selection
     */
    toggleSelection: function() {
        if (this.isFilterSelect) {
            this.clearSelections();
        } else {
            this.suspendEvents();
            
            var index;
            this.store.each(function(record) {
                index = this.store.indexOf(record);
                if (this.isSelected(index)) {
                    this.deselectRow(index);
                } else {
                    this.selectRow(index, true);
                }
            }, this);
            
            this.resumeEvents();
            this.fireEvent('selectionchange', this);
        }
    },
    
    /**
     * Returns number of selected rows
     * 
     * @return {Number}
     */
    getCount: function() {
        if(! this.isFilterSelect) {
            return Tine.widgets.grid.FilterSelectionModel.superclass.getCount.call(this);
        } else {
            return this.store.getTotalCount();
        }
    },
   
    /**
     * converts a 'normal' row selection into a filter
     * 
     * @private
     * @return {Array} filterData
     */
    getFilterOfRowSelection: function() {
        //var idProperty = this.store.fields.getMeta('idProperty');
        var idProperty = this.store.reader.meta.id;
        var ids = [];
        this.each(function(record) {
            ids.push(record.id);
        });
        return [{field: idProperty, operator: 'in', value: ids}];
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

/**
 * Foreign Record Filter
 * 
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.ForeignRecordFilter
 * @extends     Tine.widgets.grid.FilterModel
 * 
 * <p>Filter for foreign records</p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * @param       {Object} config
 * @constructor
 */
Tine.widgets.grid.ForeignRecordFilter = Ext.extend(Tine.widgets.grid.FilterModel, {
    
    /**
     * @cfg {Application} app (required)
     */
    app: null,
    
    /**
     * @cfg {Application} ownRecordClass own record class for generic filter row
     */
    ownRecordClass: null,
    
    /**
     * @cfg {Record} foreignRecordClass needed for explicit defined filters
     */
    foreignRecordClass : null,
    
    /**
     * @cfg {String} linkType {relation|foreignId} needed for explicit defined filters
     */
    linkType: 'relation',
    
    /**
     * @cfg {String} filterName server side filterGroup Name, needed for explicit defined filters
     */
    filterName: null,
    
    /**
     * @cfg {String} ownField for explicit filterRow
     */
    ownField: null,
    
    /**
     * @cfg {String} editDefinitionText untranslated edit definition button text
     */
    editDefinitionText: 'Edit definition', // _('Edit definition')
    
    /**
     * @cfg {Object} optional picker config
     */
    pickerConfig: null,
    
    /**
     * @cfg {String} startDefinitionText untranslated start definition button text
     */
    startDefinitionText: 'Start definition', // _('Start definition')
    
    /**
     * @property this filterModel is the generic filterRow
     * @type Boolean
     */
    isGeneric: false,
    
    field: 'foreignRecord',
    
    /**
     * ignore this php models (filter is not shown)
     * @cfg {Array}
     */
    ignoreRelatedModels: null,
    
    /**
     * @private
     */
    initComponent: function() {
        if (this.foreignRecordClass) {
            this.foreignRecordClass = Tine.Tinebase.data.RecordMgr.get(this.foreignRecordClass);
        }
        
        // TODO: remove this when files can be searched
        this.ignoreRelatedModels = this.ignoreRelatedModels ? this.ignoreRelatedModels.push('Filemanager_Model_Node') : ['Filemanager_Model_Node'];
        
        if (this.ownField) {
            this.field = this.ownField;
        }
        
        this['init' + (this.isGeneric ? 'Generic' : 'Explicit')]();
        Tine.widgets.grid.ForeignRecordFilter.superclass.initComponent.call(this);
    },
    
    /**
     * init the generic foreign filter row
     */
    initGeneric: function() {
            
        this.label = _('Related to');
        
        var operators = [];
        
        // linkType relations automatic list
        if (this.ownRecordClass.hasField('relations')) {
            var operators = [];
            Ext.each(Tine.widgets.relation.Manager.get(this.app, this.ownRecordClass, this.ignoreRelatedModels), function(relation) {
                if (Tine.Tinebase.common.hasRight('run', relation.relatedApp)) {
                    // TODO: leave label as it is?
                    var label = relation.text.replace(/ \(.+\)/,'');
                    operators.push({operator: {linkType: 'relation', foreignRecordClass: Tine.Tinebase.common.resolveModel(relation.relatedModel, relation.relatedApp)}, label: label});
                }
            }, this);
        }
        // get operators from registry
        Ext.each(Tine.widgets.grid.ForeignRecordFilter.OperatorRegistry.get(this.ownRecordClass), function(def) {
            // translate label
            var foreignRecordClass = Tine.Tinebase.data.RecordMgr.get(def.foreignRecordClass),
                appName = foreignRecordClass.getMeta('appName'),
                app = Tine.Tinebase.appMgr.get(appName),
                label = app ? app.i18n._hidden(def.label) : def.label;
            
            operators.push({operator: {linkType: def.linkType, foreignRecordClass: foreignRecordClass, filterName: def.filterName}, label: label});
        }, this);
        
        // we need this to detect operator changes
        Ext.each(operators, function(o) {o.toString = this.objectToString}, this);
        
        this.operatorStore = new Ext.data.JsonStore({
            fields: ['operator', 'label'],
            data: operators
        });
        
        if ( this.operatorStore.getCount() > 0) {
            this.defaultOperator = this.operatorStore.getAt(0).get('operator');
        }
    },
    
    /**
     * init an explicit filter row
     */
    initExplicit: function() {
        this.foreignField = this.foreignRecordClass.getMeta('idProperty');
        
        var foreignApp = Tine.Tinebase.appMgr.get(this.foreignRecordClass.getMeta('appName')),
            i18n;
        if (foreignApp) {
            i18n = foreignApp.i18n;
        } else {
            i18n = new Locale.Gettext();
            i18n.textdomain('Tinebase');
        }
        
        if (! this.label) {
            this.label = i18n.n_(this.foreignRecordClass.getMeta('recordName'), this.foreignRecordClass.getMeta('recordsName'), 1);
        } else {
            this.label = i18n._(this.label);
        }
        
        if (! this.operators) {
            this.operators = ['equals', 'definedBy'];
        }
        
        if (! this.defaultOperator) {
            this.defaultOperator = 'equals';
        }
        
    },
    
    onDefineRelatedRecord: function(filter) {
        if (! filter.toolbar) {
            this.createRelatedRecordToolbar(filter);
        }
        
        this.ftb.setActiveSheet(filter.toolbar);
        filter.formFields.value.setText((this.editDefinitionText));
    },
    
    /**
     * get related record value data
     * 
     * NOTE: generic filters have their foreign record definition in the values
     */
    getRelatedRecordValue: function(filter) {
        var filters = filter.toolbar ? filter.toolbar.getValue() : [],
            foreignRecordClass = filter.foreignRecordDefinition.foreignRecordClass,
            value;
            
        if (this.isGeneric) {
            value = {
                appName: foreignRecordClass.getMeta('appName'),
                modelName: foreignRecordClass.getMeta('modelName'),
                linkType: filter.foreignRecordDefinition.linkType,
                filterName : filter.foreignRecordDefinition.filterName,
                filters: filters
            };
            
        } else {
            value = filters;
            // get value for idField if our own operator is not definedBy
            if (filter.get('operator') != 'definedBy') {
                value.push({field: ':' + foreignRecordClass.getMeta('idProperty'), operator: filter.get('operator'), value: filter.formFields.value.value});
            }
            
            // get values of filters of our toolbar we are superfilter for (left hand stuff)
            this.ftb.filterStore.each(function(filter) {
                var filterModel = this.ftb.getFilterModel(filter);
                if (filterModel.superFilter && filterModel.superFilter == this) {
                    var filterData = this.ftb.getFilterData(filter);
                    value.push(filterData);
                }
            }, this);
            
        }
        
        return value;
    },
    
    /**
     * set related record value data
     * @param {} filter
     */
    setRelatedRecordValue: function(filter) {
        var value = filter.get('value');
        
        if (['equals', 'oneOf'].indexOf(filter.get('operator') ? filter.get('operator') : filter.formFields.operator.origGetValue()) >= 0 ) {
            // NOTE: if setValue got called in the valueField internally, value is arguments[1] (createCallback)
            return filter.formFields.value.origSetValue(arguments[1] ? arguments[1] : value);
        }
        
        // generic: choose right operator : appname -> generic filters have no subfilters an if one day, no left hand once!
        if (this.isGeneric) {
            // get operator
            this.operatorStore.each(function(r) {
                var operator = r.get('operator'),
                    foreignRecordClass = operator.foreignRecordClass;
                    
                if (foreignRecordClass.getMeta('appName') == value.appName && foreignRecordClass.getMeta('modelName') == value.modelName) {
                    filter.formFields.operator.setValue(operator);
                    filter.foreignRecordDefinition = operator;
                    return false;
                }
            }, this);
            
            // set all content on childToolbar
            if (Ext.isObject(filter.foreignRecordDefinition) && value && Ext.isArray(value.filters) && value.filters.length) {
                if (! filter.toolbar) {
                    this.createRelatedRecordToolbar(filter);
                }
                
                filter.toolbar.setValue(value.filters);
                
                // change button text
                if (filter.formFields.value && Ext.isFunction(filter.formFields.value.setText)) {
                    filter.formFields.value.setText(_(this.editDefinitionText));
                }
            }
            
            
        } else {
            if (! Ext.isArray(value)) return;
            
            // explicit chose right operator /equals / in /definedBy: left sided values create (multiple) subfilters in filterToolbar
            var foreignRecordDefinition = filter.foreignRecordDefinition,
                foreignRecordClass = foreignRecordDefinition.foreignRecordClass,
                foreignRecordIdProperty = foreignRecordClass.getMeta('idProperty'),
                parentFilters = [];
                
            Ext.each(value, function(filterData, i) {
                if (! Ext.isString(filterData.field)) return;
                
                if (filterData.implicit) parentFilters.push(filterData);
                    
                var parts = filterData.field.match(/^(:)?(.*)/),
                    leftHand = !!parts[1],
                    field = parts[2];
                
                if (leftHand) {
                    // leftHand id property is handled below
                    if (field == foreignRecordIdProperty) {
                        return;
                    }
                    
                    // move filter to leftHand/parent filterToolbar
                    if (this.ftb.getFilterModel(filterData.field)) {
                        // ftb might have a record with this id
                        // and we can't keep it yet
                        delete filterData.id;
                        this.ftb.addFilter(new this.ftb.record(filterData));
                    }
                    
                    parentFilters.push(filterData);
                }
            }, this);
            
            // remove parent filters
            Ext.each(parentFilters, function(filterData) {value.remove(filterData);}, this);
            
            // if there where no remaining childfilters, hide this filterrow
            if (! value.length)  {
                // prevent loop
                filter.set('value', '###NOT SET###');
                filter.set('value', '');
                
                filter.formFields.operator.setValue(this.defaultOperator);
                this.onOperatorChange(filter, this.defaultOperator, false);
                
                // if (not empty value through operator chage)
                Tine.log.info('hide row -> not yet implemented');
            }
            
            // a single id filter is always displayed in the parent Toolbar with our own filterRow
            else if (value.length == 1 && [foreignRecordIdProperty, ':' + foreignRecordIdProperty].indexOf(value[0].field) > -1) {
                filter.set('value', value[0].value);
                filter.formFields.operator.setValue(value[0].operator);
                this.onOperatorChange(filter, value[0].operator, true);
            }
            
            // set remaining child filters
            else {
                if (! filter.toolbar) {
                    this.createRelatedRecordToolbar(filter);
                }
                
                filter.toolbar.setValue(value);
                
                filter.formFields.operator.setValue('definedBy');
            }
        }
        
    },
    
    /**
     * create a related record toolbar
     */
    createRelatedRecordToolbar: function(filter) {
        var foreignRecordDefinition = filter.foreignRecordDefinition,
            foreignRecordClass = foreignRecordDefinition.foreignRecordClass,
            filterModels = foreignRecordClass.getFilterModel(),
            ftb = this.ftb;

        if (! filter.toolbar) {
            // add our subfilters in this toolbar (right hand)
            if (Ext.isFunction(this.getSubFilters)) {
                filterModels = filterModels.concat(this.getSubFilters());
            }

            filter.toolbar = new Tine.widgets.grid.FilterToolbar({
                recordClass: foreignRecordClass,
                filterModels: filterModels,
                defaultFilter: foreignRecordClass.getMeta('defaultFilter') ? foreignRecordClass.getMeta('defaultFilter') : 'query'
            });
            
            ftb.addFilterSheet(filter.toolbar);
            
            // force rendering as we can't set values on non rendered toolbar atm.
            this.ftb.setActiveSheet(filter.toolbar);
            this.ftb.setActiveSheet(this.ftb);
        }
    },
    
    /**
     * operator renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    operatorRenderer: function (filter, el) {
        var operator;
        
        // init operator value
        filter.set('operator', filter.get('operator') ? filter.get('operator') : this.defaultOperator);
        
        if (! this.isGeneric) {
            operator = Tine.widgets.grid.ForeignRecordFilter.superclass.operatorRenderer.apply(this, arguments);
            filter.foreignRecordDefinition = {linkType: this.linkType, foreignRecordClass: this.foreignRecordClass, filterName: this.filterName}
        } else {
            operator = new Ext.form.ComboBox({
                filter: filter,
                width: 80,
                id: 'tw-ftb-frow-operatorcombo-' + filter.id,
                mode: 'local',
                lazyInit: false,
                emptyText: _('select a operator'),
                forceSelection: true,
                typeAhead: true,
                triggerAction: 'all',
                store: this.operatorStore,
                displayField: 'label',
                valueField: 'operator',
                value: filter.get('operator'),
                renderTo: el
            });
            operator.on('select', function(combo, newRecord, newKey) {
                if (combo.value != combo.filter.get('operator')) {
                    this.onOperatorChange(combo.filter, combo.value);
                }
            }, this);
            
            // init foreignRecordDefinition
            filter.foreignRecordDefinition = filter.get('operator');
        }
        
        
        operator.origGetValue = operator.getValue.createDelegate(operator);
        
        // op is always AND atm.
        operator.getValue = function() {
            return 'AND';
        };
        
//        var origSetValue = operator.setValue.createDelegate(operator);
//        operator.setValue = function(value) {
//            origSetValue(value == 'AND' ? 'definedBy' : value);
//        }
        
        return operator;
    },
    
    /**
     * called on operator change of a filter row
     * @private
     */
    onOperatorChange: function(filter, newOperator, keepValue) {
        if (this.isGeneric) {
            filter.foreignRecordDefinition = newOperator;
        }
        
        if (filter.get('operator') != newOperator) {
            if (filter.toolbar) {
                filter.toolbar.destroy();
                delete filter.toolbar;
            }
        }
        
        filter.set('operator', newOperator);

        if (! keepValue) {
            filter.set('value', '');
        }
        
        var el = Ext.select('tr[id=' + this.ftb.frowIdPrefix + filter.id + '] td[class^=tw-ftb-frow-value]', this.ftb.el).first();
        
        // NOTE: removeMode got introduced on ext3.1 but is not docuemented
        //       'childonly' is no ext mode, we just need something other than 'container'
        if (filter.formFields.value && Ext.isFunction(filter.formFields.value.destroy)) {
            filter.formFields.value.removeMode = 'childsonly';
            filter.formFields.value.destroy();
            delete filter.formFields.value;
        }
        
        filter.formFields.value = this.valueRenderer(filter, el);
    },
    
    /**
     * value renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    valueRenderer: function(filter, el) {
        var operator = filter.get('operator') ? filter.get('operator') : this.defaultOperator,
            value;

        switch(operator) {
            case 'equals':
                
                value = Tine.widgets.form.RecordPickerManager.get(this.foreignRecordClass.getMeta('appName'), this.foreignRecordClass, Ext.apply({
                    filter: filter,
                    blurOnSelect: true,
                    width: this.filterValueWidth,
                    listWidth: 500,
                    listAlign: 'tr-br',
                    id: 'tw-ftb-frow-valuefield-' + filter.id,
                    value: filter.data.value ? filter.data.value : this.defaultValue,
                    renderTo: el
                }, this.pickerConfig));
                
                value.on('specialkey', function(field, e){
                     if(e.getKey() == e.ENTER){
                         this.onFiltertrigger();
                     }
                }, this);
                
                value.origSetValue = value.setValue.createDelegate(value);
                break;
                
            default: 
                this.setRelatedRecordValue(filter);
                
                if (! filter.formFields.value) {
                    value = new Ext.Button({
                        text: _(this.startDefinitionText),
                        filter: filter,
                        width: this.filterValueWidth,
                        id: 'tw-ftb-frow-valuefield-' + filter.id,
                        renderTo: el,
                        handler: this.onDefineRelatedRecord.createDelegate(this, [filter]),
                        scope: this
                    });
                    
                    // show button
                    el.addClass('x-btn-over');
                    
//                    value.getValue = this.getRelatedRecordValue.createDelegate(this, [filter]);
//                    value.setValue = this.setRelatedRecordValue.createDelegate(this, [filter]);
                    
                    // change text if setRelatedRecordValue had child filters
                    if (filter.toolbar) {
                        value.setText((this.editDefinitionText));
                    }
                
                } else {
                    value = filter.formFields.value;
                }
                
                break;
        }
        
        value.setValue = this.setRelatedRecordValue.createDelegate(this, [filter], 0);
        value.getValue = this.getRelatedRecordValue.createDelegate(this, [filter]);
        
        return value;
    },
    
//    getSubFilters: function() {
//        var filterConfigs = this.foreignRecordClass.getFilterModel();
//        
//        Ext.each(filterConfigs, function(config) {
//            this.subFilterModels.push(Tine.widgets.grid.FilterToolbar.prototype.createFilterModel.call(this, config));
//        }, this);
//        
//        return this.subFilterModels;
//    },
    
    objectToString: function() {
        return Ext.encode(this);
    },
    
    onDestroy: function(filterRecord) {
        if(filterRecord.toolbar) {
            this.ftb.removeFilterSheet(filterRecord.toolbar);
            
            delete filterRecord.toolbar;
        }
        
    }
});
    
/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterRegistry
 * @singleton
 */
Tine.widgets.grid.ForeignRecordFilter.OperatorRegistry = function() {
    var operators = {};
    
    return {
        register: function(appName, modelName, operator) {
            var key = appName + '.' + modelName;
            if (! operators[key]) {
                operators[key] = [];
            }
            
            operators[key].push(operator);
        },
        
        get: function(appName, modelName) {
            if (Ext.isFunction(appName.getMeta)) {
                modelName = appName.getMeta('modelName');
                appName = appName.getMeta('appName');
            }
        
            var key = appName + '.' + modelName;
            
            return operators[key] || [];
        }
    };
}();

Tine.widgets.grid.FilterToolbar.FILTERS['foreignrecord'] = Tine.widgets.grid.ForeignRecordFilter;

/**
 * filter own records
 * 
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.OwnRecordFilter
 * @extends     Tine.widgets.grid.ForeignRecordFilter
 * 
 * @constructor
 */
Tine.widgets.grid.OwnRecordFilter = Ext.extend(Tine.widgets.grid.ForeignRecordFilter, {
    
    isGeneric: false,

    initComponent: function() {
        this.label = this.ftb.generateTitle();
        this.field = this.ownRecordClass.getMeta('idProperty');
        this.defaultOperator = 'definedBy';
        
        this.foreignRecordClass = this.ownRecordClass;
        
        Tine.widgets.grid.OwnRecordFilter.superclass.initComponent.call(this);
    },
    
    getFilterData: function(filterRecord) {
        var data = {
            field: this.field,
            id: filterRecord.id,
            label: filterRecord.toolbar ? filterRecord.toolbar.title : null
        };
        
        if (filterRecord.get('operator') == 'equals') {
            Ext.apply(data, {
                operator: 'equals',
                value: filterRecord.formFields.value.getValue()[0].value
            });
        } else {
            Ext.apply(data, {
                condition: filterRecord.condition || 'AND', 
                filters: this.getRelatedRecordValue(filterRecord)
            });
        }
        
        return data;
    },
    
    setFilterData: function(filterRecord, filterData) {
        // work around store compare bug
        if (Ext.isObject(filterData.filters)) {
            filterData.filters.toString = this.objectToString
        }
        
        if (filterData.operator == 'equals') {
            filterData.operator = 'AND';
            
            filterData.value = [{
                field: ':' + this.field,
                operator: 'equals',
                value: filterData.value
            }];
            filterData.value.toString = this.objectToString;
            
            filterRecord.set('operator', 'AND');
            filterRecord.set('value', filterData.value);
        } else {
            filterRecord.condition = filterData.condition || 'AND';
        }
        
        //filterRecord.set('value', filterData.filters);
        this.setRelatedRecordValue(filterRecord);
    },
    
    setRelatedRecordValue: function(filterRecord) {
        
        if (filterRecord.data.filters) {
            filterRecord.set('value', filterRecord.data.filters);
            delete filterRecord.data.filters;
        }
        
        Tine.widgets.grid.OwnRecordFilter.superclass.setRelatedRecordValue.apply(this, arguments);
    }
});

Tine.widgets.grid.FilterToolbar.FILTERS['ownrecord'] = Tine.widgets.grid.OwnRecordFilter;/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

/**
 * quickadd grid panel
 * 
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.QuickaddGridPanel
 * @extends     Ext.ux.grid.QuickaddGridPanel
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * @param       {Object} config
 * @constructor
 * Create a new Tine.widgets.grid.QuickaddGridPanel
 */
Tine.widgets.grid.QuickaddGridPanel = Ext.extend(Ext.ux.grid.QuickaddGridPanel, {
    /**
     * @property recordClass
     */
    recordClass: null,
    dataField: null,
    
    /**
     * useBBar 
     * @config Boolean
     */
    useBBar: false,
    
    /**
     * @private
     */
    clicksToEdit:'auto',
    frame: true,

    /**
     * @private
     */
    initComponent: function() {
        this.initGrid();
        this.initActions();
        
        this.on('rowcontextmenu', this.onRowContextMenu, this);
        if (! this.store) {
            // create basic store
            this.store = new Ext.data.Store({
                // explicitly create reader
                reader: new Ext.data.ArrayReader({
                        idIndex: 0  // id for each record will be the first element
                    },
                    this.recordClass
                )
            });
        }

        Tine.widgets.grid.QuickaddGridPanel.superclass.initComponent.call(this);
        
        this.on('newentry', this.onNewentry, this);
    },

    /**
     * init grid
     */
    initGrid: function() {
        this.enableHdMenu = false;
        this.plugins = this.plugins || [];
        this.plugins.push(new Ext.ux.grid.GridViewMenuPlugin({}));
        
        this.sm = new Ext.grid.RowSelectionModel();
        this.sm.on('selectionchange', function(sm) {
            var rowCount = sm.getCount();
            this.deleteAction.setDisabled(rowCount == 0);
        }, this);
        
        this.cm = (! this.cm) ? this.getColumnModel() : this.cm;
    },
    
    /**
     * @private
     */
    initActions: function() {
        this.deleteAction = new Ext.Action({
            text: _('Remove'),
            iconCls: 'actionDelete',
            handler : this.onDelete,
            scope: this,
            disabled: true
        });
        
        if (this.useBBar) {
            this.bbar = [this.deleteAction];
        }
    },
    
    /**
     * get column model
     * 
     * @return {Ext.grid.ColumnModel}
     */
    getColumnModel: function() {
        return new Ext.grid.ColumnModel([]);
    },
    
    /**
     * new entry event -> add new record to store
     * 
     * @param {Object} recordData
     * @return {Boolean}
     */
    onNewentry: function(recordData) {
        var initialData = null;
        if (Ext.isFunction(this.recordClass.getDefaultData)) {
            initialData = Ext.apply(this.recordClass.getDefaultData(), recordData);
        } else {
            initialData = recordData;
        }
        var newRecord = new this.recordClass(initialData, Ext.id());
        this.store.insert(0 , [newRecord]);
        
        return true;
    },
    
    /**
     * delete event
     */
    onDelete: function() {
        var selectedRows = this.getSelectionModel().getSelections();
        for (var i = 0; i < selectedRows.length; ++i) {
            this.store.remove(selectedRows[i]);
        }
    },
    
    onRowContextMenu: function(grid, row, e) {
        e.stopEvent();
        var selModel = grid.getSelectionModel();
        if(!selModel.isSelected(row)) {
            // disable preview update if config option is set to false
            this.updateOnSelectionChange = this.updateDetailsPanelOnCtxMenu;
            selModel.selectRow(row);
        }

        this.getContextMenu().showAt(e.getXY());
        // reset preview update
        this.updateOnSelectionChange = true;
    },
    
    getContextMenu: function() {
        if (! this.contextMenu) {
            var items = [this.deleteAction];
            
            this.contextMenu = new Ext.menu.Menu({
                items: items/*,
                plugins: [{
                    ptype: 'ux.itemregistry',
                    key:   this.app.appName + '-GridPanel-ContextMenu'
                }]*/
            });
        }
        
        return this.contextMenu;
    },
    
    /**
     * get next available id
     * @return {Number}
     */
    getNextId: function() {
        var newid = this.store.getCount() + 1;
        
        while (this.store.getById(newid)) {
            newid++;
        }
        
        return newid;
    },

    /**
     * get values from store (as array)
     * 
     * @param {Array}
     * 
     * TODO improve this
     */
    setStoreFromArray: function(data) {
        for (var i = 0; i < data.length; ++i) {
            if (this.dataField === null) {
                var recordData = data[i];
            } else {
                var recordData = {};
                recordData[this.dataField] = data[i];
            }
            
            this.store.insert(0, new this.recordClass(recordData));
        }        
    },
    
    /**
     * get values from store (as array)
     * 
     * @return {Array}
     */
    getFromStoreAsArray: function() {
        var result = [];
        this.store.each(function(record) {
            result.push((this.dataField === null) ? record.data : record.get(this.dataField));
        }, this);
        //store.commitChanges();
        
        return result;
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 * @version     $Id
 */

/*global Ext, Tine*/
 
Ext.ns('Tine.widgets.grid');

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FileUploadGrid
 * @extends     Ext.grid.GridPanel
 * 
 * <p>FileUpload grid for dialogs</p>
 * <p>
 * </p>
 * 
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * 
 * @param       {Object} config
 * 
 * @constructor Create a new  Tine.widgets.grid.FileUploadGrid
 */
Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
    
    /**
     * @private
     */
    id: 'tinebase-file-grid',
    
    /**
     * @cfg filesProperty
     * @type String
     */
    filesProperty: 'files',
    
    /**
     * @cfg showTopToolbar
     * @type Boolean
     * TODO     think about that -> when we deactivate the top toolbar, we lose the dropzone for files!
     */
    //showTopToolbar: null,
    
    /**
     * config values
     * @private
     */
    header: false,
    border: false,
    deferredRender: false,
    autoExpandColumn: 'name',
    showProgress: true,
    
    /**
     * init
     * @private
     */
    initComponent: function () {
        
        this.record = this.record || null;
        this.id = this.id + Ext.id();
        
     // init actions
        this.actionUpdater = new Tine.widgets.ActionUpdater({
            containerProperty: 'container_id', 
            evalGrants: false
        });
        
        
        this.initToolbarAndContextMenu();
        this.initStore();
        this.initColumnModel();
        this.initSelectionModel();
        
    
        this.plugins = [ new Ext.ux.grid.GridViewMenuPlugin({}) ];
        this.enableHdMenu = false;
      
        Tine.widgets.grid.FileUploadGrid.superclass.initComponent.call(this);
        
        this.on('rowcontextmenu', function (grid, row, e) {
            e.stopEvent();
            var selModel = grid.getSelectionModel();
            if (! selModel.isSelected(row)) {
                selModel.selectRow(row);
            }
            this.contextMenu.showAt(e.getXY());
        }, this);
        
               
    },
    
    /**
     * on upload failure
     * @private
     */
    onUploadFail: function (uploader, fileRecord) {
        
        var dataSize;
        if (fileRecord.html5upload) {
            dataSize = dataSize = Tine.Tinebase.registry.get('maxPostSize');
        }
        else {
            dataSize = Tine.Tinebase.registry.get('maxFileUploadSize');
        }
        
        Ext.MessageBox.alert(
            _('Upload Failed'), 
            _('Could not upload file. Filesize could be too big. Please notify your Administrator. Max upload size: ') + parseInt(dataSize, 10) / 1048576 + ' MB'
        ).setIcon(Ext.MessageBox.ERROR);
        
        this.getStore().remove(fileRecord);
        if(this.loadMask) this.loadMask.hide();
    },
    
    /**
     * on remove
     * @param {} button
     * @param {} event
     */
    onRemove: function (button, event) {
        
        var selectedRows = this.getSelectionModel().getSelections();
        for (var i = 0; i < selectedRows.length; i += 1) {
            this.store.remove(selectedRows[i]);
            var upload = Tine.Tinebase.uploadManager.getUpload(selectedRows[i].get('uploadKey'));
            upload.setPaused(true);
        }
    },
    
    
    /**
     * on pause
     * @param {} button
     * @param {} event
     */
    onPause: function (button, event) {
 
        var selectedRows = this.getSelectionModel().getSelections();
        for(var i=0; i < selectedRows.length; i++) {
            var upload = Tine.Tinebase.uploadManager.getUpload(selectedRows[i].get('uploadKey'));
            upload.setPaused(true);
        }       
        this.getSelectionModel().deselectRange(0, this.getSelectionModel().getCount());
    },

    
    /**
     * on resume
     * @param {} button
     * @param {} event
     */
    onResume: function (button, event) {

        var selectedRows = this.getSelectionModel().getSelections();
        for(var i=0; i < selectedRows.length; i++) {
            var upload = Tine.Tinebase.uploadManager.getUpload(selectedRows[i].get('uploadKey'));
            upload.resumeUpload();
        }
        this.getSelectionModel().deselectRange(0, this.getSelectionModel().getCount());
    },

    
    /**
     * init toolbar and context menu
     * @private
     */
    initToolbarAndContextMenu: function () {
        
        this.action_add = new Ext.Action(this.getAddAction());

        this.action_remove = new Ext.Action({
            text: _('Remove file'),
            iconCls: 'action_remove',
            scope: this,
            disabled: true,
            handler: this.onRemove
        });
        
        this.action_pause = new Ext.Action({
            text: _('Pause upload'),
            iconCls: 'action_pause',
            scope: this,
//            disabled: true,
            handler: this.onPause,
            actionUpdater: this.isPauseEnabled
        });
        
        this.action_resume = new Ext.Action({
            text: _('Resume upload'),
            iconCls: 'action_resume',
            scope: this,
//            disabled: true,
            handler: this.onResume,
            actionUpdater: this.isResumeEnabled
        });
        
        this.tbar = [
            this.action_add,
            this.action_remove
        ];
        
        this.contextMenu = new Ext.menu.Menu({
            items:  [
                     this.action_remove,
                     this.action_pause,
                     this.action_resume
            ]
        });
        
        this.actionUpdater.addActions([
            this.action_pause,
            this.action_resume                                      
        ]);

    },
    
    /**
     * init store
     * @private
     */
    initStore: function () {
        this.store = new Ext.data.SimpleStore({
            fields: Ext.ux.file.Upload.file
        });
        
        this.loadRecord(this.record);
    },
    
    /**
     * returns add action
     * 
     * @return {Object} add action config
     */
    getAddAction: function () {
        return {
            text: _('Add file'),
            iconCls: 'action_add',
            scope: this,
            plugins: [{
                ptype: 'ux.browseplugin',
                multiple: true,
                dropElSelector: 'div[id=' + this.id + ']'
            }],
            handler: this.onFilesSelect
        };
    },
    
    /**
     * populate grid store
     * 
     * @param {} record
     */
    loadRecord: function (record) {
        if (record && record.get(this.filesProperty)) {
            var files = record.get(this.filesProperty);
            for (var i = 0; i < files.length; i += 1) {
                var file = new Ext.ux.file.Upload.file(files[i]);
                file.data.status = 'complete';
                this.store.add(file);
            }
        }
    },
    
    /**
     * init cm
     * @private
     */
    initColumnModel: function () {
        this.cm = new Ext.grid.ColumnModel(this.getColumns());
    },
    
    getColumns: function() {
        var columns = [{
            resizable: true,
            id: 'name',
            dataIndex: 'name',
            width: 300,
            header: _('name'),
            renderer: Ext.ux.PercentRendererWithName
        }, {
            resizable: true,
            id: 'size',
            dataIndex: 'size',
            width: 70,
            header: _('size'),
            renderer: Ext.util.Format.fileSize
        }, {
            resizable: true,
            id: 'type',
            dataIndex: 'type',
            width: 70,
            header: _('type')
            // TODO show type icon?
            //renderer: Ext.util.Format.fileSize
        }];
        
        return columns;
    },

    /**
     * init sel model
     * @private
     */
    initSelectionModel: function () {
        this.selModel = new Ext.grid.RowSelectionModel({multiSelect: true});
        
        this.selModel.on('selectionchange', function (selModel) {
            var rowCount = selModel.getCount();
            this.action_remove.setDisabled(rowCount === 0);
            this.actionUpdater.updateActions(selModel);

        }, this);
    },
    
    /**
     * upload new file and add to store
     * 
     * @param {} btn
     * @param {} e
     */
    onFilesSelect: function (fileSelector, e) {
        
        var files = fileSelector.getFileList();
        Ext.each(files, function (file) {

            var upload = new Ext.ux.file.Upload({
                file: file,
                fileSelector: fileSelector
            });

            var uploadKey = Tine.Tinebase.uploadManager.queueUpload(upload);
            var fileRecord = Tine.Tinebase.uploadManager.upload(uploadKey);
            
            upload.on('uploadfailure', this.onUploadFail, this);
            upload.on('uploadcomplete', this.onUploadComplete, fileRecord);
            upload.on('uploadstart', Tine.Tinebase.uploadManager.onUploadStart, this);

            if(fileRecord.get('status') !== 'failure' ) {
                this.store.add(fileRecord);
            }

            
        }, this);
        
    },
    
    onUploadComplete: function(upload, fileRecord) {
    
        fileRecord.beginEdit();
        fileRecord.set('status', 'complete');
        fileRecord.set('progress', 100);
        fileRecord.commit(false);
        Tine.Tinebase.uploadManager.onUploadComplete();
    },
    
    /**
     * returns true if files are uploading atm
     * 
     * @return {Boolean}
     */
    isUploading: function () {
        var uploadingFiles = this.store.query('status', 'uploading');
        return (uploadingFiles.getCount() > 0);
    },
    
    isPauseEnabled: function(action, grants, records) {
         
        for(var i=0; i<records.length; i++) {
            if(records[i].get('type') === 'folder') {
                action.hide();
                return;
            }
        }
        
        for(var i=0; i < records.length; i++) {
            if(!records[i].get('status') || (records[i].get('type ') !== 'folder' && records[i].get('status') !== 'paused'
                    &&  records[i].get('status') !== 'uploading' && records[i].get('status') !== 'pending')) {
                action.hide();
                return;
            }
        }
        
        action.show();
        
        for(var i=0; i < records.length; i++) {
            if(records[i].get('status')) {
                action.setDisabled(false);
            }
            else {
                action.setDisabled(true);
            }
            if(records[i].get('status') && records[i].get('status') !== 'uploading'){
                action.setDisabled(true);
            }
            
        }             
    },

    isResumeEnabled: function(action, grants, records) {
        for(var i=0; i<records.length; i++) {
            if(records[i].get('type') === 'folder') {
                action.hide();
                return;
            }
        }
       
        for(var i=0; i < records.length; i++) {
            if(!records[i].get('status') || (records[i].get('type ') !== 'folder' &&  records[i].get('status') !== 'uploading' 
                    &&  records[i].get('status') !== 'paused' && records[i].get('status') !== 'pending')) {
                action.hide();
                return;
            }
        }
        
        action.show();
        
        for(var i=0; i < records.length; i++) {
            if(records[i].get('status')) {
                action.setDisabled(false);
            }
            else {
                action.setDisabled(true);
            }
            if(records[i].get('status') && records[i].get('status') !== 'paused'){
                action.setDisabled(true);
            }
            
        }   
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

/**
 * Picker GridPanel
 * 
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.PickerGridPanel
 * @extends     Ext.grid.GridPanel
 * 
 * <p>Picker GridPanel</p>
 * <p><pre>
 * </pre></p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * @param       {Object} config
 * @constructor
 * Create a new Tine.widgets.grid.PickerGridPanel
 */
Tine.widgets.grid.PickerGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
    /**
     * @cfg {bool}
     * enable bottom toolbar
     */
    enableBbar: true,

    /**
     * @cfg {bool}
     * enable top toolbar (with search combo)
     */
    enableTbar: true,
    
    /**
     * store to hold records
     * 
     * @type Ext.data.Store
     * @property store
     */
    store: null,
    
    /**
     * record class
     * @cfg {Tine.Tinebase.data.Record} recordClass
     */
    recordClass: null,
    
    /**
     * defaults for new records of this.recordClass
     * @cfg {Object} recordClass
     */
    recordDefaults: null,
    
    /**
     * record class
     * @cfg {Tine.Tinebase.data.Record} recordClass
     */
    searchRecordClass: null,
    
    /**
     * search combo class
     * @cfg {} searchComboClass
     */
    searchComboClass: null,
    
    /**
     * search combo config
     * @cfg {} searchComboConfig
     */
    searchComboConfig: null,
    
    /**
     * is the row selected after adding?
     * @type Boolean
     */
    selectRowAfterAdd: true,
    
    /**
     * is the row highlighted after adding?
     * @type Boolean
     */
    highlightRowAfterAdd: false,
    
    /**
     * @type Ext.Menu
     * @property contextMenu
     */
    contextMenu: null,
    
    /**
     * @cfg {Array} contextMenuItems
     * additional items for contextMenu
     */
    contextMenuItems: null,
    
    /**
     * @cfg {Array} Array of column's config objects where the config options are in
     */
    configColumns: null,
    
    /**
     * @private
     */
    initComponent: function() {
        this.contextMenuItems = (this.contextMenuItems !== null) ? this.contextMenuItems : [];
        this.configColumns = (this.configColumns !== null) ? this.configColumns : [];
        this.searchComboConfig = this.searchComboConfig || {};
        
        this.initStore();
        this.initActionsAndToolbars();
        this.initGrid();
        
        Tine.widgets.grid.PickerGridPanel.superclass.initComponent.call(this);
    },

    /**
     * init store
     * @private
     */
    initStore: function() {
        
        if (this.store === null) {
            this.store = new Ext.data.SimpleStore({
                fields: this.recordClass
            });
        }
        
        // focus+select new record
        this.store.on('add', function(store, records, index) {
            (function() {
                if (this.rendered) {
                    if (this.selectRowAfterAdd) {
                        this.getView().focusRow(index);
                        this.getSelectionModel().selectRow(index);
                    } else if (this.highlightRowAfterAdd && records.length === 1){
                        // some eyecandy
                        var row = this.getView().getRow(index);
                        Ext.fly(row).highlight();
                    }
                }
            }).defer(300, this);
        }, this);
    },

    /**
     * init actions and toolbars
     */
    initActionsAndToolbars: function() {
        
        this.actionRemove = new Ext.Action({
            text: _('Remove record'),
            disabled: true,
            scope: this,
            handler: this.onRemove,
            iconCls: 'action_deleteContact'
        });
        
        var contextItems = [this.actionRemove];
        this.contextMenu = new Ext.menu.Menu({
            items: contextItems.concat(this.contextMenuItems)
        });
        
        // removes temporarly added items
        this.contextMenu.on('hide', function() {
            if(this.contextMenu.hasOwnProperty('tempItems') && this.contextMenu.tempItems.length) {
                Ext.each(this.contextMenu.tempItems, function(item) {
                    this.contextMenu.remove(item.itemId);
                }, this);
            }
            this.contextMenu.tempItems = [];
        }, this);
        
        if (this.enableBbar) {
            this.bbar = new Ext.Toolbar({
                items: [
                    this.actionRemove
                ].concat(this.contextMenuItems)
            });
        }

        if (this.enableTbar) {
            this.initTbar();
        }
    },
    
    /**
     * init top toolbar
     */
    initTbar: function() {
        this.tbar = new Ext.Toolbar({
            items: [
                this.getSearchCombo()
            ],
            listeners: {
                scope: this,
                resize: this.onTbarResize
            }
        });
    },
    
    onTbarResize: function(tbar) {
        if (tbar.items.getCount() == 1) {
            var combo = tbar.items.get(0),
                gridWidth = this.getGridEl().getWidth(),
                offsetWidth = combo.getEl().getLeft() - this.getGridEl().getLeft();
            
            if (tbar.items.getCount() == 1) {
                tbar.items.get(0).setWidth(gridWidth - offsetWidth);
            }
        }
    },
    
    /**
     * init grid (column/selection model, ctx menu, ...)
     */
    initGrid: function() {
        this.cm = this.getColumnModel();
        
        this.selModel = new Ext.grid.RowSelectionModel({multiSelect:true});
        
        // remove non-plugin config columns
        var nonPluginColumns = [];
        for (var i=0; i < this.configColumns.length; i++) {
            if (!this.configColumns[i].init || typeof(this.configColumns[i].init) != 'function') {
                nonPluginColumns.push(this.configColumns[i]);
            }
        }
        for (var i=0; i < nonPluginColumns.length; i++) {
            this.configColumns.remove(nonPluginColumns[i]);
        }
        this.plugins = this.configColumns;
    
        // on selectionchange handler
        this.selModel.on('selectionchange', function(sm) {
            var rowCount = sm.getCount();
            this.actionRemove.setDisabled(rowCount == 0);
        }, this);
        
        // on rowcontextmenu handler
        this.on('rowcontextmenu', this.onRowContextMenu.createDelegate(this), this);
    },
    /**
     * that's the context menu handler
     * @param {} grid
     * @param {} row
     * @param {} e
     */
    onRowContextMenu: function(grid, row, e) {
        e.stopEvent();
        
        this.fireEvent('beforecontextmenu', grid, row, e);
        
        var selModel = grid.getSelectionModel();
        if(!selModel.isSelected(row)) {
            selModel.selectRow(row);
        }
        
        this.contextMenu.showAt(e.getXY());
    },
    
    /**
     * @return {Tine.Tinebase.widgets.form.RecordPickerComboBox|this.searchComboClass}
     */
    getSearchCombo: function() {
        var searchComboClass = (this.searchComboClass !== null) ? this.searchComboClass : Tine.Tinebase.widgets.form.RecordPickerComboBox;
        
        return new searchComboClass(Ext.apply({
            recordStore: this.store,
            blurOnSelect: true,
            recordClass: (this.searchRecordClass !== null) ? this.searchRecordClass : this.recordClass,
            newRecordClass: this.recordClass,
            newRecordDefaults: this.recordDefaults,
            emptyText: _('Search for records ...'),
            onSelect: this.onAddRecordFromCombo
        }, this.searchComboConfig));
    },
    
    /**
     * @param {Record} recordToAdd
     * 
     * TODO make reset work correctly -> show emptyText again
     */
    onAddRecordFromCombo: function(recordToAdd) {
        var record = new this.newRecordClass(Ext.applyIf(recordToAdd.data, this.newRecordDefaults || {}), recordToAdd.id);
        
        // check if already in
        if (! this.recordStore.getById(record.id)) {
            this.recordStore.add([record]);
        }
        this.collapse();
        this.clearValue();
        this.reset();
    },
    
    /**
     * remove handler
     * 
     * @param {} button
     * @param {} event
     */
    onRemove: function(button, event) {
        var selectedRows = this.getSelectionModel().getSelections();
        for (var i = 0; i < selectedRows.length; ++i) {
            this.store.remove(selectedRows[i]);
        }           
    },
    
    /**
     * key down handler
     * @private
     */
    onKeyDown: function(e){
        if (e.ctrlKey) {
            switch (e.getKey()) {
                case e.A:
                    // select all records
                    this.getSelectionModel().selectAll(true);
                    e.preventDefault();
                    break;
            }
        } else {
            switch (e.getKey()) {
                case e.DELETE:
                    // delete selected record(s)
                    this.onRemove();
                    break;
            }
        }
    }
});

Ext.reg('wdgt.pickergrid', Tine.widgets.grid.PickerGridPanel);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

/**
 * Link GridPanel
 * 
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.LinkGridPanel
 * @extends     Tine.widgets.grid.PickerGridPanel
 * 
 * <p>Link GridPanel</p>
 * <p><pre>
 * </pre></p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * 
 * @param       {Object} config
 * @constructor
 * Create a new Tine.widgets.grid.LinkGridPanel
 */
Tine.widgets.grid.LinkGridPanel = Ext.extend(Tine.widgets.grid.PickerGridPanel, {
    
    /**
     * @cfg for PickerGridPanel
     */
    recordClass: Tine.Tinebase.Model.Relation,
    autoExpandColumn: 'name',
    enableTbar: true,
    clicksToEdit: 1,
    selectRowAfterAdd: false,

    /**
     * the record
     * @type Record 
     */
    record: null,
    
    /**
     * relation types can be stored in keyfield
     * @type 
     */
    relationTypesKeyfieldName: null,
    
    /**
     * relation types for combobox in editor grid
     * @type Array
     */
    relationTypes: null,
    
    /**
     * default relation type
     * @type String
     */
    defaultType: '',
    
    /**
     * @type Tinebase.Application 
     */
    app: null,
    
    typeColumnHeader: null,
    
    /**
     * @return Ext.grid.ColumnModel
     * @private
     */
    getColumnModel: function () {
        
        this.typeEditor = (this.relationTypesKeyfieldName) 
            ? new Tine.Tinebase.widgets.keyfield.ComboBox({
                app: this.app.appName,
                keyFieldName: this.relationTypesKeyfieldName
            }) 
            : (this.relationTypes) 
                ? new Ext.form.ComboBox({
                    displayField: 'label',
                    valueField: 'relation_type',
                    mode: 'local',
                    triggerAction: 'all',
                    lazyInit: false,
                    autoExpand: true,
                    blurOnSelect: true,
                    listClass: 'x-combo-list-small',
                    store: new Ext.data.SimpleStore({
                        fields: ['label', 'relation_type'],
                        data: this.relationTypes
                    })
                }) 
                : null;
        
        return new Ext.grid.ColumnModel({
            defaults: {
                sortable: true
            },
            columns:  [
                {   id: 'name', header: _('Name'), dataIndex: 'related_record', renderer: this.relatedRecordRender, scope: this}, {
                    id: 'type', 
                    header: (this.typeColumnHeader) ? this.typeColumnHeader : _('Type'), 
                    dataIndex: 'type', 
                    width: 100, 
                    sortable: true,
                    renderer: (this.relationTypesKeyfieldName) 
                        ? Tine.Tinebase.widgets.keyfield.Renderer.get(this.app.appName, this.relationTypesKeyfieldName) 
                        : this.relationTypeRenderer,
                    scope: this,
                    editor: this.typeEditor
                }
            ].concat(this.configColumns)
        });
    },
    
    /**
     * related record renderer
     * 
     * @param {Record} value
     * @return {String}
     */
    relatedRecordRender: function(value) {
        var result = '';
        
        if (value) {
            result = Ext.util.Format.htmlEncode(value.get(this.searchRecordClass.getMeta('titleProperty')));
        }
        
        return result;
    },
    
    /**
     * type renderer
     * 
     * @param {String} value
     * @return {String}
     */
    relationTypeRenderer: function(value) {
        var result = '';
        
        if (value) {
            result = this.app.i18n._(Ext.util.Format.capitalize(value));
        }
        
        return result;
    },
    
    /**
     * @param {Record} recordToAdd
     */
    onAddRecordFromCombo: function(recordToAdd) {
        var record = new Tine.Tinebase.Model.Relation(Ext.apply({
            related_record: new this.newRecordClass(recordToAdd.data, recordToAdd.id),
            related_id: recordToAdd.id,
            own_id: (this.record) ? this.record.id : null
        }, this.relationDefaults), recordToAdd.id);
        
        Tine.log.debug('Adding new relation:');
        Tine.log.debug(record);
        
        // check if already in
        if (this.recordStore.findExact('related_id', recordToAdd.id) === -1) {
            this.recordStore.add([record]);
        }
        this.collapse();
        this.clearValue();
        this.reset();
    },
    
    /**
     * populate store and set record
     * 
     * @param {Record} record
     */
    onRecordLoad: function(record) {
        if (this.record) {
            return;
        }
        
        this.record = record;

        Tine.log.debug('Loading relations into store...');
        if (record.get('relations') && record.get('relations').length > 0) {
            var relationRecords = [];
            Ext.each(record.get('relations'), function(relation) {
                relation.related_record = new this.searchRecordClass(
                    relation.related_record, 
                    relation.related_id
                );
                relationRecords.push(new Tine.Tinebase.Model.Relation(relation, relation.id));
                
            }, this);
            this.store.add(relationRecords);
        }
        
        // activate highlighting after initial load
        (function() {
            this.highlightRowAfterAdd = true;
        }).defer(400, this);
        
        // TODO perhaps we should filter all that do not match the model
    },
    
    /**
     * get relations data as array
     * 
     * @return {Array}
     */
    getData: function() {
        var relations = [];
        this.store.each(function(record) {
            record.data.related_record = record.data.related_record.data;
            relations.push(record.data);
        }, this);
        
        return relations;
    }
});

Ext.reg('wdgt.linkgrid', Tine.widgets.grid.LinkGridPanel);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * TODO         this is only used by the Felamimail flag filter atm, we should try to replace this / generalize it
 *              @see Tine.widgets.grid.PickerFilter
 */
Ext.ns('Tine.widgets.grid');

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterModelMultiSelect
 * @extends     Tine.widgets.grid.FilterModel
 * 
 * @author      Philipp Schuele <p.schuele@metaways.de>
 */
Tine.widgets.grid.FilterModelMultiSelect = Ext.extend(Tine.widgets.grid.FilterModel, {
    /**
     * @property Tine.Tinebase.Application app
     */
    app: null,
    
    /**
     * @cfg field
     * @type String
     */
    field: '',

    /**
     * @cfg defaultOperator
     * @type String
     */
    defaultOperator: 'in',

    /**
     * @cfg defaultValue
     * @type String
     */
    defaultValue: '',

    /**
     * @cfg label
     * @type String
     */
    label: '',

    /**
     * @cfg filterValueWidth
     * @type Integer
     */
    filterValueWidth: 200,

    /**
     * @cfg multiselectField
     */
    multiselectFieldConfig: null,
    
    /**
     * @private
     */
    initComponent: function() {
        this.operators = this.operators || ['in', 'notin'];
        this.multiselectFieldConfig = this.multiselectFieldConfig || {};
        
        Tine.widgets.grid.FilterModelMultiSelect.superclass.initComponent.call(this);
    },
    
    /**
     * value renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    valueRenderer: function(filter, el) {
        var value = new Tine.widgets.grid.FilterModelMultiSelectValueField(Ext.apply({
            app: this.app,
            filter: filter,
            width: this.filterValueWidth,
            id: 'tw-ftb-frow-valuefield-' + filter.id,
            value: filter.data.value ? filter.data.value : this.defaultValue,
            renderTo: el
        }, this.multiselectFieldConfig));
        value.on('specialkey', function(field, e){
             if(e.getKey() == e.ENTER){
                 this.onFiltertrigger();
             }
        }, this);
        value.on('select', this.onFiltertrigger, this);
        
        return value;
    }
});

Tine.widgets.grid.FilterToolbar.FILTERS['tinebase.multiselect'] = Tine.widgets.grid.FilterModelMultiSelect;

/**
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.FilterModelMultiSelectValueField
 * @extends     Ext.ux.form.LayerCombo
 * 
 * @author      Philipp Schuele <p.schuele@metaways.de>
 */
Tine.widgets.grid.FilterModelMultiSelectValueField = Ext.extend(Ext.ux.form.LayerCombo, {
    hideButtons: false,
    formConfig: {
        labelAlign: 'left',
        labelWidth: 30
    },
    labelField: 'name',
    xtype: 'checkbox',
    recordClass: null,
    valueStore: null,

    selectionWidget: null,
    labelRenderer: Ext.emptyFn,
    
    /**
     * init
     */
    initComponent: function() {
        this.on('beforecollapse', this.onBeforeCollapse, this);
        if (! this.store) {
            this.store = new Ext.data.SimpleStore({
                fields: this.recordClass
            });
        }
        
        Tine.widgets.grid.FilterModelMultiSelectValueField.superclass.initComponent.call(this);
    },
    
    /**
     * get form values
     * 
     * @return {Array}
     */
    getFormValue: function() {
        var values = [];

        if (this.xtype == 'checkbox') {
            var formValues = this.getInnerForm().getForm().getValues();
            for (var id in formValues) {
                if (formValues[id] === 'on' && this.valueStore.getById(id)) {
                    values.push(id);
                }
            }
        } else {
            this.store.each(function(record) {
                values.push(record.data);
            }, this);            
        }
        
        return values;
    },
    
    /**
     * get items
     * 
     * @return {Array}
     */
    getItems: function() {
        var items = [];

        if (this.xtype == 'wdgt.pickergrid') {
            this.initSelectionWidget();
            
            // defeat scoping :)
            selectionWidget = this.selectionWidget;
            
            this.pickerGridPanel = new Tine.widgets.grid.PickerGridPanel({
                height: this.layerHeight || 'auto',
                recordClass: this.recordClass,
                store: this.store,
                autoExpandColumn: this.labelField,
                getColumnModel: this.getColumnModel.createDelegate(this),
                initActionsAndToolbars: function() {
                    Tine.widgets.grid.PickerGridPanel.prototype.initActionsAndToolbars.call(this);
                    this.tbar = new Ext.Toolbar({
                        layout: 'fit',
                        items: [ selectionWidget ]
                    });
                }
            });
            
            items.push(this.pickerGridPanel);
            
        } else if (this.xtype == 'checkbox') {
            this.valueStore.each(function(record) {
                items.push({
                    xtype: this.xtype,
                    boxLabel: record.get(this.labelField),
                    name: record.get('id')
                    //icon: record.get('status_icon'),
                });
            }, this);
        }
        
        return items;
    },
    
    /**
     * init selection widget
     */
    initSelectionWidget: function() {
        this.selectionWidget.on('select', this.onRecordSelect, this);
    },
    
    /**
     * @return Ext.grid.ColumnModel
     */
    getColumnModel: function() {
        var labelColumn = {id: this.labelField, header: String.format(_('Selected  {0}'), this.recordClass.getMeta('recordsName')), dataIndex: this.labelField};
        if (this.labelRenderer != Ext.emptyFn) {
            labelColumn.renderer = this.labelRenderer;
        }
        
        return new Ext.grid.ColumnModel({
            defaults: {
                sortable: false
            },
            columns:  [ labelColumn ]
        });
    },
    
    /**
     * record select
     * 
     * @param {String} field
     * @param {Object} recordData
     */
    onRecordSelect: function(field, recordData) {
        this.addRecord(recordData);        
        this.selectionWidget.clearValue();
    },
    
    /**
     * adds record from selection widget to store
     * 
     * @param {Object} recordData
     */
    addRecord: function(recordData) {
        var data = (recordData.data) ? recordData.data : recordData.attributes ? recordData.attributes : recordData;
        
        var existingRecord = this.store.getById(recordData.id);
        if (! existingRecord) {
            
            this.store.add(new Tine.Tinebase.Model.Container(data));
            
        } else {
            var idx = this.store.indexOf(existingRecord);
            var row = this.pickerGridPanel.getView().getRow(idx);
            Ext.fly(row).highlight();
        }
        
        this.selectionWidget.selectPanel.close();
    },
    
    /**
     * @param {String} value
     * @return {Ext.form.Field} this
     */
    setValue: function(value) {
        value = Ext.isArray(value) ? value : [value];
        
        var recordText = [];
        this.currentValue = [];
        
        if (this.xtype == 'checkbox') {
            this.valueStore.each(function(record) {
                var id = record.get('id');
                var name = record.get(this.labelField);
                Ext.each(value, function(valueId) {
                    // NOTE: no type match id's might be int or string and should match anyway!
                    if (valueId == id) {
                        recordText.push(name);
                        this.currentValue.push(id);
                    }
                }, this);
            }, this);
        } else {
            this.store.removeAll();
            var record, id, text;
            for (var i=0; i < value.length; i++) {
                text = this.getRecordText(value[i]);
                if (text && text !== '') {
                    recordText.push(text);
                }
            }
        }
        
        this.setRawValue(recordText.join(', '));
        
        return this;
    },
    
    /**
     * get text from record defined by value (id or something else)
     * 
     * @param {String|Object} value
     * @return {String}
     */
    getRecordText: function(value) {
        var text = '';
        
        id = (Ext.isString(value)) ? value : value.id;
        record = this.valueStore.getById(id);
        if (record) {
            this.currentValue.push(record.id);
            // always copy/clone record because it can't exist in 2 different stores
            this.store.add(record.copy());
            text = record.get(this.labelField);
            text = (this.labelRenderer != Ext.emptyFn) ? this.labelRenderer(text) : text;
        }
        
        return text;
    },
    
    /**
     * cancel collapse if ctx menu or record selection is shown
     * 
     * @return Boolean
     */
    onBeforeCollapse: function() {
        if (this.pickerGridPanel) {
            var contextMenuVisible = this.pickerGridPanel.contextMenu && ! this.pickerGridPanel.contextMenu.hidden,
                selectionVisible = this.isSelectionVisible();
            
            return ! (contextMenuVisible || selectionVisible);
        } else {
            return true;
        }
    },
    
    /**
     * is selection visible ?
     * - overwrite this when extending to make sure that the selection widget is no longer visible on collapse
     * 
     * @return {Boolean}
     */
    isSelectionVisible: function() {
        return false;
    },
    
    /**
     * sets values to innerForm
     */
    setFormValue: function(value) {
        if (this.xtype == 'checkbox') {
            this.getInnerForm().getForm().items.each(function(item) {
                item.setValue(value.indexOf(item.name) >= 0 ? 'on' : 'off');
            }, this);
        }
    }
});
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine.widgets.relation');

/**
 * @namespace   Tine.widgets.relation
 * @class       Tine.widgets.relation.Manager
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 */
Tine.widgets.relation.Manager = function() {

    var items = {};
    var ignoreApplications = ['Admin', 'Setup', 'Tinebase', 'ActiveSync'];

    return {
        /**
         * returns the relations config
         * @param {String/Tine.Tinebase.Application} app
         * @param {String/Tine.Tinebase.data.Record} recordClass
         * @param {Array} ignoreModels the php model names to ignore (they won't be returned)
         */
        get: function(app, recordClass, ignoreModels) {
            var key = this.getKey(app, recordClass);

            // create if not cached
            if(items[key] === undefined) {
                this.create(recordClass, key);
            }
            
            var allRelations = items[key];
            var usedRelations = [];
            // sort out ignored models
            if (ignoreModels) {
                Ext.each(allRelations, function(relation) {
                    if(ignoreModels.indexOf(relation.relatedApp + '_Model_' + relation.relatedModel) == -1) {
                        usedRelations.push(relation);
                    }
                }, this);
            } else {
                return allRelations;
            }
            
            return usedRelations;
        },

        /**
         * returns the relations config existence
         * @param {String/Tine.Tinebase.Application} app
         * @param {String/Tine.Tinebase.data.Record} recordClass
         * @return {Boolean}
         */
        has: function(app, recordClass) {
            var key = this.getKey(app, recordClass);
            
            // create if not cached
            if(items[key] === undefined) this.create(recordClass, key);
            return items[key] ? true : false;
        },

        /**
         * creates the relations config if found in registry
         * @param Tine.Tinebase.data.Record recordClass
         * @param {String} key
         * @return {Boolean}
         */
        create: function(recordClass, key) {
            var registered = [];
            if(!items[key]) items[key] = [];

            // add generic relations when no config exists
            Tine.Tinebase.data.RecordMgr.each(function(rec) {
                if (Tine.Tinebase.common.hasRight('run', rec.getMeta('appName')) && (ignoreApplications.indexOf(rec.getMeta('appName')) == -1) && (rec.getFieldNames().indexOf('relations') > -1)) {
                    items[key].push({
                        ownModel: recordClass.getMeta('recordName'),
                        relatedApp: rec.getMeta('appName'),
                        relatedModel: rec.getMeta('modelName'),
                        text: rec.getRecordName() + ' (' + rec.getAppName() + ')'
                    });
                }
            });

            // set to false, so not try again
            if(items[key].length == 0) items[key] = false;
        },
        
        /**
         * returns the key (appName + modelName)
         * @param {String/Tine.Tinebase.Application} appName
         * @param {String/Tine.Tinebase.data.Record} modelName
         * @return {String}
         */
        getKey: function(appName, modelName) {
            var appName = Tine.Tinebase.common.resolveApp(appName);
            var modelName = Tine.Tinebase.common.resolveModel(modelName);
            return appName + modelName;
        }
    };

}();/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine.widgets.relation');

/**
 * @namespace   Tine.widgets.relation
 * @class       Tine.widgets.relation.MenuItemManager
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 */
Tine.widgets.relation.MenuItemManager = function() {
    var items = {};
    return {
        /**
         * registers the menuitem. just add the config for this action as item.
         * @param {String} appName
         * @param {String} modelName
         * @param {Object} itemConfig
         */
        register: function(appName, modelName, itemConfig) {
            var key = appName + modelName;
            
            if(!items[key]) {
                items[key] = [];
            }
            
            items[key].push(itemConfig);
        },
        
        /**
         * returns registered menuitems. add runtime config here.
         * @param {String} appName
         * @param {String} modelName
         * @param {Object} config
         * @return {Array}
         */
        get: function(appName, modelName, config) {
            var key = appName + modelName;
            var ret = [];

            if(items[key]) {
                Ext.each(items[key], function(item) {
                    ret.push(new Ext.Action(Ext.apply({}, item, config)));
                });
            }
            return ret;
        }
    }
}();/*
 * Tine 2.0
 *
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.widgets.relation');
/**
 * @namespace   Tine.widgets.relation
 * @class       Tine.widgets.relation.GenericPickerGridPanel
 * @extends     Tine.widgets.grid.PickerGridPanel
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 */
Tine.widgets.relation.GenericPickerGridPanel = Ext.extend(Tine.widgets.grid.PickerGridPanel, {
    /**
     * @cfg for PickerGridPanel
     */
    recordClass: Tine.Tinebase.Model.Relation,
    clicksToEdit: 1,
    selectRowAfterAdd: false,
    
    /**
     * disable Toolbar creation, create a custom one
     * @type Boolean
     */
    enableTbar: false,
    title: null,
    /**
     * the record
     * @type Record
     */
    record: null,
    /**
     *
     * @type {String}
     */
    ownRecordClass: null,
    /**
     * @type Tinebase.Application
     */
    app: null,
    /**
     * The calling EditDialog
     * @type Tine.widgets.dialog.EditDialog
     */
    editDialog: null,
    /* private */
    /**
     * configuration fetched from registry
     * @type {Object}
     * autoconfig
     */
    possibleRelations: null,
    /**
     * Selects the Model to relate to
     * @type {Ext.form.ComboBox} modelCombo
     */
    modelCombo: null,
    /**
     * Array with searchcombos for each model
     * @type {Array}
     */
    searchCombos: null,
    /**
     * The by the modelcombo activated model
     * @type {String}
     */
    activeModel: null,
    /**
     * Array of possible degrees
     * @type {Array} degreeData
     */
    degreeData: null,
    /**
     * keyfieldConfigs shortcuts
     * @type {Object} keyFieldConfigs
     */
    keyFieldConfigs: null,
    /**
     * constrains config
     * @type {Object} constrainsConfig
     */
    constrainsConfig: null,
    /* config */
    frame: true,
    border: true,
    autoScroll: true,
    layout: 'fit',

    /**
     * initializes the component
     */
    initComponent: function() {
        
        this.record = this.editDialog.record,
        this.app = this.editDialog.app;
        this.ownRecordClass = this.editDialog.recordClass,
        
        this.possibleRelations = Tine.widgets.relation.Manager.get(this.app, this.ownRecordClass, this.editDialog.ignoreRelatedModels);
        
        this.initTbar();
        this.viewConfig = {
            getRowClass: this.getViewRowClass,
            enableRowBody: true
            };
        this.actionEditInNewWindow = new Ext.Action({
            text: _('Edit record'),
            disabled: true,
            scope: this,
            handler: this.onEditInNewWindow,
            iconCls: 'action_edit'
        });
        
        this.title = this.i18nTitle = Tine.Tinebase.translation.ngettext('Relation', 'Relations', 50);
        
        Tine.widgets.dialog.MultipleEditDialogPlugin.prototype.registerSkipItem(this);

        this.on('rowdblclick', this.onEditInNewWindow.createDelegate(this), this);
        
        this.on('beforecontextmenu', this.onBeforeContextMenu.createDelegate(this), this);
        
        this.contextMenuItems = [this.actionEditInNewWindow];
        // preparing keyfield and constrains configs
        this.keyFieldConfigs = {};
        this.constrainsConfig = {};
        
        Ext.each(this.app.getRegistry().get('relatableModels'), function(rel) {
            if(rel.ownModel == this.ownRecordClass.getMeta('modelName')) {
                if(rel.keyfieldConfig) {
                    if(rel.keyfieldConfig.from == 'foreign') {
                        this.keyFieldConfigs[rel.relatedApp + rel.relatedModel] = {app: rel.relatedApp, name: rel.keyfieldConfig.name};
                    } else {
                        this.keyFieldConfigs[this.app.name + rel.ownModel] = {app: this.app.name, name: rel.keyfieldConfig.name};
                    }
                }
                if(rel.config) {
                    this.constrainsConfig[rel.relatedApp + rel.relatedModel] = rel.config;
                }
            }
        }, this);

        this.degreeData = [
            ['sibling', _('Sibling')],
            ['parent', _('Parent')],
            ['child', _('Child')]
        ];
        
        this.on('beforeedit', this.onBeforeRowEdit, this);
        this.on('validateedit', this.onValidateRowEdit, this);
        this.on('afteredit', this.onAfterRowEdit, this);
        
        this.editDialog.on('save', this.onSaveRecord, this);
        this.editDialog.on('load', this.loadRecord, this);
        
        Tine.widgets.relation.GenericPickerGridPanel.superclass.initComponent.call(this);
        
        this.selModel.on('selectionchange', function(sm) {
            this.actionEditInNewWindow.setDisabled(sm.getCount() != 1);
        }, this);
        
        this.store.on('add', this.onAdd, this);
    },
    
    onSaveRecord: function(dialog, record, ticket) {
        var interceptor = ticket();

        // update from relationsPanel if any
        if(this.isValid()) {
            if(record.data.hasOwnProperty('relations')) {
                delete record.data.relations;
            }
            record.set('relations', this.getData());
        } else {
            Ext.Msg.alert(_('Relations failure'), _('There are invalid relations. Please check before saving.'));
            return false;
        }
        interceptor();
    },
    
    /**
     * updates the title ot the tab
     * @param {Integer} count
     */
    updateTitle: function(count) {
        count = Ext.isNumber(count) ? count : this.store.getCount();
        this.setTitle((count > 0) ?  this.i18nTitle + ' (' + count + ')' : this.i18nTitle);
    },
    
    /**
     * creates the toolbar
     */
    initTbar: function() {
        var items = [this.getModelCombo(), ' '];
        items = items.concat(this.createSearchCombos());

        this.tbar = new Ext.Toolbar({
            items: items
        });
    },

    /**
     * adds invlid row class to a invalid row and adds the error qtip
     * @param {Tine.Tinebase.data.Record} record
     * @param {Integer} index
     * @param {Object} rowParams
     * @param {Ext.data.store} store
     * @scope this.view
     * @return {String}
     */
    getViewRowClass: function(record, index, rowParams, store) {
        if(this.invalidRowRecords && this.invalidRowRecords.indexOf(record.id) !== -1) {
            var model = record.get('related_model').split('_Model_');
            model = Tine[model[0]].Model[model[1]];
            rowParams.body = '<div style="height: 19px; margin-top: -19px" ext:qtip="' +
                String.format(_('The maximum number of {0} with the type {1} is reached. Please change the type of this relation'), model.getRecordsName(), this.grid.typeRenderer(record.get('type'), null, record))
                + '"></div>';
            return 'tine-editorgrid-row-invalid';
        }
        rowParams.body='';
        return '';
    },
    /**
     * calls the editdialog for the model
     */
    onEditInNewWindow: function() {
        var selected = this.getSelectionModel().getSelected(),
            app = selected.get('related_model').split('_')[0],
            model = selected.get('related_model').split('_')[2],
            ms = Tine.Tinebase.appMgr.get(app).getMainScreen(),
            recordData = selected.get('related_record'),
            record = new Tine[app].Model[model](recordData);

        ms.activeContentType = model;
        var cp = ms.getCenterPanel(model);
        cp.onEditInNewWindow({actionType: 'edit', mode: 'remote'}, record);
    },
    
    /**
     * is called before context menu is shown
     * adds additional menu items from Tine.widgets.relation.MenuItemManager
     * @param {Tine.widgets.grid.PickerGridPanel} grid
     * @param {Integer} index
     * @return {Boolean}
     */
    onBeforeContextMenu: function(grid, index) {
        var record = grid.store.getAt(index),
            rm = record.get('related_model');
            if(!rm) {
                return;
            }
        var model = rm.split('_Model_');
        
        var app = Tine.Tinebase.appMgr.get(model[0]);
        var additionalItems = Tine.widgets.relation.MenuItemManager.get(model[0], model[1], {
            scope: app,
            grid: grid,
            gridIndex: index
        });
        
        Ext.each(additionalItems, function(item) {
            item.setText(app.i18n._(item.getText()));
            this.contextMenu.add(item);
            if(! this.contextMenu.hasOwnProperty('tempItems')) {
                this.contextMenu.tempItems = [];
            }
            this.contextMenu.tempItems.push(item);
        }, this);
    },
    
    /**
     * creates the model combo
     * @return {Ext.form.ComboBox}
     */
    getModelCombo: function() {
        if(!this.modelCombo) {
            var data = [];
            var id = 0;

            Ext.each(this.possibleRelations, function(rel) {
                data.push([id, rel.text, rel.relatedApp, rel.relatedModel]);
                id++;
            }, this);

            this.modelCombo = new Ext.form.ComboBox({
                store: new Ext.data.ArrayStore({
                    fields: ['id', 'text', 'appName', 'modelName'],
                    data: data
                }),

                allowBlank: false,
                forceSelection: true,
                value: data.length > 0 ? data[0][0] : null,
                displayField: 'text',
                valueField: 'id',
                idIndex: 0,
                mode: 'local',
                triggerAction: 'all',
                selectOnFocus: true,
                getActiveData: function() {
                    var rec = this.getStore().getAt(this.getValue());
                    if(rec) {
                        return rec.data;
                    } else {
                        return null;
                    }
                },
                listeners: {
                    scope: this,
                    select: function(combo) {
                        var value = combo.getActiveData();
                        if(value) {
                            this.showSearchCombo(value.appName, value.modelName);
                        }
                    }
                }
            });

        }
        return this.modelCombo;
    },
    /**
     * creates the searchcombos for the models
     * @return {}
     */
    createSearchCombos: function() {
        var sc = [];
        this.searchCombos = {};

        Ext.each(this.possibleRelations, function(rel) {
            var key = rel.relatedApp+rel.relatedModel;
            this.searchCombos[key] = Tine.widgets.form.RecordPickerManager.get(rel.relatedApp, rel.relatedModel,{
                width: 300,
                allowBlank: true,
                listeners: {
                    scope: this,
                    select: this.onAddRecordFromCombo
                }
            });
            sc.push(this.searchCombos[key]);
            this.searchCombos[key].hide();
        }, this);

        this.showSearchCombo(this.possibleRelations[0].relatedApp, this.possibleRelations[0].relatedModel);
        return sc;
    },
    /**
     * shows the active model searchcombo
     * @param {String} appName
     * @param {String} modelName
     */
    showSearchCombo: function(appName, modelName) {
        var key = appName+modelName;
        if(this.activeModel) this.searchCombos[this.activeModel].hide();
        this.searchCombos[key].show();
        this.activeModel = appName+modelName;
    },
    /**
     * returns the active search combo
     * @return {}
     */
    getActiveSearchCombo: function() {
        return this.searchCombos[this.activeModel];
    },

    /**
     * @return Ext.grid.ColumnModel
     * @private
     */
    getColumnModel: function () {
        
        this.degreeEditor = new Ext.form.ComboBox({
            store: new Ext.data.ArrayStore({
                fields: ['id', 'value'],
                data: this.degreeData
            }),
            allowBlank: false,
            displayField: 'value',
            valueField: 'id',
            mode: 'local'
        });

        if(!this.colModel) {
            this.colModel = new Ext.grid.ColumnModel({
                defaults: {
                    sortable: true,
                    width: 180
                },
                columns: [
                    {id: 'related_model', dataIndex: 'related_model', header: _('Record'), editor: false, renderer: this.relatedModelRenderer.createDelegate(this), scope: this},
                    {id: 'related_record', dataIndex: 'related_record', header: _('Description'), renderer: this.relatedRecordRenderer.createDelegate(this), editor: false, scope: this},
                    {id: 'remark', dataIndex: 'remark', header: _('Remark'), renderer: this.remarkRenderer.createDelegate(this), editor: Ext.form.Field, scope: this, width: 120},
                    {id: 'own_degree', hidden: true, dataIndex: 'own_degree', header: _('Dependency'), editor: this.degreeEditor, renderer: this.degreeRenderer.createDelegate(this), scope: this, width: 100},
                    {id: 'type', dataIndex: 'type', renderer: this.typeRenderer, header: _('Type'),  scope: this, width: 120, editor: true},
                    {id: 'creation_time', dataIndex: 'creation_time', editor: false, renderer: Tine.Tinebase.common.dateTimeRenderer, header: _('Creation Time'), width: 140}
                ]
            });
        }
        return this.colModel;
    },
    /**
     * creates the special editors
     * @param {} o
     */
    onBeforeRowEdit: function(o) {
        var model = o.record.get('related_model').split('_');
        var app = model[0];
        model = model[2];
        var colModel = o.grid.getColumnModel();

        switch (o.field) {
            case 'type':
                var editor = null;
                if(this.constrainsConfig[app+model]) {
                    editor = this.getTypeEditor(this.constrainsConfig[app+model]);
                } else if (this.keyFieldConfigs[app+model]) {
                    editor = new Tine.Tinebase.widgets.keyfield.ComboBox({
                        app: app,
                        keyFieldName: this.keyFieldConfigs[app+model].name
                    });
                }
                if (editor) colModel.config[o.column].setEditor(editor);
                else colModel.config[o.column].setEditor(null);
                break;
            default: return;
        }
        if(colModel.config[o.column].editor) colModel.config[o.column].editor.selectedRecord = null;
    },

    /**
     * returns the type editors for each row in the grid
     * @param {} relation
     * @return {}
     */
    getTypeEditor: function(config) {
        var data = [];
        Ext.each(config, function(c){
            data.push([c.type.toUpperCase(), c.text]);
        });
        return new Ext.form.ComboBox({
            store: new Ext.data.ArrayStore({
                fields: ['id', 'value'],
                data: data
            }),
            allowBlank: false,
            displayField: 'value',
            valueField: 'id',
            mode: 'local'
        });
    },

    /**
     * related record renderer
     *
     * @param {Record} value
     * @return {String}
     */
    relatedRecordRenderer: function (recData, meta, relRec) {
        var relm = relRec.get('related_model');
        if(!relm) {
            return '';
        }
        var split = relm.split('_'); 
        var recordClass = Tine[split[0]][split[1]][split[2]];
        var record = new recordClass(recData);
        var result = '';
        if (recData) {
            result = Ext.util.Format.htmlEncode(record.getTitle());
        }
        return result;
    },
    
    /**
     * renders the remark
     * @param {String} value
     * @param {Object} row
     * @param {String} Tine.Tinebase.data.Record
     * @return {String}
     */
    remarkRenderer: function(value, row, record) {
        if(record && record.get('related_model')) {
            var app = Tine.Tinebase.appMgr.get(record.get('related_model').split('_Model_')[0]);
        }
        if(! value) {
            value = '';
        } else if (Ext.isObject(value)) {
            var str = '';
            Ext.iterate(value, function(label, val) {
                str += app.i18n._(Ext.util.Format.capitalize(label)) + ': ' + (Ext.isNumber(val) ? val : Ext.util.Format.capitalize(val)) + ', ';
            }, this);
            value = str.replace(/, $/,'');
        } else if (Ext.isArray(value)) {
            var str = '';
            Ext.each(value, function(val) {
                str += (Ext.isNumber(val) ? val : Ext.util.Format.capitalize(val)) + ', ';
            }, this);
            value = str.replace(/, $/,'');
        } else if(value.match(/^\[.*\]$/)) {
            value = Ext.decode(value);
            return this.remarkRenderer(value);
        }
        
        return Ext.util.Format.htmlEncode(value);
    },
    /**
     * renders the degree
     * @param {String} value
     * @return {String}
     */
    degreeRenderer: function(value) {
        if(!this.degreeDataObject) {
            this.degreeDataObject = {};
            Ext.each(this.degreeData, function(dd) {
                this.degreeDataObject[dd[0]] = dd[1];
            }, this);
        }
        return this.degreeDataObject[value] ? _(this.degreeDataObject[value]) : '';
    },

    /**
     * renders the titleProperty of the models
     * @param {String} value
     * @return {String}
     */
    relatedModelRenderer: function(value) {
        if(!value) {
            return '';
        }
        var split = value.split('_');
        var model = Tine[split[0]][split[1]][split[2]];
        return '<span class="tine-recordclass-gridicon ' + _(model.getMeta('appName')) + model.getMeta('modelName') + '">&nbsp;</span>' + model.getRecordName() + ' (' + model.getAppName() + ')';
    },

    /**
     * renders the type
     * @param {String} value
     * @param {Object} row
     * @param {Tine.Tinebase.data.Record} rec
     * @return {String}
     */
    typeRenderer: function(value, row, rec) {
        if(!rec.get('own_model') || !rec.get('related_model')) {
            return '';
        }
        var o = rec.get('own_model').split('_Model_').join('');
        var f = rec.get('related_model').split('_Model_').join('');

        var renderer = Ext.util.Format.htmlEncode;
        if (this.constrainsConfig[f]) {
            Ext.each(this.constrainsConfig[f], function(c){
                if(c.type == value) value = c.text;
            });
        } else if(this.keyFieldConfigs[o]) {
            renderer = Tine.Tinebase.widgets.keyfield.Renderer.get(this.keyFieldConfigs[o].app, this.keyFieldConfigs[o].name);
        } else if(this.keyFieldConfigs[f]) {
            renderer = Tine.Tinebase.widgets.keyfield.Renderer.get(this.keyFieldConfigs[f].app, this.keyFieldConfigs[f].name);
        }

        return renderer(value);
    },

    /**
     * returns the default relation values
     * @return {Array}
     */
    getRelationDefaults: function() {
        return {
            own_backend: 'Sql',
            related_backend: 'Sql',
            own_id: (this.record) ? this.record.id : null,
            own_model: this.ownRecordClass.getPhpClassName()
        };
    },

    /**
     * is called when selecting a record in the searchCombo (relationpickercombo)
     */
    onAddRecordFromCombo: function() {
        var record = this.getActiveSearchCombo().store.getById(this.getActiveSearchCombo().getValue());
        
        this.onAddRecord(record);
        
        this.getActiveSearchCombo().collapse();
        this.getActiveSearchCombo().reset();
    },

    /**
     * call to add relation from an external component
     * @param {Tine.Tinebase.data.Record} record
     * @param {Object} relconf
     */
    onAddRecord: function(record, relconf) {
        if(record) {
            if(!relconf) {
                relconf = {};
            }
            if(record.data.hasOwnProperty('relations')) {
                delete record.data.relations;
            }
            
            var relationRecord = new Tine.Tinebase.Model.Relation(Ext.apply(this.getRelationDefaults(), Ext.apply({
                related_record: record.data,
                related_id: record.id,
                related_model: this.getActiveSearchCombo().recordClass.getMeta('appName') + '_Model_' + this.getActiveSearchCombo().recordClass.getMeta('modelName'),
                type: '',
                own_degree: 'sibling'
            }, relconf)), record.id);

            // add if not already in
            if (this.store.findExact('related_id', record.id) === -1) {
                Tine.log.debug('Adding new relation:');
                Tine.log.debug(relationRecord);
                this.store.add([relationRecord]);
            }
        }
        this.getActiveSearchCombo().collapse();
        this.getActiveSearchCombo().reset();
    },
    
    /**
     * checks if record to add is already linked or is the same record
     * @param {Tine.Tinebase.data.Record} recordToAdd
     * @param {String} relatedModel
     * @return {Boolean}
     */
    relationCheck: function(recordToAdd, relatedModel) {
        var add = true;
        this.store.each(function(relation) {
            if(relation.get('related_model') == relatedModel && relation.get('related_id') == recordToAdd.getId()) {
                Ext.MessageBox.show({
                    title: _('Failure'),
                    msg: _('The record you tried to link is already linked. Please edit the existing link.'),
                    buttons: Ext.MessageBox.OK,
                    icon: Ext.MessageBox.INFO
                });
                add = false;
                return false;
            }
        }, this);
        
        // don't allow to relate itself
        if((this.ownRecordClass.getMeta('phpClassName') == relatedModel) && recordToAdd.getId() == this.editDialog.record.getId()) {
            Ext.MessageBox.show({
                title: _('Failure'),
                msg: _('You tried to link a record with itself. This is not allowed!'),
                buttons: Ext.MessageBox.OK,
                icon: Ext.MessageBox.ERROR  
            });
            add = false;
        }
        return add;
    },
    
    /**
     * is called after a row has been edited
     * @param {Object} o
     */
    onAfterRowEdit: function(o) {
        this.onUpdate(o.grid.store, o.record);
        this.view.refresh();
    },
    /**
     * validates constrains config, is called after row edit
     * @param {Object} o
     */
    onValidateRowEdit: function(o) {
        if(o.field === 'type') {
            var model = o.record.get('related_model').split('_');
            var app = model[0];
            if(!this.view.invalidRowRecords) this.view.invalidRowRecords = [];
            model = model[2];
            
            if(this.constrainsConfig[app + model]) {
                // remove itself at first
                this.view.invalidRowRecords.remove(o.record.get('id'));
                Ext.each(this.constrainsConfig[app + model], function(conf) {
                    // check new value
                    if(conf.max && conf.max > 0 && (conf.type == o.value)) {
                        var resNew = this.store.queryBy(function(record, id) {
                            if((o.value == record.get('type')) && (record.get('related_model') == (app + '_Model_' + model))) return true;
                            else return false;
                        }, this);
                        // add all record ids to invalidRecords, if maximum is reached
                        if(resNew.getCount() >= conf.max) {
                            resNew.each(function(item) {
                                if(this.view.invalidRowRecords.indexOf(item.id) === -1) this.view.invalidRowRecords.push(item.id);
                            }, this);
                            if(this.view.invalidRowRecords.indexOf(o.record.id) === -1) this.view.invalidRowRecords.push(o.record.id);
                        }
                    } 
                    // check old value
                    if (conf.max && conf.max > 0 && (conf.type == o.originalValue)) {
                        var resOld = this.store.queryBy(function(record, id) {
                            if((o.originalValue == record.get('type')) && (record.get('related_model') == (app + '_Model_' + model))) return true;
                            else return false;
                        }, this);

                        if((resOld.getCount()-1) <= conf.max) {
                            resOld.each(function(item) {
                                if(item.id != o.record.get('id')) this.view.invalidRowRecords.remove(item.id);
                            }, this);
                        }
                    }
                }, this);
            }
        }
        return true;
    },

    /**
     * is called when a record is added to the store
     * @param {Ext.data.SimpleStore} store
     * @param {Array} records
     */
    onAdd: function(store, records) {
        Ext.each(records, function(record) {
            Ext.each(this.editDialog.relationPickers, function(picker) {
                if(picker.relationType == record.get('type') && record.get('related_id') != picker.getValue() && picker.fullModelName == record.get('related_model')) {
                    var split = picker.fullModelName.split('_Model_');
                    picker.combo.selectedRecord = new Tine[split[0]].Model[split[1]](record.get('related_record'));
                    picker.combo.startRecord = new Tine[split[0]].Model[split[1]](record.get('related_record'));
                    picker.combo.setValue(picker.combo.selectedRecord);
                    picker.combo.startValue = picker.combo.selectedRecord.get(this.recordClass.getMeta('idProperty'));
                }
            }, this);
        }, this);
    },
    /**
     * is called when a record in the grid changes
     * @param {Ext.data.SimpleStore} store
     * @param {} record
     */
    onUpdate: function(store, record) {
        store.each(function(record) {
            Ext.each(this.editDialog.relationPickers, function(picker) {
                if(picker.relationType == record.get('type') && picker.fullModelName == record.get('related_model')) {
                    picker.setValue(record.get('related_record'));
                }
            }, this);
        }, this);
        this.updateTitle();
    },

    /**
     * populate store and set record
     * @param {Record} record
     */
    loadRecord: function(dialog, record, ticketFn) {
        this.store.removeAll();
        var interceptor = ticketFn();
        if (record.get('relations') && record.get('relations').length > 0) {
            this.updateTitle(record.get('relations').length);
            var relationRecords = [];
            
            Ext.each(record.get('relations'), function(relation) {
                if(this.editDialog.ignoreRelatedModels) {
                    if(relation.hasOwnProperty('related_model') && this.editDialog.ignoreRelatedModels.indexOf(relation.related_model) == -1) {
                        relationRecords.push(new Tine.Tinebase.Model.Relation(relation, relation.id));
                    }
                } else {
                    relationRecords.push(new Tine.Tinebase.Model.Relation(relation, relation.id));
                }
            }, this);
            this.store.add(relationRecords);
            
            // sort by creation time
            this.store.sort('creation_time', 'DESC');
            
            this.updateTitle();
        }

        // add other listeners after population
        if(this.store) {
            this.store.on('update', this.onUpdate, this);
            this.store.on('add', this.updateTitle, this);
            this.store.on('remove', function(store, records, index) {
                Ext.each(records, function(record) {
                    Ext.each(this.editDialog.relationPickers, function(picker) {
                        if(picker.relationType == record.get('type') && record.get('related_id') == picker.getValue() && picker.fullModelName == record.get('related_model')) {
                            picker.clear();
                        }
                    }, this);
                }, this);
                this.updateTitle();
            }, this);
        }
        interceptor();
    },
    /**
     * checks if there are invalid relations
     * @return {Boolean}
     */
    isValid: function() {
        if(this.view && this.view.invalidRowRecords && this.view.invalidRowRecords.length > 0) return false;
        return true;
    },

    /**
     * get relations data as array
     *
     * @return {Array}
     */
    getData: function() {
        var relations = [];
        this.store.each(function(record) {
            delete record.data.related_record.relations;
            delete record.data.related_record.relation;
            relations.push(record.data);
        }, this);

        return relations;
    }
});/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.grid');

/**
 * tine 2.0 app grid panel widget
 * 
 * @namespace   Tine.widgets.grid
 * @class       Tine.widgets.grid.GridPanel
 * @extends     Ext.Panel
 * 
 * <p>Application Grid Panel</p>
 * <p>
 * </p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * 
 * @param       {Object} config
 * @constructor
 * Create a new GridPanel
 */
Tine.widgets.grid.GridPanel = function(config) {
    Ext.apply(this, config);

    this.gridConfig = this.gridConfig || {};
    this.defaultSortInfo = this.defaultSortInfo || {};
    this.defaultPaging = this.defaultPaging || {
        start: 0,
        limit: 50
    };

    // autogenerate stateId
    if (this.stateful !== false && ! this.stateId) {
        this.stateId = this.recordClass.getMeta('appName') + '-' + this.recordClass.getMeta('recordName') + '-GridPanel';
    }

    Tine.widgets.grid.GridPanel.superclass.constructor.call(this);
};

Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     * instance of the app object (required)
     */
    app: null,
    /**
     * @cfg {Object} gridConfig
     * Config object for the Ext.grid.GridPanel
     */
    gridConfig: null,
    /**
     * @cfg {Ext.data.Record} recordClass
     * record definition class  (required)
     */
    recordClass: null,
    /**
     * @cfg {Ext.data.DataProxy} recordProxy
     */
    recordProxy: null,
    /**
     * @cfg {Tine.widgets.grid.FilterToolbar} filterToolbar
     */
    filterToolbar: null,
    /**
     * @cfg {Bool} evalGrants
     * should grants of a grant-aware records be evaluated (defaults to true)
     */
    evalGrants: true,
    /**
     * @cfg {Bool} filterSelectionDelete
     * is it allowed to deleteByFilter?
     */
    filterSelectionDelete: false,
    /**
     * @cfg {Object} defaultSortInfo
     */
    defaultSortInfo: null,
    /**
     * @cfg {Object} storeRemoteSort
     */
    storeRemoteSort: true,
    /**
     * @cfg {Bool} usePagingToolbar 
     */
    usePagingToolbar: true,
    /**
     * @cfg {Object} defaultPaging 
     */
    defaultPaging: null,
    /**
     * @cfg {Object} pagingConfig
     * additional paging config
     */
    pagingConfig: null,
    /**
     * @cfg {Tine.widgets.grid.DetailsPanel} detailsPanel
     * if set, it becomes rendered in region south 
     */
    detailsPanel: null,
    /**
     * @cfg {Array} i18nDeleteQuestion 
     * spechialised strings for deleteQuestion
     */
    i18nDeleteQuestion: null,
    /**
     * @cfg {String} i18nAddRecordAction 
     * spechialised strings for add action button
     */
    i18nAddActionText: null,
    /**
     * @cfg {String} i18nEditRecordAction 
     * specialised strings for edit action button
     */
    i18nEditActionText: null,
    /**
     * @cfg {Array} i18nDeleteRecordAction 
     * specialised strings for delete action button
     */
    i18nDeleteActionText: null,

    /**
     * @cfg {Object} editDialogConfig 
     * config passed to edit dialog
     */
    editDialogConfig: null,

    /**
     * @cfg {String} editDialogClass 
     */
    editDialogClass: null,

    /**
     * @cfg {String} i18nEmptyText 
     */
    i18nEmptyText: null,

    /**
     * @cfg {String} newRecordIcon 
     * icon for adding new records button
     */
    newRecordIcon: null,

    /**
     * @cfg {Bool} i18nDeleteRecordAction 
     * update details panel if context menu is shown
     */
    updateDetailsPanelOnCtxMenu: true,

    /**
     * @cfg {Number} autoRefreshInterval (seconds)
     */
    autoRefreshInterval: 300,

    /**
     * @cfg {Bool} hasFavoritesPanel 
     */
    hasFavoritesPanel: true,
    
    /**
     * @cfg {Bool} hasQuickSearchFilterToolbarPlugin 
     */
    hasQuickSearchFilterToolbarPlugin: true,

    /**
     * disable 'select all pages' in paging toolbar
     * @cfg {Bool} disableSelectAllPages
     */
    disableSelectAllPages: false,

    /**
     * enable if records should be multiple editable
     * @cfg {Bool} multipleEdit
     */
    multipleEdit: false,
    
    /**
     * set if multiple edit requires special right
     * @type {String}  multipleEditRequiredRight
     */
    multipleEditRequiredRight: null,
    
    /**
     * enable if selection of 2 records should allow merging
     * @cfg {Bool} duplicateResolvable
     */
    duplicateResolvable: false,
    
    /**
     * @property autoRefreshTask
     * @type Ext.util.DelayedTask
     */
    autoRefreshTask: null,

    /**
     * @type Bool
     * @property updateOnSelectionChange
     */
    updateOnSelectionChange: true,

    /**
     * @type Bool
     * @property copyEditAction
     * 
     * TODO activate this by default
     */
    copyEditAction: false,

    /**
     * @type Ext.Toolbar
     * @property actionToolbar
     */
    actionToolbar: null,

    /**
     * @type Ext.ux.grid.PagingToolbar
     * @property pagingToolbar
     */
    pagingToolbar: null,

    /**
     * @type Ext.Menu
     * @property contextMenu
     */
    contextMenu: null,

    /**
     * @property lastStoreTransactionId 
     * @type String
     */
    lastStoreTransactionId: null,

    /**
     * @property editBuffer  - array of ids of records edited since last explicit refresh
     * @type Array of ids
     */
    editBuffer: null,

    /**
     * @property deleteQueue - array of ids of records currently being deleted
     * @type Array of ids
     */
    deleteQueue: null,

    /**
     * configuration object of model from application starter
     * @type object
     */
    modelConfig: null,
    
    /**
     * @property selectionModel
     * @type Tine.widgets.grid.FilterSelectionModel
     */
    selectionModel: null,
    
    /**
     * add records from other applications using the split add button
     * - activated by default
     * 
     * @type Bool
     * @property splitAddButton
     */
    splitAddButton: true,

    layout: 'border',
    border: false,
    stateful: true,

    /**
     * extend standard initComponent chain
     * 
     * @private
     */
    initComponent: function(){
        // init some translations
        this.i18nRecordName = this.recordClass.getRecordName();
        this.i18nRecordsName = this.recordClass.getRecordsName();
        this.i18nContainerName = this.recordClass.getContainerName();
        this.i18nContainersName = this.recordClass.getContainersName();
        this.i18nEmptyText = this.i18nEmptyText || String.format(Tine.Tinebase.translation._("There could not be found any {0}. Please try to change your filter-criteria, view-options or the {1} you search in."), this.i18nRecordsName, this.i18nContainersName);

        this.i18nEditActionText = this.i18nEditActionText ? this.i18nEditActionText : [String.format(Tine.Tinebase.translation.ngettext('Edit {0}', 'Edit {0}', 1), this.i18nRecordName), String.format(Tine.Tinebase.translation.ngettext('Edit {0}', 'Edit {0}', 2), this.i18nRecordsName)];

        this.editDialogConfig = this.editDialogConfig || {};
        
        this.editBuffer = [];
        this.deleteQueue = [];
        
        // init generic filter toolbar
        this.initGenericFilterToolbar();
        
        // init generic columnModel
        this.initGenericColumnModel();
        
        // init store
        this.initStore();
        
        // init (ext) grid
        this.initGrid();

        // init actions
        this.actionUpdater = new Tine.widgets.ActionUpdater({
            containerProperty: this.recordClass.getMeta('containerProperty'), 
            evalGrants: this.evalGrants
        });
        this.initActions();

        this.initLayout();

        // for some reason IE looses split height when outer layout is layouted
        if (Ext.isIE6 || Ext.isIE7) {
            this.on('show', function() {
                if (this.layout.rendered && this.detailsPanel) {
                    var height = this.detailsPanel.getSize().height;
                    this.layout.south.split.setCurrentSize(height);
                }
            }, this);
        }

        Tine.widgets.grid.GridPanel.superclass.initComponent.call(this);
    },

    /**
     * initialises generic filter toolbar
     * @private
     */
    initGenericFilterToolbar: function() {
        if(this.modelConfig) {
            var plugins = [];
            if (! Ext.isDefined(this.hasQuickSearchFilterToolbarPlugin) || this.hasQuickSearchFilterToolbarPlugin) {
                this.quickSearchFilterToolbarPlugin = new Tine.widgets.grid.FilterToolbarQuickFilterPlugin();
                plugins.push(this.quickSearchFilterToolbarPlugin);
            }
            var modelName = this.recordClass.getMeta('modelName');
            this.filterToolbar = new Tine.widgets.grid.FilterPanel({
                app: this.app,
                recordClass: this.recordClass,
                allowSaving: true,
                filterModels: this.recordClass.getFilterModel(),
                defaultFilter: this.recordClass.getMeta('defaultFilter') ? this.recordClass.getMeta('defaultFilter') : 'query',
                plugins: plugins,
                filters: []
            });
            this.plugins = this.plugins || [];
            this.plugins.push(this.filterToolbar);
        }
    },

    /**
     * initializes the generic column model on auto bootstrap
     */
    initGenericColumnModel: function() {
        if(this.modelConfig) {
            var columns = [];
            Ext.each(this.modelConfig.keys, function(key) {
                // When no label exists, don't use in grid
                if(this.modelConfig.fields[key].label) {
                    var config = {
                        id: key,
                        dataIndex: key,
                        header: this.app.i18n._(this.modelConfig.fields[key].label),
                        hidden: this.modelConfig.fields[key].hidden 
                    }
                    var renderer = Tine.widgets.grid.RendererManager.get(this.app.name, this.recordClass.getMeta('modelName'), key);
                    if(renderer) {
                        config.renderer = renderer;
                    }
                    columns.push(config);
                }
            }, this);
            this.gridConfig.cm = new Ext.grid.ColumnModel({ 
                defaults: {
                    sortable: true,
                    resizable: true
                },
                columns: columns
            });
        }
    },
    
    /**
     * @private
     * 
     * NOTE: Order of items matters! Ext.Layout.Border.SplitRegion.layout() does not
     *       fence the rendering correctly, as such it's impotant, so have the ftb
     *       defined after all other layout items
     */
    initLayout: function() {
        this.items = [{
            region: 'center',
            xtype: 'panel',
            layout: 'fit',
            border: false,
            tbar: this.pagingToolbar,
            items: this.grid
        }];

        // add detail panel
        if (this.detailsPanel) {
            this.items.push({
                region: 'south',
                border: false,
                collapsible: true,
                collapseMode: 'mini',
                header: false,
                split: true,
                layout: 'fit',
                height: this.detailsPanel.defaultHeight ? this.detailsPanel.defaultHeight : 125,
                items: this.detailsPanel

            });
            this.detailsPanel.doBind(this.grid);
        }

        // add filter toolbar
        if (this.filterToolbar) {
            this.items.push({
                region: 'north',
                border: false,
                autoScroll: true,
                items: this.filterToolbar,
                listeners: {
                    scope: this,
                    afterlayout: function(ct) {
                        ct.suspendEvents();
                        ct.setHeight(Math.min(120, this.filterToolbar.getHeight()));
                        ct.getEl().child('div[class^="x-panel-body"]', true).scrollTop = 1000000;
                        ct.ownerCt.layout.layout();
                        ct.resumeEvents();
                    }
                }
            });
        }

    },

    /**
     * init actions with actionToolbar, contextMenu and actionUpdater
     * 
     * @private
     */
    initActions: function() {

        this.action_editInNewWindow = new Ext.Action({
            requiredGrant: 'readGrant',
            requiredMultipleGrant: 'editGrant',
            requiredMultipleRight: this.multipleEditRequiredRight,
            text: this.i18nEditActionText ? this.i18nEditActionText[0] : String.format(_('Edit {0}'), this.i18nRecordName),
            singularText: this.i18nEditActionText ? this.i18nEditActionText[0] : String.format(_('Edit {0}'), this.i18nRecordName),
            pluralText:  this.i18nEditActionText ? this.i18nEditActionText[1] : String.format(Tine.Tinebase.translation.ngettext('Edit {0}', 'Edit {0}', 1), this.i18nRecordsName),
            disabled: true,
            translationObject: this.i18nEditActionText ? this.app.i18n : Tine.Tinebase.translation,
            actionType: 'edit',
            handler: this.onEditInNewWindow.createDelegate(this, [{actionType: 'edit'}]),
            iconCls: 'action_edit',
            scope: this,
            allowMultiple: this.multipleEdit
        });

        this.action_editCopyInNewWindow = new Ext.Action({
            hidden: ! this.copyEditAction,
            requiredGrant: 'readGrant',
            text: String.format(_('Copy {0}'), this.i18nRecordName),
            disabled: true,
            actionType: 'copy',
            handler: this.onEditInNewWindow.createDelegate(this, [{actionType: 'copy'}]),
            iconCls: 'action_editcopy',
            scope: this
        });

        this.action_addInNewWindow = new Ext.Action({
            requiredGrant: 'addGrant',
            actionType: 'add',
            text: this.i18nAddActionText ? this.app.i18n._hidden(this.i18nAddActionText) : String.format(_('Add {0}'), this.i18nRecordName),
            handler: this.onEditInNewWindow.createDelegate(this, [{actionType: 'add'}]),
            iconCls: (this.newRecordIcon !== null) ? this.newRecordIcon : this.app.appName + 'IconCls',
            scope: this
        });

        this.actions_print = new Ext.Action({
            requiredGrant: 'readGrant',
            text: _('Print Page'),
            disabled: false,
            handler: function() {
                Ext.ux.Printer.print(this.getGrid());
            },
            iconCls: 'action_print',
            scope: this,
            allowMultiple: true
        });

        this.initDeleteAction();

        this.action_tagsMassAttach = new Tine.widgets.tags.TagsMassAttachAction({
            hidden:         ! this.recordClass.getField('tags'),
            selectionModel: this.grid.getSelectionModel(),
            recordClass:    this.recordClass,
            updateHandler:  this.loadGridData.createDelegate(this),
            app:            this.app
        });

        this.action_tagsMassDetach = new Tine.widgets.tags.TagsMassDetachAction({
            hidden:         ! this.recordClass.getField('tags'),
            selectionModel: this.grid.getSelectionModel(),
            recordClass:    this.recordClass,
            updateHandler:  this.loadGridData.createDelegate(this),
            app:            this.app
        });

        this.action_resolveDuplicates = new Ext.Action({
            requiredGrant: null,
            text: String.format(_('Merge {0}'), this.i18nRecordsName),
                iconCls: 'action_resolveDuplicates',
                scope: this,
                handler: this.onResolveDuplicates,
                disabled: false,
                actionUpdater: function(action, grants, records) {
                    if(records && (records.length != 2)) action.setDisabled(true);
                    else action.setDisabled(false);
                }
        });
        
        // add actions to updater
        this.actionUpdater.addActions([
            this.action_addInNewWindow,
            this.action_editInNewWindow,
            this.action_editCopyInNewWindow,
            this.action_deleteRecord,
            this.action_tagsMassAttach,
            this.action_tagsMassDetach,
            this.action_resolveDuplicates
        ]);

        // init actionToolbar (needed for correct fitertoolbar init atm -> fixme)
        this.getActionToolbar();
    },

    initDeleteAction: function() {
        // note: unprecise plural form here, but this is hard to change
        this.action_deleteRecord = new Ext.Action({
            requiredGrant: 'deleteGrant',
            allowMultiple: true,
            singularText: this.i18nDeleteActionText ? this.i18nDeleteActionText[0] : String.format(Tine.Tinebase.translation.ngettext('Delete {0}', 'Delete {0}', 1), this.i18nRecordName),
            pluralText: this.i18nDeleteActionText ? this.i18nDeleteActionText[1] : String.format(Tine.Tinebase.translation.ngettext('Delete {0}', 'Delete {0}', 1), this.i18nRecordsName),
            translationObject: this.i18nDeleteActionText ? this.app.i18n : Tine.Tinebase.translation,
            text: this.i18nDeleteActionText ? this.i18nDeleteActionText[0] : String.format(Tine.Tinebase.translation.ngettext('Delete {0}', 'Delete {0}', 1), this.i18nRecordName),
            handler: this.onDeleteRecords,
            disabled: true,
            iconCls: 'action_delete',
            scope: this
        });
    },

    /**
     * init store
     * @private
     */
    initStore: function() {
        if (this.recordProxy) {
            this.store = new Ext.data.Store({
                fields: this.recordClass,
                proxy: this.recordProxy,
                reader: this.recordProxy.getReader(),
                remoteSort: this.storeRemoteSort,
                sortInfo: this.defaultSortInfo,
                listeners: {
                    scope: this,
                    'add': this.onStoreAdd,
                    'remove': this.onStoreRemove,
                    'update': this.onStoreUpdate,
                    'beforeload': this.onStoreBeforeload,
                    'load': this.onStoreLoad,
                    'beforeloadrecords': this.onStoreBeforeLoadRecords,
                    'loadexception': this.onStoreLoadException
                }
            });
        } else {
            this.store = new Tine.Tinebase.data.RecordStore({
                recordClass: this.recordClass
            });
        }

        // init autoRefresh
        this.autoRefreshTask = new Ext.util.DelayedTask(this.loadGridData, this, [{
            removeStrategy: 'keepBuffered',
            autoRefresh: true
        }]);
    },

    /**
     * returns view row class
     */
    getViewRowClass: function(record, index, rowParams, store) {
        var noLongerInFilter = record.not_in_filter;

        var className = '';
        if (noLongerInFilter) {
            className += 'tine-grid-row-nolongerinfilter';
        }
        return className;
    },    

    /**
     * new entry event -> add new record to store
     * 
     * @param {Object} recordData
     * @return {Boolean}
     */
    onStoreNewEntry: function(recordData) {

        var initialData = null;
        if (Ext.isFunction(this.recordClass.getDefaultData)) {
            initialData = Ext.apply(this.recordClass.getDefaultData(), recordData);
        } else {
            initialData = recordData;
        }
        var record = new this.recordClass(initialData);
        this.store.insert(0 , [record]);

        if (this.usePagingToolbar) {
            this.pagingToolbar.refresh.disable();
        }
        this.recordProxy.saveRecord(record, {
            scope: this,
            success: function(newRecord) {
                this.store.suspendEvents();
                this.store.remove(record);
                this.store.insert(0 , [newRecord]);
                this.store.resumeEvents();

                this.addToEditBuffer(newRecord);

                this.loadGridData({
                    removeStrategy: 'keepBuffered'
                });
            }
        });

        return true;
    },

    /**
     * header is clicked
     * 
     * @param {Object} grid
     * @param {Number} colIdx
     * @param {Event} e
     * @return {Boolean}
     */
    onHeaderClick: function(grid, colIdx, e) {

        Ext.apply(this.store.lastOptions, {
            preserveCursor:     true,
            preserveSelection:  true, 
            preserveScroller:   true, 
            removeStrategy:     'default'
        });
    },

    /**
     * called when Records have been added to the Store
     */
    onStoreAdd: function(store, records, index) {
        this.store.totalLength += records.length;
        if (this.pagingToolbar) {
            this.pagingToolbar.updateInfo();
        }
    },
    
    /**
     * called when a Record has been removed from the Store
     */
    onStoreRemove: function(store, record, index) {
        this.store.totalLength--;
        if (this.pagingToolbar) {
            this.pagingToolbar.updateInfo();
        }
    },
    
    /**
     * called when the store gets updated, e.g. from editgrid
     */
    onStoreUpdate: function(store, record, operation) {

        switch (operation) {
            case Ext.data.Record.EDIT:

                this.addToEditBuffer(record);
                if (this.usePagingToolbar) {
                    this.pagingToolbar.refresh.disable();
                }

                this.recordProxy.saveRecord(record, {
                    scope: this,
                    success: function(updatedRecord) {
                        store.commitChanges();

                        // update record in store to prevent concurrency problems
                        record.data = updatedRecord.data;

                        this.loadGridData({
                            removeStrategy: 'keepBuffered'
                        });
                    }
                });
                break;
            case Ext.data.Record.COMMIT:
                //nothing to do, as we need to reload the store anyway.
                break;
        }
    },

    /**
     * called before store queries for data
     */
    onStoreBeforeload: function(store, options) {

        // define a transaction
        this.lastStoreTransactionId = options.transactionId = Ext.id();

        options.params = options.params || {};
        // allways start with an empty filter set!
        // this is important for paging and sort header!
        options.params.filter = [];

        if (! options.removeStrategy || options.removeStrategy !== 'keepBuffered') {
            this.editBuffer = [];
        }

//        options.preserveSelection = options.hasOwnProperty('preserveSelection') ? options.preserveSelection : true;
//        options.preserveScroller = options.hasOwnProperty('preserveScroller') ? options.preserveScroller : true;

        // fix nasty paging tb
        Ext.applyIf(options.params, this.defaultPaging);
    },

    /**
     * called after a new set of Records has been loaded
     * 
     * @param  {Ext.data.Store} this.store
     * @param  {Array}          loaded records
     * @param  {Array}          load options
     * @return {Void}
     */
    onStoreLoad: function(store, records, options) {
        // we always focus the first row so that keynav starts in the grid
        // this resets scroller ;-( -> need a better solution
//        if (this.store.getCount() > 0) {
//            this.grid.getView().focusRow(0);
//        }

        // restore selection
        if (Ext.isArray(options.preserveSelection)) {
            Ext.each(options.preserveSelection, function(record) {
                var row = this.store.indexOfId(record.id);
                if (row >= 0) {
                    this.grid.getSelectionModel().selectRow(row, true);
                }
            }, this);
        }

        // restore scroller
        if (Ext.isNumber(options.preserveScroller)) {
            this.grid.getView().scroller.dom.scrollTop = options.preserveScroller;
        }

        // reset autoRefresh
        if (window.isMainWindow && this.autoRefreshInterval) {
            this.autoRefreshTask.delay(this.autoRefreshInterval * 1000);
        }
    },

    /**
     * on store load exception
     * 
     * @param {Tine.Tinebase.data.RecordProxy} proxy
     * @param {String} type
     * @param {Object} error
     * @param {Object} options
     */
    onStoreLoadException: function(proxy, type, error, options) {

        // reset autoRefresh
        if (window.isMainWindow && this.autoRefreshInterval) {
            this.autoRefreshTask.delay(this.autoRefreshInterval * 5000);
        }

        if (this.usePagingToolbar && this.pagingToolbar.refresh) {
            this.pagingToolbar.refresh.enable();
        }

        if (! options.autoRefresh) {
            proxy.handleRequestException(error);
        } else {
            Tine.log.debug('Tine.widgets.grid.GridPanel::onStoreLoadException -> auto refresh failed.');
        }
    },

    /**
     * onStoreBeforeLoadRecords
     * 
     * @param {Object} o
     * @param {Object} options
     * @param {Boolean} success
     * @param {Ext.data.Store} store
     */
    onStoreBeforeLoadRecords: function(o, options, success, store) {

        if (this.lastStoreTransactionId && options.transactionId && this.lastStoreTransactionId !== options.transactionId) {
            Tine.log.debug('cancelling old transaction request.');
            return false;
        }

        // save selection -> will be applied onLoad
        if (options.preserveSelection) {
            options.preserveSelection = this.grid.getSelectionModel().getSelections();
        }

        // save scroller -> will be applied onLoad
        if (options.preserveScroller && this.grid.getView().scroller && this.grid.getView().scroller.dom) options.preserveScroller = this.grid.getView().scroller.dom.scrollTop;

        // apply removeStrategy
        if (! options.removeStrategy || options.removeStrategy === 'default') {
            return true;
        }

        var records = [],
            recordsIds = [],
            newRecordCollection = new Ext.util.MixedCollection();

        // fill new collection
        Ext.each(o.records, function(record) {
            newRecordCollection.add(record.id, record);
        });

        // assemble update & keep
        this.store.each(function(record) {
            var newRecord = newRecordCollection.get(record.id);
            if (newRecord) {
                records.push(newRecord);
                recordsIds.push(newRecord.id);
            } else if (options.removeStrategy === 'keepAll' || (options.removeStrategy === 'keepBuffered' && this.editBuffer.indexOf(record.id) >= 0)) {
                var copiedRecord = record.copy();
                copiedRecord.not_in_filter = true;
                records.push(copiedRecord);
                recordsIds.push(record.id);
            }
        }, this);

        // assemble adds
        newRecordCollection.each(function(record, idx) {
            if (recordsIds.indexOf(record.id) == -1 && this.deleteQueue.indexOf(record.id) == -1) {
                var lastRecord = newRecordCollection.itemAt(idx-1);
                var lastRecordIdx = lastRecord ? recordsIds.indexOf(lastRecord.id) : -1;
                records.splice(lastRecordIdx+1, 0, record);
                recordsIds.splice(lastRecordIdx+1, 0, record.id);
            }
        }, this);

        o.records = records;
    },

    /**
     * perform the initial load of grid data
     */
    initialLoad: function() {
        var defaultFavorite = Tine.widgets.persistentfilter.model.PersistentFilter.getDefaultFavorite(this.app.appName);
        var favoritesPanel  = this.app.getMainScreen() && typeof this.app.getMainScreen().getWestPanel().getFavoritesPanel === 'function' && this.hasFavoritesPanel 
            ? this.app.getMainScreen().getWestPanel().getFavoritesPanel() 
            : null;
        if (defaultFavorite && favoritesPanel) {
            favoritesPanel.selectFilter(defaultFavorite);
        } else {
            this.store.load.defer(10, this.store, [
                typeof this.autoLoad == 'object' ?
                    this.autoLoad : undefined]);
        }

        if (this.usePagingToolbar && this.recordProxy) {
            this.pagingToolbar.refresh.disable.defer(10, this.pagingToolbar.refresh);
        }
    },

    /**
     * init ext grid panel
     * @private
     */
    initGrid: function() {
        // init sel model
        this.selectionModel = new Tine.widgets.grid.FilterSelectionModel({
            store: this.store,
            gridPanel: this
        });
        this.selectionModel.on('selectionchange', function(sm) {
            //Tine.widgets.actionUpdater(sm, this.actions, this.recordClass.getMeta('containerProperty'), !this.evalGrants);
            this.actionUpdater.updateActions(sm);

            this.ctxNode = this.selectionModel.getSelections();
            if (this.updateOnSelectionChange && this.detailsPanel) {
                this.detailsPanel.onDetailsUpdate(sm);
            }
        }, this);

        if (this.usePagingToolbar) {
            this.pagingToolbar = new Ext.ux.grid.PagingToolbar(Ext.apply({
                pageSize: this.defaultPaging && this.defaultPaging.limit ? this.defaultPaging.limit : 50,
                store: this.store,
                displayInfo: true,
                displayMsg: Tine.Tinebase.translation._('Displaying records {0} - {1} of {2}').replace(/records/, this.i18nRecordsName),
                emptyMsg: String.format(Tine.Tinebase.translation._("No {0} to display"), this.i18nRecordsName),
                displaySelectionHelper: true,
                sm: this.selectionModel,
                disableSelectAllPages: this.disableSelectAllPages
            }, this.pagingConfig));
            // mark next grid refresh as paging-refresh
            this.pagingToolbar.on('beforechange', function() {
                this.grid.getView().isPagingRefresh = true;
            }, this);
        }

        // init view
        var view =  new Ext.grid.GridView({
            getRowClass: this.getViewRowClass,
            autoFill: true,
            forceFit:true,
            ignoreAdd: true,
            emptyText: this.i18nEmptyText,
            onLoad: Ext.grid.GridView.prototype.onLoad.createInterceptor(function() {
                if (this.grid.getView().isPagingRefresh) {
                    this.grid.getView().isPagingRefresh = false;
                    return true;
                }

                return false;
            }, this)
        });

        // which grid to use?
        var Grid = this.gridConfig.quickaddMandatory ? Ext.ux.grid.QuickaddGridPanel : (this.gridConfig.gridType || Ext.grid.GridPanel);

        this.gridConfig.store = this.store;

        // activate grid header menu for column selection
        this.gridConfig.plugins = this.gridConfig.plugins ? this.gridConfig.plugins : [];
        this.gridConfig.plugins.push(new Ext.ux.grid.GridViewMenuPlugin({}));
        this.gridConfig.enableHdMenu = false;

        if (this.stateful) {
            this.gridConfig.stateful = true;
            this.gridConfig.stateId  = this.stateId + '-Grid';
        }

        this.grid = new Grid(Ext.applyIf(this.gridConfig, {
            border: false,
            store: this.store,
            sm: this.selectionModel,
            view: view
        }));

        // init various grid / sm listeners
        this.grid.on('keydown',     this.onKeyDown,         this);
        this.grid.on('rowclick',    this.onRowClick,        this);
        this.grid.on('rowdblclick', this.onRowDblClick,     this);
        this.grid.on('newentry',    this.onStoreNewEntry,   this);
        this.grid.on('headerclick', this.onHeaderClick,   this);

        this.grid.on('rowcontextmenu', this.onRowContextMenu, this);

    },

    /**
     * executed after outer panel rendering process
     */
    afterRender: function() {
        Tine.widgets.grid.GridPanel.superclass.afterRender.apply(this, arguments);
        this.initialLoad();
    },

    /**
     * trigger store load with grid related options
     * 
     * TODO rethink -> preserveCursor and preserveSelection might conflict on page breaks!
     * TODO don't reload details panel when selection is preserved
     * 
     * @param {Object} options
     */
    loadGridData: function(options) {
        var options = options || {};

        Ext.applyIf(options, {
            callback:           Ext.emptyFn,
            scope:              this,
            params:             {},

            preserveCursor:     true, 
            preserveSelection:  true, 
            preserveScroller:   true, 
            removeStrategy:     'default'
        });

        if (options.preserveCursor && this.usePagingToolbar) {
            options.params.start = this.pagingToolbar.cursor;
        }

        this.store.load(options);
    },

    /**
     * get action toolbar
     * 
     * @return {Ext.Toolbar}
     */
    getActionToolbar: function() {
        if (! this.actionToolbar) {
            var additionalItems = this.getActionToolbarItems();

            var items = [];
            
            if (this.action_addInNewWindow) {
                if (this.splitAddButton) {
                    items.push(Ext.apply(
                        new Ext.SplitButton(this.action_addInNewWindow), {
                            scale: 'medium',
                            rowspan: 2,
                            iconAlign: 'top',
                            arrowAlign:'right',
                            menu: new Ext.menu.Menu({
                                items: [],
                                plugins: [{
                                    ptype: 'ux.itemregistry',
                                    key:   'Tine.widgets.grid.GridPanel.addButton'
                                }]
                            })
                        })
                    );
                } else {
                    items.push(Ext.apply(
                        new Ext.Button(this.action_addInNewWindow), {
                            scale: 'medium',
                            rowspan: 2,
                            iconAlign: 'top'
                        })
                    );
                }
            }
            
            if (this.action_editInNewWindow) {
                items.push(Ext.apply(
                    new Ext.Button(this.action_editInNewWindow), {
                        scale: 'medium',
                        rowspan: 2,
                        iconAlign: 'top'
                    })
                );
            }
            
            if (this.action_deleteRecord) {
                items.push(Ext.apply(
                    new Ext.Button(this.action_deleteRecord), {
                        scale: 'medium',
                        rowspan: 2,
                        iconAlign: 'top'
                    })
                );
            }
            
            if (this.actions_print) {
                items.push(Ext.apply(
                    new Ext.Button(this.actions_print), {
                        scale: 'medium',
                        rowspan: 2,
                        iconAlign: 'top'
                    })
                );
            }
            
            this.actionToolbar = new Ext.Toolbar({
                items: [{
                    xtype: 'buttongroup',
                    plugins: [{
                        ptype: 'ux.itemregistry',
                        key:   this.app.appName + '-GridPanel-ActionToolbar-leftbtngrp'
                    }],
                    items: items.concat(Ext.isArray(additionalItems) ? additionalItems : [])
                }].concat(Ext.isArray(additionalItems) ? [] : [additionalItems])
            });

            if (this.filterToolbar && typeof this.filterToolbar.getQuickFilterField == 'function') {
                this.actionToolbar.add('->', this.filterToolbar.getQuickFilterField());
            } 
        }

        return this.actionToolbar;
    },

    /**
     * template fn for subclasses to add custom items to action toolbar
     * 
     * @return {Array/Object}
     */
    getActionToolbarItems: function() {
        var items = this.actionToolbarItems || [];

        if (! Ext.isEmpty(items)) {
            // legacy handling! subclasses should register all actions when initializing actions
            this.actionUpdater.addActions(items);
        }

        return items;
    },

    /**
     * returns rows context menu
     * 
     * @return {Ext.menu.Menu}
     */
    getContextMenu: function() {

        if (! this.contextMenu) {
            var items = [];
            
            if (this.action_addInNewWindow) items.push(this.action_addInNewWindow);
            if (this.action_editCopyInNewWindow) items.push(this.action_editCopyInNewWindow);
            if (this.action_editInNewWindow) items.push(this.action_editInNewWindow);
            if (this.action_deleteRecord) items.push(this.action_deleteRecord);

            if(this.duplicateResolvable) {
                items.push(this.action_resolveDuplicates);
            }
            
            if (this.action_tagsMassAttach && ! this.action_tagsMassAttach.hidden) {
                items.push('-', this.action_tagsMassAttach, this.action_tagsMassDetach);
            }

            // lookup additional items
            items = items.concat(this.getContextMenuItems());

            // New record of another app
            this.newRecordMenu = new Ext.menu.Menu({
                items: [],
                plugins: [{
                    ptype: 'ux.itemregistry',
                    key:   this.app.appName + '-GridPanel-ContextMenu-New'
                }]
            });

            this.newRecordAction = new Ext.Action({
                text: _('New...'),
                hidden: ! this.newRecordMenu.items.length,
                iconCls: this.app.getIconCls(),
                scope: this,
                menu: this.newRecordMenu
            });

            items.push(this.newRecordAction);

            // Add to record of another app            
            this.addToRecordMenu = new Ext.menu.Menu({
                items: [],
                plugins: [{
                    ptype: 'ux.itemregistry',
                    key:   this.app.appName + '-GridPanel-ContextMenu-Add'
                }]
            });

            this.addToRecordAction = new Ext.Action({
                text: _('Add to...'),
                hidden: ! this.addToRecordMenu.items.length,
                iconCls: this.app.getIconCls(),
                scope: this,
                menu: this.addToRecordMenu
            });

            items.push(this.addToRecordAction);

            this.contextMenu = new Ext.menu.Menu({
                items: items,
                plugins: [{
                    ptype: 'ux.itemregistry',
                    key:   this.app.appName + '-GridPanel-ContextMenu'
                }]
            });
        }

        return this.contextMenu;
    },

    /**
     * template fn for subclasses to add custom items to context menu
     * 
     * @return {Array}
     */
    getContextMenuItems: function() {
        var items = this.contextMenuItems || [];

        if (! Ext.isEmpty(items)) {
            // legacy handling! subclasses should register all actions when initializing actions
            this.actionUpdater.addActions(items);
        }

        return items;
    },

    /**
     * get modlog columns
     * 
     * @return {Array}
     */
    getModlogColumns: function() {
        var result = [
            { id: 'creation_time',      header: _('Creation Time'),         dataIndex: 'creation_time',         renderer: Tine.Tinebase.common.dateRenderer,        hidden: true, sortable: true },
            { id: 'created_by',         header: _('Created By'),            dataIndex: 'created_by',            renderer: Tine.Tinebase.common.usernameRenderer,    hidden: true, sortable: true },
            { id: 'last_modified_time', header: _('Last Modified Time'),    dataIndex: 'last_modified_time',    renderer: Tine.Tinebase.common.dateRenderer,        hidden: true, sortable: true },
            { id: 'last_modified_by',   header: _('Last Modified By'),      dataIndex: 'last_modified_by',      renderer: Tine.Tinebase.common.usernameRenderer,    hidden: true, sortable: true }
        ];

        return result;
    },

    /**
     * get custom field columns for column model
     * 
     * @return {Array}
     */
    getCustomfieldColumns: function() {
        var modelName = this.recordClass.getMeta('appName') + '_Model_' + this.recordClass.getMeta('modelName'),
            cfConfigs = Tine.widgets.customfields.ConfigManager.getConfigs(this.app, modelName),
            result = [];

        Ext.each(cfConfigs, function(cfConfig) {
            result.push({
                id: cfConfig.id,
                header: cfConfig.get('definition').label,
                dataIndex: 'customfields',
                renderer: Tine.widgets.customfields.Renderer.get(this.app, cfConfig),
                sortable: false,
                hidden: true
            });
        }, this);

        return result;
    },

    /**
     * get custom field filter for filter toolbar
     * 
     * @return {Array}
     */
    getCustomfieldFilters: function() {
        var modelName = this.recordClass.getMeta('appName') + '_Model_' + this.recordClass.getMeta('modelName'),
            cfConfigs = Tine.widgets.customfields.ConfigManager.getConfigs(this.app, modelName),
            result = [];
        Ext.each(cfConfigs, function(cfConfig) {
            result.push({filtertype: 'tinebase.customfield', app: this.app, cfConfig: cfConfig});
        }, this);

        return result;
    },

    /**
     * returns filter toolbar
     * @private
     */
    getFilterToolbar: function(config) {
        config = config || {};
        var plugins = [];
        if (! Ext.isDefined(this.hasQuickSearchFilterToolbarPlugin) || this.hasQuickSearchFilterToolbarPlugin) {
            this.quickSearchFilterToolbarPlugin = new Tine.widgets.grid.FilterToolbarQuickFilterPlugin();
            plugins.push(this.quickSearchFilterToolbarPlugin);
        }

        return new Tine.widgets.grid.FilterPanel(Ext.apply(config, {
            app: this.app,
            recordClass: this.recordClass,
            filterModels: this.recordClass.getFilterModel().concat(this.getCustomfieldFilters()),
            defaultFilter: 'query',
            filters: this.defaultFilters || [],
            plugins: plugins
        }));
    },

    /**
     * return store from grid
     * 
     * @return {Ext.data.Store}
     */
    getStore: function() {
        return this.grid.getStore();
    },

    /**
     * return view from grid
     * 
     * @return {Ext.grid.GridView}
     */
    getView: function() {
        return this.grid.getView();
    },

    /**
     * return grid
     * 
     * @return {Ext.ux.grid.QuickaddGridPanel|Ext.grid.GridPanel}
     */
    getGrid: function() {
        return this.grid;
    },

    /**
     * key down handler
     * @private
     */
    onKeyDown: function(e){
        if (e.ctrlKey) {
            switch (e.getKey()) {
                case e.A:
                    // select only current page
                    this.grid.getSelectionModel().selectAll(true);
                    e.preventDefault();
                    break;
                case e.E:
                    if (this.action_editInNewWindow && !this.action_editInNewWindow.isDisabled()) {
                        this.onEditInNewWindow.call(this, {
                            actionType: 'edit'
                        });
                        e.preventDefault();
                    }
                    break;
                case e.N:
                    if (this.action_addInNewWindow && !this.action_addInNewWindow.isDisabled()) {
                        this.onEditInNewWindow.call(this, {
                            actionType: 'add'
                        });
                        e.preventDefault();
                    }
                    break;
                case e.F:
                    if (this.filterToolbar && this.hasQuickSearchFilterToolbarPlugin) {
                        e.preventDefault();
                        this.filterToolbar.getQuickFilterPlugin().quickFilter.focus();
                    }
                    break;
            }
        } else {
            if ([e.BACKSPACE, e.DELETE].indexOf(e.getKey()) !== -1) {
                if (!this.grid.editing && !this.grid.adding && !this.action_deleteRecord.isDisabled()) {
                    this.onDeleteRecords.call(this);
                    e.preventDefault();
                }
            }
        }
    },

    /**
     * row click handler
     * 
     */
    onRowClick: function(grid, row, e) {
        /* TODO check if we need this in IE
        // hack to get percentage editor working
        var cell = Ext.get(grid.getView().getCell(row,1));
        var dom = cell.child('div:last');
        while (cell.first()) {
            cell = cell.first();
            cell.on('click', function(e){
                e.stopPropagation();
                grid.fireEvent('celldblclick', grid, row, 1, e);
            });
        }
        */

        // fix selection of one record if shift/ctrl key is not pressed any longer
        if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
            var sm = grid.getSelectionModel();

            if (sm.getCount() == 1 && sm.isSelected(row)) {
                return;
            }

            sm.clearSelections();
            sm.selectRow(row, false);
            grid.view.focusRow(row);
        }
    },
    
    onRowContextMenu: function(grid, row, e) {
        e.stopEvent();
        var selModel = grid.getSelectionModel();
        if(!selModel.isSelected(row)) {
            // disable preview update if config option is set to false
            this.updateOnSelectionChange = this.updateDetailsPanelOnCtxMenu;
            selModel.selectRow(row);
        }

        this.getContextMenu().showAt(e.getXY());
        // reset preview update
        this.updateOnSelectionChange = true;
    },
    
    /**
     * row doubleclick handler
     * 
     * @param {} grid
     * @param {} row
     * @param {} e
     */
    onRowDblClick: function(grid, row, e) {
        this.onEditInNewWindow.call(this, {actionType: 'edit'});
    }, 

    /**
     * called on row context click
     * 
     * @param {Ext.grid.GridPanel} grid
     * @param {Number} row
     * @param {Ext.EventObject} e
     */
    onRowContextMenu: function(grid, row, e) {
        e.stopEvent();
        var selModel = grid.getSelectionModel();
        if(!selModel.isSelected(row)) {
            // disable preview update if config option is set to false
            this.updateOnSelectionChange = this.updateDetailsPanelOnCtxMenu;
            selModel.selectRow(row);
        }

        this.getContextMenu(grid, row, e).showAt(e.getXY());
        // reset preview update
        this.updateOnSelectionChange = true;
    },
    
    /**
     * Opens the required EditDialog
     * @param {Object} actionButton the button the action was called from
     * @param {Tine.Tinebase.data.Record} record the record to display/edit in the dialog
     * @param {Array} plugins the plugins used for the edit dialog
     * @return {Boolean}
     */
    onEditInNewWindow: function(button, record, plugins) {
        if(!record) {
            if (button.actionType == 'edit' || button.actionType == 'copy') {
                if (! this.action_editInNewWindow || this.action_editInNewWindow.isDisabled()) {
                    // if edit action is disabled or not available, we also don't open a new window
                    return false;
                }
                var selectedRows = this.grid.getSelectionModel().getSelections();
                record = selectedRows[0];
            } else {
                record = new this.recordClass(this.recordClass.getDefaultData(), 0);
            }
        }

        // plugins to add to edit dialog
        var plugins = plugins ? plugins : [];
        
        var totalcount = this.selectionModel.getCount(),
            selectedRecords = [],
            fixedFields = (button.hasOwnProperty('fixedFields') && Ext.isArray(button.fixedFields)) ? Ext.encode(button.fixedFields) : null,
            editDialogClass = this.editDialogClass || Tine[this.app.appName][this.recordClass.getMeta('modelName') + 'EditDialog'];
        
        // add "multiple_edit_dialog" plugin to dialog, if required
        if(((totalcount > 1) && (this.multipleEdit) && (button.actionType == 'edit'))) {
            Ext.each(this.selectionModel.getSelections(), function(record) {
                selectedRecords.push(record.data);
            }, this );
            
            plugins.push({
                ptype: 'multiple_edit_dialog', 
                selectedRecords: selectedRecords,
                selectionFilter: this.selectionModel.getSelectionFilter(),
                isFilterSelect: this.selectionModel.isFilterSelect,
                totalRecordCount: totalcount
            });
        }
        
        var config = null,
            popupWindow = editDialogClass.openWindow(Ext.copyTo(
            this.editDialogConfig || {}, {
                plugins: plugins ? Ext.encode(plugins) : null,
                fixedFields: fixedFields,
                record: editDialogClass.prototype.mode == 'local' ? Ext.encode(record.data) : record,
                copyRecord: (button.actionType == 'copy'),
                listeners: {
                    scope: this,
                    'update': ((this.selectionModel.getCount() > 1) && (this.multipleEdit)) ? this.onUpdateMultipleRecords : this.onUpdateRecord
                }
            }, 'record,listeners,fixedFields,copyRecord,plugins')
        );
        return true;
    },

    /**
     * is called after multiple records have been updated
     */
    onUpdateMultipleRecords: function() {
        this.store.reload();
    },

    /**
     * on update after edit
     * @param {String|Tine.Tinebase.data.Record} record
     */
    onUpdateRecord: function(record, mode) {

        if (Ext.isString(record) && this.recordProxy) {
            record = this.recordProxy.recordReader({responseText: record});
        } else if (record && Ext.isFunction(record.copy)) {
            record = record.copy();
        }

        Tine.log.debug('Tine.widgets.grid.GridPanel::onUpdateRecord() -> record:');
        Tine.log.debug(record);
        
        if (record && Ext.isFunction(record.copy)) {
            var idx = this.getStore().indexOfId(record.id);
            if (idx >=0) {
                var isSelected = this.getGrid().getSelectionModel().isSelected(idx);
                this.getStore().removeAt(idx);
                this.getStore().insert(idx, [record]);

                if (isSelected) {
                    this.getGrid().getSelectionModel().selectRow(idx, true);
                }
            } else {
                this.getStore().add([record]);
            }
            this.addToEditBuffer(record);
        }

        if (mode == 'local') {
            this.onStoreUpdate(this.getStore(), record, Ext.data.Record.EDIT);
        } else {
            this.loadGridData({
                removeStrategy: 'keepBuffered'
            });
        }
    },

    
    /**
     * is called to resolve conflicts from 2 records
     */
    onResolveDuplicates: function() {
        // TODO: allow more than 2 records      
        if(this.grid.getSelectionModel().getSelections().length != 2) return;
        
        var selections = [];
        Ext.each(this.grid.getSelectionModel().getSelections(), function(sel) {
            selections.push(sel.data);
        });
        
        var window = Tine.widgets.dialog.DuplicateMergeDialog.getWindow({
            selections: Ext.encode(selections),
            appName: this.app.name,
            modelName: this.recordClass.getMeta('modelName')
        });
        
        window.on('contentschange', function() { this.store.reload(); }, this);
    },
    
    /**
     * add record to edit buffer
     * 
     * @param {String|Tine.Tinebase.data.Record} record
     */
    addToEditBuffer: function(record) {

        var recordData = (Ext.isString(record)) ? Ext.decode(record) : record.data,
            id = recordData[this.recordClass.getMeta('idProperty')];

        if (this.editBuffer.indexOf(id) === -1) {
            this.editBuffer.push(id);
        }
    },

    /**
     * generic delete handler
     */
    onDeleteRecords: function(btn, e) {
        var sm = this.grid.getSelectionModel();

        if (sm.isFilterSelect && ! this.filterSelectionDelete) {
            Ext.MessageBox.show({
                title: _('Not Allowed'), 
                msg: _('You are not allowed to delete all pages at once'),
                buttons: Ext.Msg.OK,
                icon: Ext.MessageBox.INFO
            });

            return;
        }
        var records = sm.getSelections();

        if (Tine[this.app.appName].registry.containsKey('preferences') 
            && Tine[this.app.appName].registry.get('preferences').containsKey('confirmDelete')
            && Tine[this.app.appName].registry.get('preferences').get('confirmDelete') == 0
        ) {
            // don't show confirmation question for record deletion
            this.deleteRecords(sm, records);
        } else {
            var recordNames = records[0].get(this.recordClass.getMeta('titleProperty'));
            if (records.length > 1) {
                recordNames += ', ...';
            }

            var i18nQuestion = this.i18nDeleteQuestion ?
                this.app.i18n.n_hidden(this.i18nDeleteQuestion[0], this.i18nDeleteQuestion[1], records.length) :
                String.format(Tine.Tinebase.translation.ngettext('Do you really want to delete the selected record ({0})?',
                    'Do you really want to delete the selected records ({0})?', records.length), recordNames);
            Ext.MessageBox.confirm(_('Confirm'), i18nQuestion, function(btn) {
                if(btn == 'yes') {
                    this.deleteRecords(sm, records);
                }
            }, this);
        }
    },

    /**
     * delete records
     * 
     * @param {SelectionModel} sm
     * @param {Array} records
     */
    deleteRecords: function(sm, records) {
        // directly remove records from the store (only for non-filter-selection)
        if (Ext.isArray(records) && ! (sm.isFilterSelect && this.filterSelectionDelete)) {
            Ext.each(records, function(record) {
                this.store.remove(record);
            });
        }

        if (this.recordProxy) {
            if (this.usePagingToolbar) {
                this.pagingToolbar.refresh.disable();
            }

            var i18nItems = this.app.i18n.n_hidden(this.recordClass.getMeta('recordName'), this.recordClass.getMeta('recordsName'), records.length),
                recordIds = [].concat(records).map(function(v){ return v.id; });

            if (sm.isFilterSelect && this.filterSelectionDelete) {
                if (! this.deleteMask) {
                    this.deleteMask = new Ext.LoadMask(this.grid.getEl(), {
                        msg: String.format(_('Deleting {0}'), i18nItems) + _(' ... This may take a long time!')
                    });
                }
                this.deleteMask.show();
            }

            this.deleteQueue = this.deleteQueue.concat(recordIds);

            var options = {
                scope: this,
                success: function() {
                    this.refreshAfterDelete(recordIds);
                    this.onAfterDelete(recordIds);
                },
                failure: function (exception) {
                    this.refreshAfterDelete(recordIds);
                    this.loadGridData();
                    Tine.Tinebase.ExceptionHandler.handleRequestException(exception);                }
            };

            if (sm.isFilterSelect && this.filterSelectionDelete) {
                this.recordProxy.deleteRecordsByFilter(sm.getSelectionFilter(), options);
            } else {
                this.recordProxy.deleteRecords(records, options);
            }
        }
    },

    /**
     * refresh after delete (hide delete mask or refresh paging toolbar)
     */
    refreshAfterDelete: function(ids) {
        this.deleteQueue = this.deleteQueue.diff(ids);

        if (this.deleteMask) {
            this.deleteMask.hide();
        }

        if (this.usePagingToolbar) {
            this.pagingToolbar.refresh.show();
        }
    },

    /**
     * do something after deletion of records
     * - reload the store
     * 
     * @param {Array} [ids]
     */
    onAfterDelete: function(ids) {
        this.editBuffer = this.editBuffer.diff(ids);

        this.loadGridData({
            removeStrategy: 'keepBuffered'
        });
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.Tinebase.widgets.keyfield');

/**
 * @namespace   Tine.Tasks
 * @class       Tine.Tinebase.widgets.keyfield.Filter
 * @extends     Tine.widgets.grid.FilterModel
 * 
 * @author      Philipp Schüle <p.schuele@metaways.de>
 */
Tine.Tinebase.widgets.keyfield.Filter = Ext.extend(Tine.widgets.grid.FilterModel, {
    /**
     * @property Tine.Tinebase.Application app
     */
    app: null,
    
    /**
     * @cfg
     * @type String
     */
    keyfieldName: null,
    
    defaultOperator: 'in',
    
    /**
     * @private
     */
    initComponent: function() {
        this.operators = ['in', 'notin'];
        
        Tine.Tinebase.widgets.keyfield.Filter.superclass.initComponent.call(this);
    },
    
    /**
     * value renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    valueRenderer: function(filter, el) {
        var value = new Tine.Tinebase.widgets.keyfield.FilterValueField({
            app: this.app,
            filter: filter,
            width: 200,
            id: 'tw-ftb-frow-valuefield-' + filter.id,
            value: filter.data.value ? filter.data.value : this.defaultValue,
            renderTo: el,
            keyfieldName: this.keyfieldName
        });
        value.on('specialkey', function(field, e){
             if(e.getKey() == e.ENTER){
                 this.onFiltertrigger();
             }
        }, this);
        value.on('select', this.onFiltertrigger, this);
        
        return value;
    }
});

Tine.widgets.grid.FilterToolbar.FILTERS['tine.widget.keyfield.filter'] = Tine.Tinebase.widgets.keyfield.Filter;

/**
 * @namespace   Tine.Tasks
 * @class       Tine.Tinebase.widgets.keyfield.FilterValueField
 * @extends     Ext.ux.form.LayerCombo
 * 
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * 
 * TODO think about using Tine.widgets.grid.PickerFilterValueField
 */
Tine.Tinebase.widgets.keyfield.FilterValueField = Ext.extend(Ext.ux.form.LayerCombo, {
    hideButtons: false,
    formConfig: {
        labelAlign: 'left',
        labelWidth: 30
    },
    
    /**
     * @property Tine.Tinebase.Application app
     */
    app: null,
    
    /**
     * @cfg
     * @type String
     */
    keyfieldName: null,
    
    getFormValue: function() {
        var ids = [];
        var keyfieldStore = Tine.Tinebase.widgets.keyfield.StoreMgr.get(this.app.appName, this.keyfieldName);
        
        var formValues = this.getInnerForm().getForm().getValues();
        for (var id in formValues) {
            if (formValues[id] === 'on' && keyfieldStore.getById(id)) {
                ids.push(id);
            }
        }
        
        return ids;
    },
    
    getItems: function() {
        var items = [];
        Tine.Tinebase.widgets.keyfield.StoreMgr.get(this.app.appName, this.keyfieldName).each(function(keyfieldRecord) {
            var checkbox = {
                xtype: 'checkbox',
                boxLabel: keyfieldRecord.get('i18nValue'),
                icon: keyfieldRecord.get('icon'),
                name: keyfieldRecord.get('id')
            };
            
            if (checkbox.icon) {
                checkbox.boxLabel = '<img src="' + keyfieldRecord.get('icon') + '" class="tine-keyfield-icon"/>' + checkbox.boxLabel;
            }
            
            items.push(checkbox);
        }, this);
        
        return items;
    },
    
    /**
     * @param {String} value
     * @return {Ext.form.Field} this
     */
    setValue: function(value) {
        value = Ext.isArray(value) ? value : [value];
        
        var keyfieldRecordText = [];
        this.currentValue = [];
        
        Tine.Tinebase.widgets.keyfield.StoreMgr.get(this.app.appName, this.keyfieldName).each(function(keyfieldRecord) {
            var id = keyfieldRecord.get('id');
            var name = keyfieldRecord.get('i18nValue');
            if (value.indexOf(id) >= 0) {
                keyfieldRecordText.push(name);
                this.currentValue.push(id);
            }
        }, this);
        
        this.setRawValue(keyfieldRecordText.join(', '));
        
        return this;
    },
    
    /**
     * sets values to innerForm
     */
    setFormValue: function(value) {
        this.getInnerForm().getForm().items.each(function(item) {
            item.setValue(value.indexOf(item.name) >= 0 ? 'on' : 'off');
        }, this);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.widgets', 'Tine.widgets.tree');

/**
 * generic tree loader for tine trees
 * - calls json method with a filter to return children of a node
 * 
 * @namespace   Tine.widgets.tree
 * @class       Tine.widgets.tree.Loader
 * @extends     Ext.tree.TreeLoader
 */
Tine.widgets.tree.Loader = Ext.extend(Ext.tree.TreeLoader, {
    /**
     * @cfg {Number} how many chars of the containername to display
     */
    displayLength: 25,
    
    /**
     * @cfg {application}
     */
    app: null,
    
    /**
     * 
     * @cfg {String} method
     */
    method: null,
    
    /**
     * 
     * @cfg {Array} of filter objects for search method 
     */
    filter: null,
    
    url: 'index.php',
    
    /**
     * @private
     */
    createNode: function() {
        this.inspectCreateNode.apply(this, arguments);
        return Tine.widgets.tree.Loader.superclass.createNode.apply(this, arguments);
    },
    
    /**
     * returns params for async request
     * 
     * @param {Ext.tree.TreeNode} node
     * @return {Object}
     */
    getParams: function(node) {
        return {
            method: this.method,
            filter: this.filter
        };
    },
    
    /**
     * template fn for subclasses to inspect createNode
     * 
     * @param {Object} attr
     */
    inspectCreateNode: Ext.EmptyFn,
    
    processResponse: function(response, node, callback, scope) {
        // convert tine search response into usual treeLoader structure
        var o = response.responseData || Ext.decode(response.responseText);
        if (o.totalcount) {
            // take results part as response only
            response.responseData = o.results;
        }
        
        return Tine.widgets.tree.Loader.superclass.processResponse.apply(this, arguments);
    }
 });
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.widgets', 'Tine.widgets.tree');

/**
 * returns generic tree context menu with
 * - create/add
 * - rename
 * - delete
 * - edit grants
 * 
 * ctxNode class var is required in calling class
 */
Tine.widgets.tree.ContextMenu = {
    
    /**
     * create new Ext.menu.Menu with actions
     * 
     * @param {} config has the node name, actions, etc.
     * @return {}
     */
    getMenu: function(config) {
        
        this.config = config;
                
        /****************** create ITEMS array ****************/
              
        this.action_add = new Ext.Action({
            text: String.format(_('Add {0}'), this.config.nodeName),
            iconCls: 'action_add',
            handler: this.addNode,
            requiredGrant: 'addGrant',
            scope: this.config
        });
        
        this.action_rename = new Ext.Action({
            text: String.format(_('Rename {0}'), this.config.nodeName),
            iconCls: 'action_rename',
            handler: this.renameNode,
            scope: this.config,
            requiredGrant: 'editGrant',
            allowMultiple: false
        });
        
        this.action_delete = new Ext.Action({
            text: String.format(Tine.Tinebase.translation.ngettext('Delete {0}', 'Delete {0}', 1), this.config.nodeName),
            iconCls: 'action_delete',
            handler: this.deleteNode,
            scope: this.config,
            requiredGrant: 'deleteGrant',
            allowMultiple: true
        });
        
        this.action_grants = new Ext.Action({
            text: String.format(_('Manage {0} Permissions'), this.config.nodeName),
            iconCls: 'action_managePermissions',
            handler: this.managePermissions,
            requiredGrant: 'editGrant',
            scope: this.config
        });
        
        this.action_properties = new Ext.Action({
            text: String.format(_('{0} Properties'), this.config.nodeName),
            iconCls: 'action_manageProperties',
            handler: this.manageProperties,
            requiredGrant: 'readGrant',
            scope: this.config
        });
        
        // TODO is edit grant required?
        this.action_changecolor = new Ext.Action({
            text: String.format(_('Set {0} color'), this.config.nodeName),
            iconCls: 'action_changecolor',
//            requiredGrant: 'editGrant',
            allowMultiple: true,
            menu: new Ext.menu.ColorMenu({
                scope: this,
                listeners: {
                    select: this.changeNodeColor,
                    scope: this.config
                }
            })
        });
        
        this.action_reload = new Ext.Action({
            text: String.format(_('Reload {0}'), this.config.nodeName),
            iconCls: 'x-tbar-loading',
            handler: this.reloadNode,
            scope: this.config
        });
        
        // TODO move the next 3 to Filemanager!
        this.action_resume = new Ext.Action({
            text: String.format(_('Resume upload'), config.nodeName),
            iconCls: 'action_resume',
            handler: this.onResume,
            scope: this.config,
            actionUpdater: this.isResumeEnabled
        });
        this.action_editFile = new Ext.Action({
            requiredGrant: 'editGrant',
            allowMultiple: false,
            text: _('Edit Properties'),
            handler: this.onEditFile,
            iconCls: 'action_edit_file',
            actionType: 'edit',
            scope: this
        });
        this.action_pause = new Ext.Action({
            text: String.format(_('Pause upload'), config.nodeName),
            iconCls: 'action_pause',
            handler: this.onPause,
            actionUpdater: this.isPauseEnabled,
            scope: this.config
        });
        
        this.action_download = new Ext.Action({
            text: String.format(_('Download'), config.nodeName),
            iconCls: 'action_filemanager_save_all',
            handler: this.downloadFile,
            actionUpdater: this.isDownloadEnabled,
            scope: this.config
        });
        
        var items = [];
        for (var i=0; i < config.actions.length; i++) {
            switch(config.actions[i]) {
                case 'add':
                    items.push(this.action_add);
                    break;
                case 'delete':                    
                    items.push(this.action_delete);
                    break;
                case 'rename':
                    items.push(this.action_rename);
                    break;
                case 'properties':
                    items.push(this.action_properties);
                    break;
                case 'changecolor':
                    items.push(this.action_changecolor);
                    break;
                case 'grants':
                    items.push(this.action_grants);
                    break;
                case 'reload':
                    items.push(this.action_reload);
                    break;
                case 'resume':
                    items.push(this.action_resume);
                    break;
                case 'pause':
                    items.push(this.action_pause);
                    break;
                case 'download':
                    items.push(this.action_download);
                    break;
                case 'edit':
                    items.push(this.action_editFile);
                    break;
                default:
                    // add custom actions
                    items.push(new Ext.Action(config.actions[i]));
            }
        }

             
        /******************* return menu **********************/
        
        return new Ext.menu.Menu({
            items: items
        });
    },
    
    /**
     * create tree node
     */
    addNode: function() {
        Ext.MessageBox.prompt(String.format(_('New {0}'), this.nodeName), String.format(_('Please enter the name of the new {0}:'), this.nodeName), function(_btn, _text) {
            if( this.scope.ctxNode && _btn == 'ok') {
                if (! _text) {
                    Ext.Msg.alert(String.format(_('No {0} added'), this.nodeName), String.format(_('You have to supply a {0} name!'), this.nodeName));
                    return;
                }
                Ext.MessageBox.wait(_('Please wait'), String.format(_('Creating {0}...' ), this.nodeName));
                var parentNode = this.scope.ctxNode;
                
                var params = {
                    method: this.backend + '.add' + this.backendModel,
                    name: _text
                };
                
                // TODO try to generalize this and move app specific stuff to app
                
                if (this.backendModel == 'Node') {
                    params.application = this.scope.app.appName || this.scope.appName;
                    var filename = parentNode.attributes.nodeRecord.data.path + '/' + _text;
                    params.filename = filename;
                    params.type = 'folder';
                    params.method = this.backend + ".createNode";
                }
                else if (this.backendModel == 'Container') {
                    params.application = this.scope.app.appName || this.scope.appName;
                    params.containerType = Tine.Tinebase.container.path2type(parentNode.attributes.path);
                    params.modelName = this.scope.app.getMainScreen().getActiveContentType();
                } 
                else if (this.backendModel == 'Folder') {
                    var parentFolder = Tine.Tinebase.appMgr.get('Felamimail').getFolderStore().getById(parentNode.attributes.folder_id);
                    params.parent = parentFolder.get('globalname');
                    params.accountId = parentFolder.get('account_id');
                }
                
                Ext.Ajax.request({
                    params: params,
                    scope: this,
                    success: function(result, request){
                        var nodeData = Ext.util.JSON.decode(result.responseText);

                        // TODO add + icon if it wasn't expandable before
                        if(nodeData.type == 'folder') {
                            var nodeData = Ext.util.JSON.decode(result.responseText);

                            var app = Tine.Tinebase.appMgr.get(this.scope.app.appName);
                            var newNode = app.getMainScreen().getWestPanel().getContainerTreePanel().createTreeNode(nodeData, parentNode);
                            parentNode.appendChild(newNode);
                        }
                        else {
                            var newNode = this.scope.loader.createNode(nodeData);
                            parentNode.appendChild(newNode);
                        }
                        
                        parentNode.expand();
                        this.scope.fireEvent('containeradd', nodeData);
                                              
                        Ext.MessageBox.hide();
                    },
                    failure: function(result, request) {
                        var nodeData = Ext.util.JSON.decode(result.responseText);
                        
                        var appContext = Tine[this.scope.app.appName];
                        if(appContext && appContext.handleRequestException) {
                            appContext.handleRequestException(nodeData.data);
                        }
                    }
                });
                
            }
        }, this);
    },
    
    /**
     * rename tree node
     */
    renameNode: function() {
        if (this.scope.ctxNode) {
                       
            var node = this.scope.ctxNode;
            Ext.MessageBox.show({
                title: String.format(_('Rename {0}'), this.nodeName),
                msg: String.format(_('Please enter the new name of the {0}:'), this.nodeName),
                buttons: Ext.MessageBox.OKCANCEL,
                value: node.text,
                fn: function(_btn, _text){
                    if (_btn == 'ok') {
                        if (! _text) {
                            Ext.Msg.alert(String.format(_('Not renamed {0}'), this.nodeName), String.format(_('You have to supply a {0} name!'), this.nodeName));
                            return;
                        }
                        
                        var params = {
                            method: this.backend + '.rename' + this.backendModel,
                            newName: _text
                        };
                        
                        params.application = this.scope.app.appName || this.scope.appName;

                        if (this.backendModel == 'Node') {
                            var filename = node.attributes.path;
                            params.sourceFilenames = [filename];
                            
                            var targetFilename = "/";
                            var sourceSplitArray = filename.split("/");
                            for (var i=1; i<sourceSplitArray.length-1; i++) {
                                targetFilename += sourceSplitArray[i] + '/';
                            }
                            
                            params.destinationFilenames = [targetFilename + _text];
                            params.method = this.backend + '.moveNodes';
                        }
                        
                        // TODO try to generalize this
                        if (this.backendModel == 'Container') {
                            params.containerId = node.attributes.container.id;
                        } else if (this.backendModel == 'Folder') {
                            var folder = Tine.Tinebase.appMgr.get('Felamimail').getFolderStore().getById(node.attributes.folder_id);
                            params.oldGlobalName = folder.get('globalname');
                            params.accountId = folder.get('account_id');
                        }
                        
                        Ext.Ajax.request({
                            params: params,
                            scope: this,
                            success: function(_result, _request){
                            
                                var nodeData = Ext.util.JSON.decode(_result.responseText);
                                node.setText(_text);
                                
                                this.scope.fireEvent('containerrename', nodeData, node, _text);
                                                              
                            },
                            failure: function(result, request) {
                                var nodeData = Ext.util.JSON.decode(result.responseText);
                                
                                var appContext = Tine[this.scope.app.appName];
                                if(appContext && appContext.handleRequestException) {
                                    appContext.handleRequestException(nodeData.data);
                                }
                            }
                        });
                    }
                },
                scope: this,
                prompt: true,
                icon: Ext.MessageBox.QUESTION
            });
        }
    },
    
    /**
     * delete tree node
     */
    deleteNode: function() {
        
        if (this.scope.ctxNode) {
            var node = this.scope.ctxNode;
            
            Tine.log.debug('Tine.widgets.tree.ContextMenu::deleteNode()');
            Tine.log.debug(node);
            
            Ext.MessageBox.confirm(_('Confirm'), String.format(_('Do you really want to delete the {0} "{1}"?'), this.nodeName, node.text), function(_btn){
                if ( _btn == 'yes') {
                    
                    var params = {
                        method: this.backend + '.delete' + this.backendModel
                    };
                    
                    if (this.backendModel == 'Node') {
                                           
                        var filenames = [node.attributes.path];
                        params.application = this.scope.app.appName || this.scope.appName;
                        params.filenames = filenames;
                        params.method = this.backend + ".deleteNodes";
                    
                    } else if (this.backendModel == 'Container') {
                        params.containerId = node.attributes.container.id;
                    } else if (this.backendModel == 'Folder') {
                        var folder = Tine.Tinebase.appMgr.get('Felamimail').getFolderStore().getById(node.attributes.folder_id);
                        params.folder = folder.get('globalname');
                        params.accountId = folder.get('account_id');
                    } else {
                        // use default json api style
                        params.ids = [node.id];
                        params.method = params.method + 's';
                    }
                    
                    Ext.Ajax.request({
                        params: params,
                        scope: this,
                        success: function(_result, _request){
                              
                            if(node) {
                                if(node.isSelected()) {
                                    this.scope.getSelectionModel().select(node.parentNode);
                                    this.scope.fireEvent('click', node.parentNode, Ext.EventObject.setEvent());
                                }

                                node.remove();
                                if (this.backendModel == 'Container') {
                                    this.scope.fireEvent('containerdelete', node.attributes.container);
                                } else if (this.backendModel == 'Node') {
                                    this.scope.fireEvent('containerdelete', node);
                                } else {
                                    this.scope.fireEvent('containerdelete', node.attributes);
                                }
                            }
                           
                        },
                        failure: function(result, request) {
                            var nodeData = Ext.util.JSON.decode(result.responseText);
                            var appContext = Tine[this.scope.app.appName];
                            if(appContext && appContext.handleRequestException) {
                                appContext.handleRequestException(nodeData.data);
                            }
                        }
                    });
                }
            }, this);
        }
    },
    
    /**
     * change tree node color
     */
    changeNodeColor: function(cp, color) {
        if (this.scope.ctxNode) {
            var node = this.scope.ctxNode;
            node.getUI().addClass("x-tree-node-loading");
                Ext.Ajax.request({
                    params: {
                        method: this.backend + '.set' + this.backendModel + 'Color',
                        containerId: node.attributes.container.id,
                        color: '#' + color
                    },
                    scope: this,
                    success: function(_result, _request){
                        var nodeData = Ext.util.JSON.decode(_result.responseText);
                        node.getUI().colorNode.setStyle({color: nodeData.color});
                        node.attributes.color = nodeData.color;
                        this.scope.fireEvent('containercolorset', nodeData);
                        node.getUI().removeClass("x-tree-node-loading");
                    },
                    failure: function(result, request) {
                        var nodeData = Ext.util.JSON.decode(result.responseText);
                        
                        var appContext = Tine[this.scope.app.appName];
                        if(appContext && appContext.handleRequestException) {
                            appContext.handleRequestException(nodeData.data);
                        }
                    }
                });
        
        }
    },
    
    /**
     * manage permissions
     * 
     */
    managePermissions: function() {

        if (this.scope.ctxNode) {
            var node = this.scope.ctxNode,
                grantsContainer;
            if(node.attributes.nodeRecord && node.attributes.nodeRecord.data.name) {
                grantsContainer = node.attributes.nodeRecord.data.name;
            } else if(node.attributes.container) {
                grantsContainer = node.attributes.container;
            }
            
            var window = Tine.widgets.container.GrantsDialog.openWindow({
                title: String.format(_('Manage Permissions for {0} "{1}"'), this.nodeName, Ext.util.Format.htmlEncode(grantsContainer.name)),
                containerName: this.nodeName,
                grantContainer: grantsContainer,
                app: this.scope.app.appName
            });
        }
        
    },
    
    /**
     * manage properties
     * 
     */
    manageProperties: function() {
        if (this.scope.ctxNode) {
            var node = this.scope.ctxNode,
                grantsContainer;
            if(node.attributes.nodeRecord && node.attributes.nodeRecord.data.name) {
                grantsContainer = node.attributes.nodeRecord.data.name;
            } else if(node.attributes.container) {
                grantsContainer = node.attributes.container;
            }
            
            var window = Tine.widgets.container.PropertiesDialog.openWindow({
                title: String.format(_('Properties for {0} "{1}"'), this.nodeName, Ext.util.Format.htmlEncode(grantsContainer.name)),
                containerName: this.nodeName,
                grantContainer: grantsContainer,
                app: this.scope.app.appName
            });
        }
    },
    
    /**
     * reload node
     */
    reloadNode: function() {
        if (this.scope.ctxNode) {
            var tree = this.scope;
            this.scope.ctxNode.reload(function(node) {
                node.expand();
                node.select();
                // update grid
                tree.filterPlugin.onFilterChange();
            });
        }
    }
    
};
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 */
Ext.ns('Tine.widgets', 'Tine.widgets.tree');

/**
 * filter plugin for container tree
 * 
 * @namespace Tine.widgets.tree
 * @class     Tine.widgets.tree.FilterPlugin
 * @extends   Tine.widgets.grid.FilterPlugin
 */
Tine.widgets.tree.FilterPlugin = Ext.extend(Tine.widgets.grid.FilterPlugin, {
    /**
     * @cfg {Tree Panel} treePanel (required)
     */
    treePanel: null,
    
    /**
     * @cfg field
     * @type String
     */
    field: 'container_id',

    /**
     * @cfg nodeAttributeField
     * @type String
     */
    nodeAttributeField: 'container',
    
    /**
     * @cfg singleNodeOperator
     * @type String
     */
    singleNodeOperator: 'equals',
    
    /**
     * @cfg selectNodes
     * @type Boolean
     */
    selectNodes: true,
    
    /**
     * get container filter object
     * 
     * @return {Object}
     */
    getFilter: function() {
        var filter = {field: this.field},
            sm = this.treePanel.getSelectionModel(),
            multiSelection = typeof sm.getSelectedNodes == 'function',
            selection = multiSelection ? sm.getSelectedNodes() : [sm.getSelectedNode()];
        
        filter.operator = multiSelection ? 'in' : this.singleNodeOperator;
            
        var values = [];
        Ext.each(selection, function(node) {
            if (node) {
                values.push(node.attributes[this.nodeAttributeField]);
            }
        }, this);
        
        filter.value = Ext.isEmpty(values) ? '' : filter.operator === 'in' ? values : values[0];
        return filter;
    },
    
    /**
     * gets value of this container filter
     */
    getValue: function() {
        // only return values if gridFilter mode
        if (this.treePanel.filterMode !== 'gridFilter') {
            return null;
        }
        
        return this.getFilter();
    },
    
    /**
     * sets the selected container (node) of this tree
     * 
     * @param {Array} all filters
     */
    setValue: function(filters) {
        if (! this.selectNodes) {
            return null;
        }

        var sm = this.treePanel.getSelectionModel();
        sm.clearSelections(true);
        
        // use first OR panel in case of filterPanel
        Ext.each(filters, function(filterData) {
            if (filterData.condition && filterData.condition == 'OR') {
                filters = filterData.filters[0].filters;
                return false;
            }
        }, this);
        
        Ext.each(filters, function(filter) {
            if (filter.field !== this.field || Ext.isEmpty(filter.value)) {
                return;
            }
            
            // save active Element, so we can restore focus after selecting nodes
            this.lastFocusEl = document.activeElement;
            
            // save west panel scrolling position so we can restore it after selecting nodes
            if (this.treePanel.app.getMainScreen().getWestPanel().body) {
                this.leftPanelScrollTop = this.treePanel.app.getMainScreen().getWestPanel().body.getScroll().top;
            }
            
            this.treePanel.getSelectionModel().suspendEvents();
            this.selectValue(filter.value);
        }, this);
    },
    
    /**
     * select tree node(s)
     * 
     * @param {String} value
     */
    selectValue: function(value) {
        var values = Ext.isArray(value) ? value : [value];
        Ext.each(values, function(value, idx) {
            if (Ext.isString(value) && ! value.path) {
                value = values[idx] = {path: value};
            }
            
            var treePath = this.treePanel.getTreePath(value.path);
            this.selectPath.call(this.treePanel, treePath, null, function() {
                // mark this expansion as done and check if all are done
                value.isExpanded = true;
                var allValuesExpanded = true;
                Ext.each(values, function(v) {
                    allValuesExpanded &= v.isExpanded;
                }, this);
                
                if (allValuesExpanded) {
                    this.treePanel.getSelectionModel().resumeEvents();
                    (function() {
                        try {
                            if (this.lastFocusEl) {
                                var scroller = Ext.fly(this.lastFocusEl).up('div[class$=-scroller]'),
                                    scrollTop = scroller ? scroller.dom.scrollTop : null;
                                // TODO: is this needed (the element is already focused, atm. IE breaks (https://forge.tine20.org/mantisbt/view.php?id=6916))?
                                if(!Ext.isIE) {
                                    Ext.fly(this.lastFocusEl).focus();
                                }
                                
                                if (scrollTop) scroller.dom.scrollTop = scrollTop;
                            }
                            
                            if (this.leftPanelScrollTop) {
                                this.treePanel.app.getMainScreen().getWestPanel().body.dom.scrollTop = this.leftPanelScrollTop;
                            }
                        } catch (e) {}
                    }).defer(10, this);
                    
                }
            }.createDelegate(this), true);
        }, this);
    },
    
    /**
     * Selects the node in this tree at the specified path. A path can be retrieved from a node with {@link Ext.data.Node#getPath}
     * @param {String} path
     * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
     * @param {Function} callback (optional) The callback to call when the selection is complete. The callback will be called with
     * (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node.
     * @param {keep} bool keep current selection
     */
    selectPath : function(path, attr, callback, keep){
        attr = attr || 'id';
        var keys = path.split(this.pathSeparator),
            v = keys.pop();
        if(keys.length > 1){
            var f = function(success, node){
                if(success && node) {
                    var n = node.findChild(attr, v) || node.findChild('path', node.attributes.path + '/' + v);
                    if(n){
                        n.getOwnerTree().getSelectionModel().select(n, false, keep);
                        if(callback){
                            callback(true, n);
                        }
                    }else if(callback){
                        callback(false, n);
                    }
                }else{
                    if(callback){
                        callback(false, n);
                    }
                }
            };
            this.expandPath(keys.join(this.pathSeparator), attr, f);
        }else{
            this.root.select();
            if(callback){
                callback(true, this.root);
            }
        }
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Tine.widgets', 'Tine.widgets.customfields');

Tine.widgets.customfields.ConfigManager = function() {
    var stores = {};
    
    var getStore = function(app) {
        app = Tine.Tinebase.appMgr.get(app);
        if (! stores[app.appName]) {
            var allCfs = (Ext.isFunction(app.getRegistry)) ? app.getRegistry().get('customfields') : null;
            stores[app.appName] = new Ext.data.JsonStore({
                fields: Tine.Tinebase.Model.Customfield,
                data: allCfs ? allCfs : []
            });
            
            // place keyFieldConfig in registry so we can use the standard widgets
            stores[app.appName].each(function(cfConfig) {
                var definition = cfConfig.get('definition'),
                    options = definition.options ? definition.options : {},
                    keyFieldConfig = definition.keyFieldConfig ? definition.keyFieldConfig : null;
                    
                if (keyFieldConfig) {
                    app.getRegistry().get('config')[cfConfig.get('name')] = keyFieldConfig;
                }
            });
        }
        
        return stores[app.appName];
    };
    
    /**
     * convert config record to record field
     * 
     * @param  {Record} cfConfig
     * @return {Ext.data.Field} field definition
     */
    var config2Field = function(cfConfig) {
        var def = cfConfig.get('definition');
        
        return new Ext.data.Field(Ext.apply({
            name: '#' + cfConfig.get('name')
        }, def));
    };
    
    return {
        /**
         * returns a single field config
         * 
         * @param {String/Application}  app
         * @param {String}              model
         * @param {String}              name
         * @param {Boolean}             asField
         * @return {Record}
         */
        getConfig: function (app, model, name, asField) {
            var cfStore = getStore(app),
                cfConfig = null;
            
            cfStore.clearFilter(true);
            cfStore.filter('model', model);
            cfConfig = cfStore.findExact('name', name);
            cfConfig = cfConfig > -1 ? cfStore.getAt(cfConfig): null;
            cfStore.clearFilter(true);
            
            return asField ? config2Field(cfConfig) : cfConfig;
            
        },
        
        /**
         * returns a single field config
         * 
         * @param {String/Application}  app
         * @param {String}              model
         * @param {Boolean}             asFields
         * @return {Array}
         */
        getConfigs: function(app, model, asFields) {
            if (Ext.isFunction(model.getMeta)) {
                model = model.getMeta('appName') + '_Model_' + model.getMeta('modelName');
            }
            
            var cfStore = getStore(app),
                cfConfigs = [];
            
            cfStore.clearFilter(true);
            cfStore.filter('model', model);
            cfStore.each(function(r) {cfConfigs.push(r);});
            cfStore.clearFilter(true);
            
            if (asFields) {
                Ext.each(cfConfigs, function(cfConfig, idx) {
                    cfConfigs[idx] = config2Field(cfConfig);
                }, this);
            }
            
            return cfConfigs;
        }
    }
}();/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Tine.widgets.customfields');

Tine.widgets.customfields.Field = {
    /**
     * get the form field 
     * 
     * @param {Tine.Tinebase.Application} app the application the customfield belongs to
     * @param {Tine.Tinebase.data.Record} cfConfig customfields config record
     * @param {Object} config additional field config
     * @param {Tine.widgets.dialog.EditDialog} editDialog the calling editdialog - just needed if record must not link itself
     * 
     * @return {Ext.form.Field}
     */
    get: function (app, cfConfig, config, editDialog) {
        Tine.log.debug(cfConfig);
        
        var def = cfConfig.get('definition'),
            uiConfig = def && def.uiconfig ? def.uiconfig : {},
            fieldDef = {
                fieldLabel: def.label,
                name: 'customfield_' + cfConfig.get('name'),
                xtype: (def.value_search == 1) ? 'customfieldsearchcombo' : uiConfig.xtype,
                customfieldId: cfConfig.id,
                readOnly: cfConfig.get('account_grants').indexOf('writeGrant') < 0
            };
            
            // auto xtype per data type
            // @todo support array of scalars
            // @todo suppot recordSets of model
            if (! uiConfig.xtype && def.type && ! def.value_search) {
                switch (Ext.util.Format.lowercase(def.type)) {
                    case 'keyfield':
                        var options = def.options ? def.options : {},
                            keyFieldConfig = def.keyFieldConfig ? def.keyFieldConfig : null;
                            
                        Ext.apply(fieldDef, {
                            xtype: 'widget-keyfieldcombo',
                            app: options.app ? options.app : app,
                            keyFieldName: options.keyFieldName ? options.keyFieldName : cfConfig.get('name'),
                            sortBy: 'id'
                        });
                        break;
                       case 'record':
                           var options = def.options ? def.options : {},
                            recordConfig = def.recordConfig ? def.recordConfig : null;
                            
                        Ext.apply(fieldDef, {
                            xtype: 'tinerecordpickercombobox',
                            app: options.app ? options.app : app,
                            resizable: true,
                            recordClass: eval(recordConfig.value.records),
                            allowLinkingItself: false,
                            editDialog: editDialog
                        });
                        break;
                    case 'integer':
                    case 'int':
                        fieldDef.xtype = 'numberfield';
                        break;
                    case 'date':
                        fieldDef.xtype = 'datefield';
                        fieldDef.listAlign = 'tr-br?';
                        break;
                    case 'time':
                        fieldDef.xtype = 'timefield';
                        fieldDef.listAlign = 'tr-br?';
                        break;
                    case 'datetime':
                        fieldDef.xtype = 'datetimefield';
                        fieldDef.listAlign = 'tr-br?';
                        break;
                    case 'boolean':
                    case 'bool':
                        fieldDef.xtype = 'checkbox';
                        break;
                    case 'string':
                    default:
                        fieldDef.xtype = 'textfield';
                        break;
                }
            }
            
        if (def.length) {
            fieldDef.maxLength = def.length;
        }
        
        if (def.required) {
            fieldDef.allowBlank = false;
        }
        
        try {
            var fieldObj = Ext.ComponentMgr.create(Ext.apply(fieldDef, config));
            return fieldObj;
        } catch (e) {
            Tine.log.debug(e);
            Tine.log.err('Unable to create custom field "' + cfConfig.get('name') + '". Check definition!');
        }
    }
};
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Tine.widgets', 'Tine.widgets.customfields');

/**
 * @namespace   Tine.widgets.customfields
 * @class       Tine.widgets.customfields.FilterModel
 * @extends     Tine.widgets.grid.FilterModel
 */
Tine.widgets.customfields.FilterModel = Ext.extend(Tine.widgets.grid.FilterModel, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     */
    app: null,
    
    /**
     * @cfg {Record} cfConfig
     */
    cfConfig: null,
    
    valueType: 'customfield',
    
    /**
     * @private
     */
    initComponent: function() {
        this.field = 'customfield:' + this.cfConfig.id;
        this.cfDefinition = this.cfConfig.get('definition');
        this.label =  this.cfDefinition.label;
        
        switch (this.cfDefinition.type) {
            case 'record':
            case 'keyField':
                this.operators = ['equals', 'not'];
                this.defaultOperator = 'equals';
                this.valueRenderer = this.cfValueRenderer;
                break;
            case 'bool':
            case 'boolean':
                this.valueType = 'bool';
                this.defaultOperator = 'equals';
                this.defaultValue = '0';
                break;
            case 'date':
            case 'datetime':
                this.valueType = 'date';
                this.defaultOperator = 'within';
        }
        
        Tine.widgets.customfields.FilterModel.superclass.initComponent.call(this);
    },
    
    /**
     * cf value renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    cfValueRenderer: function(filter, el) {
        // value
        var value = Tine.widgets.customfields.Field.get(this.app, this.cfConfig, {
            filter: filter,
            width: 200,
            id: 'tw-ftb-frow-valuefield-' + filter.id,
            value: filter.data.value ? filter.data.value : this.defaultValue,
            renderTo: el
        });
        value.on('specialkey', function(field, e){
             if(e.getKey() == e.ENTER){
                 this.onFiltertrigger();
             }
        }, this);
        return value;
    }
});
Tine.widgets.grid.FilterToolbar.FILTERS['tinebase.customfield'] = Tine.widgets.customfields.FilterModel;

/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
Ext.ns('Tine.widgets.customfields');

/**
 * manages key field renderers
 * 
 * @namespace   Tine.widgets.customfields
 * @class       Tine.widgets.customfields.Renderer
 * @singleton
 */
Tine.widgets.customfields.Renderer = function(){
    var renderers = {};
    
    return {
        /**
         * returns key field record renderer
         * 
         * @param {String/Application}  app
         * @param {Record}              cfConfig 
         * @param {String}              what pipe seperated field with text|icon
         * @return Ext.data.Store
         */
        get: function(app, cfConfig, what) {
            var appName = Ext.isString(app) ? app : app.appName,
                app = Tine.Tinebase.appMgr.get(appName),
                cfDefinition = cfConfig.get('definition'),
                cfName = cfConfig.get('name'),
                what = what ? what : 'text|icon',
                whatParts = what.split('|'),
                key = appName + cfConfig.id + what;
                
            if (! renderers[key]) {
                if (['keyfield' /*, 'bool', 'boolean'*/].indexOf(Ext.util.Format.lowercase(cfDefinition.type)) > -1) {
                    // NOTE existingkeyfields might come from an other app!
                    var app = cfDefinition.options && Ext.isString(cfDefinition.options.app) ? cfDefinition.options.app : app;
                    var keyFieldName = cfDefinition.options && Ext.isString(cfDefinition.options.keyFieldName) ? cfDefinition.options.keyFieldName : cfName;
                    renderers[key] = function(customfields) {
                        return Tine.Tinebase.widgets.keyfield.Renderer.render(app, keyFieldName, customfields[cfName]);
                    };
                    
                } else if (['record'].indexOf(Ext.util.Format.lowercase(cfDefinition.type)) > -1) {
                    renderers[key] = function(customfields) {
                        var recordClass = eval(cfDefinition.recordConfig.value.records);
                                                
                        return Ext.isObject(customfields[cfName]) && customfields[cfName].hasOwnProperty(recordClass.getMeta('titleProperty'))
                            ? customfields[cfName][recordClass.getMeta('titleProperty')]
                            : customfields[cfName];
                    };
                } else {
                    renderers[key] = function(customfields) {
                        switch (cfDefinition.type)
                        {
                            case 'date':
                                return Tine.Tinebase.common.dateRenderer(customfields[cfName]);
                            case 'datetime':
                                return Tine.Tinebase.common.dateTimeRenderer(customfields[cfName]);
                            case 'time':
                                return Tine.Tinebase.common.timeRenderer(customfields[cfName]);
                            case 'boolean':
                                return Tine.Tinebase.common.booleanRenderer(customfields[cfName]);
                            default:
                                return Ext.util.Format.htmlEncode(customfields[cfName]);
                        }
                    };
                }
            }
            
            return renderers[key];
        },
        
        /**
         * render a given value
         * 
         * @param {String/Application}  app
         * @param {Record}              cfConfig 
         * @return Ext.data.Store
         */
        render: function(app, cfConfig, id) {
            var renderer = this.get(app, cfConfig);
            
            return renderer(id);
        },
        
        /**
         * register a custom renderer
         * 
         * @param {String/Application}  app
         * @param {Record}              cfConfig 
         * @param {Function}            renderer
         */
        register: function(app, cfConfig, renderer) {
            var appName = Ext.isString(app) ? app : app.appName,
                cfDefinition = cfConfig.get('definition'),
                cfName = cfConfig.get('name'),
                key = appName + cfConfig.id;
                
            renderers[key] = renderer;
        }
    }
}();
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.widgets', 'Tine.widgets.customfields');

/**
 * CustomfieldsSearchCombo
 * 
 * @namespace   Tine.Tinebase.widgets.customfields
 * @class       Tine.Tinebase.widgets.customfields.CustomfieldSearchCombo
 * @extends     Ext.form.ComboBox
 * 
 * <p>Customfields Search Combo</p>
 * <p>
 * searches for custom field values 
 * 
 * <pre>
 * </pre></p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * @param       {Object} config
 * @constructor
 * Create a new Tine.Tinebase.widgets.customfields.CustomfieldSearchCombo
 */
Tine.widgets.customfields.CustomfieldSearchCombo = Ext.extend(Ext.form.ComboBox, {
    
    /**
     * @property customfieldId
     * @type String
     */
    customfieldId: null,
    
    /**
     * config
     */
    forceSelection: false,
    triggerAction: 'all',
    minChars: 1,
    pageSize: 10,
    displayField: 'value',
    valueField: 'value',
    enableKeyEvents: true,
    
    /**
     * @private
     */
    initComponent: function() {
        this.store = new Ext.data.JsonStore({
            fields: Tine.Tinebase.Model.CustomfieldValue,
            baseParams: {
                method: 'Tinebase.searchCustomFieldValues',
                sort: 'value',
                dir: 'ASC'
            },
            root: 'results',
            totalProperty: 'totalcount'
        });
        
        this.on('beforequery', this.onBeforeQuery, this);
        this.on('keypress', this.syncValues, this, {buffer: 500});
        
        Tine.widgets.customfields.CustomfieldSearchCombo.superclass.initComponent.call(this);
    },
    
    /**
     * prepare paging
     * 
     * @param {Ext.data.Store} store
     * @param {Object} options
     */
    onBeforeLoad: function(store, options) {
        options.params.paging = {
            start: options.params.start,
            limit: options.params.limit
        };
    },
    
    /**
     * sync values so values don't get lost if no value is explicitly selected
     * this happens e.g. when ctrl+enter after searching
     */
    syncValues: function() {
        var val = this.getRawValue();
        this.setValue(val);
    },
     
    /**
     * use beforequery to set query filter
     * 
     * @param {Object} qevent
     */
    onBeforeQuery: function(qevent){
        this.store.baseParams.filter = [
            {field: 'customfield_id',   operator: 'equals',     value: this.customfieldId },
            {field: 'value',            operator: 'group',      value: '' },
            {field: 'value',            operator: 'contains', value: qevent.query }
        ];
    }
});

Ext.reg('customfieldsearchcombo', Tine.widgets.customfields.CustomfieldSearchCombo);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Tine.widgets.customfields');

Tine.widgets.customfields.EditDialogPlugin = function (config) {
    Ext.apply(this, config);
};

Tine.widgets.customfields.EditDialogPlugin.prototype = {
    /**
     * @type Tine.Tinebase.Application app
     */
    app: null,
    
    /**
     * @type Tine.widgets.dialog.EditDialog editDialog
     */
    editDialog: null,
    
    init: function(editDialog) {
        this.editDialog = editDialog;
        
        // edit dialog without recordClass cannot have custom fields
        if(!this.editDialog.recordClass) {
            return;
        }
        
        this.app = Tine.Tinebase.appMgr.get(this.editDialog.app);
        this.customfieldsValue = [];
        
        // compute cf's for cf tab and add a cf tab on demand
        this.editDialog.on('beforerender', this.onBeforeRender, this);
        
        // fill/buffer all cf's with values
        this.editDialog.on('load', this.onRecordLoad, this);
        
        // get all cf values
        this.editDialog.onRecordUpdate = this.editDialog.onRecordUpdate.createSequence(this.onRecordUpdate, this);
    },
    
    /**
     * dispatch values from customfield property
     */
    onRecordLoad: function() {
        var form = this.editDialog.getForm(),
            modelName = this.editDialog.recordClass.getMeta('appName') + '_Model_' + this.editDialog.recordClass.getMeta('modelName'),
            name,
            field,
            cfConfig;

        this.customfieldsValue = this.editDialog.record.get('customfields') || {};
        
        for (name in this.customfieldsValue) {
            field = form.findField('customfield_' + name);
            cfConfig = Tine.widgets.customfields.ConfigManager.getConfig(this.app, modelName, name);
            
            if (cfConfig) {
                // transform datetime values
                if (['date', 'datetime'].indexOf(Ext.util.Format.lowercase(cfConfig.get('definition').type)) != -1) {
                    this.customfieldsValue[name] = Date.parseDate(this.customfieldsValue[name], Date.patterns.ISO8601Long);
                }
                
                if (field) {
                    if(field.isXType('combo') && Ext.isObject(this.customfieldsValue[name])) {
                        var phpClassName = cfConfig.get('model').split('_'),
                            recordClass = Tine[phpClassName[0]].Model[phpClassName[2]],
                            record = new recordClass(this.customfieldsValue[name]);
                            
                        field.setValue(record.getId());
                        field.selectedRecord = record.data; 
                    } else {
                        field.setValue(this.customfieldsValue[name]);
                    }
                }
            }
        }
    },
    
    /**
     * combile cf values in customfield property
     */
    onRecordUpdate: function() {
        var form = this.editDialog.getForm();
        
        form.items.each(function(f) {
            var name = f.getName();
            
            if (name.match(/^customfield_(.+)$/)) {
                name = name.match(/^customfield_(.+)$/)[1];
                
                this.customfieldsValue[name] = f.getValue();
            }
        }, this);
        
        this.customfieldsValue.toString = function() {
            return Ext.util.JSON.encode(this.customfieldsValue);
        }
        
        this.editDialog.record.set('customfields', this.customfieldsValue);
    },
    
    /**
     * create cf tab on demand
     */
    onBeforeRender: function() {
        var modelName = this.editDialog.recordClass.getMeta('appName') + '_Model_' + this.editDialog.recordClass.getMeta('modelName'),
            allCfConfigs = Tine.widgets.customfields.ConfigManager.getConfigs(this.app, modelName),
            form = this.editDialog.getForm(),
            cfConfigs = new Ext.util.MixedCollection();
        
        // remove already applied cfs / fill the mixed collection
        Ext.each(allCfConfigs, function(cfConfig) {
            if (! form.findField('customfield_' + cfConfig.get('name'))) {
                cfConfigs.add(cfConfig.id, cfConfig);
            }
        }, this);
        
        // auto add cf tab
        if (cfConfigs.getCount()) {
            this.addCFTab(cfConfigs);
        }
    },
    
    /**
     * create a cf tab
     * 
     * @param {Ext.util.MixedCollection} cfConfigs
     */
    addCFTab: function(cfConfigs) {
        this.cfTabItems = [];
        this.fieldsets = {};
        
        cfConfigs.sort('ASC', function(r1, r2) {
            var o1 = (r1.get('definition').uiconfig && r1.get('definition').uiconfig.order) ? r1.get('definition').uiconfig.order : 0,
                o2 = (r2.get('definition').uiconfig && r2.get('definition').uiconfig.order) ? r2.get('definition').uiconfig.order : 0;
            return o1 > o2 ? 1 : 0;
        });

        var definition = null,
            fieldObj = null,
            uiConfig = null,
            group = null;
        
        cfConfigs.each(function(cfConfig) {
            try {
                definition = cfConfig.get('definition');
                fieldObj = Tine.widgets.customfields.Field.get(this.app, cfConfig, {anchor: '95%'}, this.editDialog);
                uiConfig = definition.uiconfig ? definition.uiconfig : {};
                group = uiConfig.group ? uiConfig.group : _('General');
                
                Tine.log.debug('Tine.widgets.customfields.EditDialogPlugin::addCFTab() - Adding ' + cfConfig.get('name') + ' to group ' + group);
                this.getFieldSet(group).add(fieldObj);
                
            } catch (e) {
                Tine.log.debug(e);
                Tine.log.err('Unable to create custom field "' + cfConfig.get('name') + '". Check definition!');
            }
        }, this);
        
        this.cfTab  = new Ext.Panel({
            title: _('Custom Fields'),
            layout: 'form',
            border: true,
            frame: true,
            labelAlign: 'top',
            autoScroll: true,
            items: this.cfTabItems,
            defaults: {
                anchor: '100%',
                labelSeparator: ''
            }
        });
        
        // find the first tabPanel and add it there
        var tabPanel = this.editDialog.items.find(function(item) {
            return Ext.isObject(item) && Ext.isFunction(item.getXType) && item.getXType() == 'tabpanel';
        });
        
        if (tabPanel) {
            tabPanel.add(this.cfTab);
        }
    },
    
    /**
     * sort custom fields into groups
     * 
     * @param {String} name
     * @return {Ext.form.FieldSet}
     */
    getFieldSet: function(name) {
        var reg = /\s+/;
        var system_name = name.replace(reg,'_');
        if (! this.fieldsets[system_name]) {
            this.fieldsets[system_name] = new Ext.form.FieldSet({
                title: name,
                autoHeight:true,
                autoWidth:true,
                labelAlign: 'top',
                labelWidth: '90%',
                collapsible:true,
                name:system_name,
                id: Ext.id() + system_name
            });
            this.cfTabItems.push(this.fieldsets[system_name]);
        }
        return this.fieldsets[system_name];
    }
};

Ext.preg('tinebase.widgets.customfield.editdialogplugin', Tine.widgets.customfields.EditDialogPlugin)/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * TODO         this is only used in the phone dial dialog, do we need that?
 */
 
Ext.ns('Tine.widgets', 'Tine.widgets.customfields');

/**
 * CustomfieldsCombo
 * 
 * @namespace   Tine.widgets.customfields
 * @class       Tine.widgets.customfields.CustomfieldsCombo
 * @extends     Ext.form.ComboBox
 * 
 */
Tine.widgets.customfields.CustomfieldsCombo = Ext.extend(Ext.form.ComboBox, {
    
    typeAhead: false,
    forceSelection: true,
    mode: 'local',
    triggerAction: 'all',    
    
    
    initComponent: function() {
        
        Tine.widgets.customfields.CustomfieldsCombo.superclass.initComponent.call(this);

    },
    
    
       stateEvents: ['select'],
     getState: function() { return this.getValue(); },
    applyState: function(state) { this.setValue(state); }
});


/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Tine.widgets', 'Tine.widgets.relation');

/**
 * @namespace   Tine.widgets.relation
 * @class       Tine.widgets.relation.FilterModel
 * @extends     Tine.widgets.grid.FilterModel
 * 
 * @todo CARE FOR SHEET DESTRUCTON!
 * @todo CARE FOR OPERATOR CHANGE
 */
Tine.widgets.relation.FilterModel = Ext.extend(Tine.widgets.grid.FilterModel, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     */
    app: null,
    
    valueType: 'relation',
    field: 'relation',
    
    /**
     * @private
     */
    initComponent: function() {
        this.label = _('Relation');
        
        // @TODO whipe some models?
        var relatedModels = [];
        Tine.Tinebase.data.RecordMgr.eachKey(function(operator, record) {
            if (record.hasField('relations') && Ext.isFunction(record.getFilterModel)) {
                var label = Tine.Tinebase.appMgr.get(record.getMeta('appName')).i18n._hidden(record.getMeta('recordsName'));
                
                relatedModels.push({operator: operator, label: label});
            }
        }, this);
        
        
        this.relatedModelStore = this.fieldStore = new Ext.data.JsonStore({
            fields: ['operator', 'label'],
            data: relatedModels
        });
        
        this.defaultOperator = this.relatedModelStore.getAt(0).get('operator');
        
        Tine.widgets.relation.FilterModel.superclass.initComponent.call(this);
        
    },
    
    onDefineRelatedRecord: function(filter) {
        var operator = filter.formFields.operator.getValue();
        console.log(operator);
        console.log(Tine.Tinebase.data.RecordMgr.get(operator));
        
        if (! filter.sheet) {
            filter.sheet = new Tine.widgets.grid.FilterToolbar({
                recordClass: Tine.Tinebase.data.RecordMgr.get(operator),
                defaultFilter: 'query'
            });
            
            this.ftb.addFilterSheet(filter.sheet);
        }
        
        this.ftb.setActiveSheet(filter.sheet);
        filter.formFields.value.setText(_('Defined by ...'));
    },
    
    /**
     * operator renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    operatorRenderer: function (filter, el) {
        var operator = new Ext.form.ComboBox({
            filter: filter,
            width: 80,
            id: 'tw-ftb-frow-operatorcombo-' + filter.id,
            mode: 'local',
            lazyInit: false,
            emptyText: _('select a operator'),
            forceSelection: true,
            typeAhead: true,
            triggerAction: 'all',
            store: this.relatedModelStore,
            displayField: 'label',
            valueField: 'operator',
            value: filter.get('operator') ? filter.get('operator') : this.defaultOperator,
            renderTo: el
        });
        operator.on('select', function(combo, newRecord, newKey) {
            if (combo.value != combo.filter.get('operator')) {
                this.onOperatorChange(combo.filter, combo.value);
            }
        }, this);
        
        return operator;
    },
    
    /**
     * value renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    valueRenderer: function(filter, el) {
        var value = new Ext.Button({
            text: _('Define ...'),
            filter: filter,
            width: this.filterValueWidth,
            id: 'tw-ftb-frow-valuefield-' + filter.id,
            renderTo: el,
            value: filter.data.value ? filter.data.value : this.defaultValue,
            handler: this.onDefineRelatedRecord.createDelegate(this, [filter]),
            scope: this,
            getValue: function() {}
        });
        
        // show button
        el.addClass('x-btn-over');
        
        return value;
    }
});

Tine.widgets.grid.FilterToolbar.FILTERS['tinebase.relation'] = Tine.widgets.relation.FilterModel;

/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @subpackage  widgets
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

/*global Ext, Tine*/

Ext.ns('Tine.widgets.account');

/**
 * Account Picker GridPanel
 * 
 * @namespace   Tine.widgets.account
 * @class       Tine.widgets.account.PickerGridPanel
 * @extends     Tine.widgets.grid.PickerGridPanel
 * 
 * <p>Account Picker GridPanel</p>
 * <p><pre>
 * TODO         use selectAction config?
 * </pre></p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * @param       {Object} config
 * @constructor Create a new Tine.widgets.account.PickerGridPanel
 */
Tine.widgets.account.PickerGridPanel = Ext.extend(Tine.widgets.grid.PickerGridPanel, {
    /**
     * @cfg {String} one of 'user', 'group', 'both'
     * selectType
     */
    selectType: 'user',
    
    /**
     * @cfg{String} selectTypeDefault 'user' or 'group' defines which accountType is selected when  {selectType} is true
     */
    selectTypeDefault: 'user',
    
    /**
     * @cfg {Ext.Action}
     * selectAction
     */
    //selectAction: false,
    
    /**
     * @cfg {bool}
     * add 'anyone' selection if selectType == 'both'
     */
    selectAnyone: true,

    /**
     * @cfg {bool}
     * show hidden (user) contacts / (group) lists
     */
    showHidden: false,

    /**
     * get only users with defined status (enabled, disabled, expired)
     * get all -> 'enabled expired disabled'
     * 
     * @type String
     * @property userStatus
     */
    userStatus: 'enabled',
    
    /**
     * @cfg {bool} have the record account properties an account prefix?
     */
    hasAccountPrefix: false,
    
    /**
     * @cfg {String} recordPrefix
     */
    recordPrefix: '',

    /**
     * grid config
     * @private
     */
    autoExpandColumn: 'name',
    
    /**
     * @private
     */
    initComponent: function () {
        this.recordPrefix = (this.hasAccountPrefix) ? 'account_' : '';
        
        this.recordClass = this.recordClass || Tine.Tinebase.Model.Account;
        this.groupRecordClass = this.groupRecordClass || Tine.Addressbook.Model.List;
        
        Tine.widgets.account.PickerGridPanel.superclass.initComponent.call(this);

        this.store.sort(this.recordPrefix + 'name');
    },

    /**
     * init top toolbar
     */
    initTbar: function() {
        this.accountTypeSelector = this.getAccountTypeSelector();
        this.contactSearchCombo = this.getContactSearchCombo();
        this.groupSearchCombo = this.getGroupSearchCombo();
        
        var items = [];
        switch (this.selectType) 
        {
        case 'both':
            items = items.concat([this.contactSearchCombo, this.groupSearchCombo]);
            if (this.selectTypeDefault === 'user') {
                this.groupSearchCombo.hide();
            } else {
                this.contactSearchCombo.hide();
            }
            break;
        case 'user':
            items = this.contactSearchCombo;
            break;
        case 'group':
            items = this.groupSearchCombo;
            break;
        }
        
        this.comboPanel = new Ext.Panel({
            layout: 'hfit',
            border: false,
            items: items,
            columnWidth: 1
        });
        
        this.tbar = new Ext.Toolbar({
            items: [
                this.accountTypeSelector,
                this.comboPanel
            ],
            layout: 'column'
        });
    },
    
    /**
     * define actions
     * 
     * @return {Ext.Action}
     */
    getAccountTypeSelector: function () {
        var userActionCfg = {
            text: _('Search User'),
            scope: this,
            iconCls: 'tinebase-accounttype-user',
            handler: this.onSwitchCombo.createDelegate(this, ['contact', 'tinebase-accounttype-user'])
        };
        var groupActionCfg = {
            text: _('Search Group'),
            scope: this,
            iconCls: 'tinebase-accounttype-group',
            handler: this.onSwitchCombo.createDelegate(this, ['group', 'tinebase-accounttype-group'])
        };
        var anyoneActionCfg = {
            text: _('Add Anyone'),
            scope: this,
            iconCls: 'tinebase-accounttype-addanyone',
            handler: this.onAddAnyone
        };
        
        // set items
        var items = [];
        switch (this.selectType) 
        {
        case 'both':
            items = items.concat([userActionCfg, groupActionCfg]);
            if (this.selectAnyone) {
                items.push(anyoneActionCfg);
            }
            break;
        case 'user':
            items = userActionCfg;
            break;
        case 'group':
            items = groupActionCfg;
            break;
        }
        
        // create action
        return new Ext.Action({
            width: 20,
            text: '',
            disabled: false,
            iconCls: (this.selectTypeDefault === 'user') ? 'tinebase-accounttype-user' : 'tinebase-accounttype-group',
            menu: new Ext.menu.Menu({
                items: items
            }),
            scope: this
        });
    },
    
    /**
     * add anyone to grid
     */
    onAddAnyone: function() {
        var recordData = (this.recordDefaults !== null) ? this.recordDefaults : {};
        recordData[this.recordPrefix + 'type'] = 'anyone';
        recordData[this.recordPrefix + 'name'] = _('Anyone');
        recordData[this.recordPrefix + 'id'] = 0;
        var record = new this.recordClass(recordData, 0);
        
        // check if already in
        if (! this.store.getById(record.id)) {
            this.store.add([record]);
        }
    },
    
    /**
     * @return {Tine.Addressbook.SearchCombo}
     */
    getContactSearchCombo: function () {
        return new Tine.Addressbook.SearchCombo({
            accountsStore: this.store,
            emptyText: _('Search for users ...'),
            newRecordClass: this.recordClass,
            newRecordDefaults: this.recordDefaults,
            recordPrefix: this.recordPrefix,
            userOnly: true,
            onSelect: this.onAddRecordFromCombo,
            additionalFilters: (this.showHidden) ? [{field: 'showDisabled', operator: 'equals', value: true}] : []
        });
    },
    
    /**
     * @return {Tine.Tinebase.widgets.form.RecordPickerComboBox}
     */
    getGroupSearchCombo: function () {
        return new Tine.Tinebase.widgets.form.RecordPickerComboBox({
            //anchor: '100%',
            accountsStore: this.store,
            blurOnSelect: true,
            recordClass: this.groupRecordClass,
            newRecordClass: this.recordClass,
            newRecordDefaults: this.recordDefaults,
            recordPrefix: this.recordPrefix,
            emptyText: _('Search for groups ...'),
            onSelect: this.onAddRecordFromCombo
        });
    },
    
    /**
     * @return Ext.grid.ColumnModel
     * @private
     */
    getColumnModel: function () {
        return new Ext.grid.ColumnModel({
            defaults: {
                sortable: true
            },
            columns:  [
                //{id: 'type', header: '',        dataIndex: this.recordPrefix + 'type', width: 35, renderer: Tine.Tinebase.common.accountTypeRenderer},
                {id: 'name', header: _('Name'), dataIndex: this.recordPrefix + 'name', renderer: Tine.Tinebase.common.accountRenderer}
            ].concat(this.configColumns)
        });
    },
    
    /**
     * @param {Record} recordToAdd
     * 
     * TODO make reset work correctly -> show emptyText again
     */
    onAddRecordFromCombo: function (recordToAdd) {
        var recordData = {},
            record;
        
        // account record selected
        if (recordToAdd.data.account_id) {
            // user account record
            recordData[this.recordPrefix + 'id'] = recordToAdd.data.account_id;
            recordData[this.recordPrefix + 'type'] = 'user';
            recordData[this.recordPrefix + 'name'] = recordToAdd.data.n_fileas;
            recordData[this.recordPrefix + 'data'] = recordToAdd.data;

            record = new this.newRecordClass(Ext.applyIf(recordData, this.newRecordDefaults), recordToAdd.data.account_id);
        } 
        // group or addressbook list record selected
        else if (recordToAdd.data.group_id || recordToAdd.data.id) {
            recordData[this.recordPrefix + 'id'] = recordToAdd.data.group_id || recordToAdd.data.id;
            recordData[this.recordPrefix + 'type'] = 'group';
            recordData[this.recordPrefix + 'name'] = recordToAdd.data.name;
            recordData[this.recordPrefix + 'data'] = recordToAdd.data;
            
            record = new this.newRecordClass(Ext.applyIf(recordData, this.newRecordDefaults), recordToAdd.id);
        }
        
        // check if already in
        if (! this.accountsStore.getById(record.id)) {
            this.accountsStore.add([record]);
        }
        this.collapse();
        this.clearValue();
        this.reset();
    },
    
    /**
     * 
     * @param {String} show
     * @param {String} iconCls
     * 
     * TODO fix width hack (extjs bug?)
     */
    onSwitchCombo: function (show, iconCls) {
        var showCombo = (show === 'contact') ? this.contactSearchCombo : this.groupSearchCombo;
        var hideCombo = (show === 'contact') ? this.groupSearchCombo : this.contactSearchCombo;
        
        if (! showCombo.isVisible()) {
            var width = hideCombo.getWidth();
            
            hideCombo.hide();
            showCombo.show();
            
            // adjust width
            showCombo.setWidth(width - 1);
            showCombo.setWidth(showCombo.getWidth() + 1);
            
            this.accountTypeSelector.setIconClass(iconCls);
        }
    }
});
Ext.reg('tinerecordpickergrid', Tine.widgets.account.PickerGridPanel);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets', 'Tine.widgets.container');

/**
 * Container select ComboBox widget
 * 
 * @namespace   Tine.widgets.container
 * @class       Tine.widgets.container.selectionComboBox
 * @extends     Ext.form.ComboBox
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
    /**
     * @cfg {Boolean} allowNodeSelect
     */
    allowNodeSelect: false,
    /**
     * @cfg {array}
     * default container
     */
    defaultContainer: false,
    /**
     * @cfg {Number} how many chars of the containername to display
     */
    displayLength: 25,
    /**
     * @property {Object} currently displayed container
     */
    selectedContainer: null,
    /**
     * @cfg {Number} list width
     */    
    listWidth: 400,
    /**
     * @cfg {string} containerName
     * name of container (singular)
     */
    containerName: 'container',
    /**
     * @cfg {string} containersName
     * name of container (plural)
     */
    containersName: 'containers',
    /**
     * @cfg {Boolean} hideTrigger2
     */
    hideTrigger2: true,
    /**
     * @cfg {String} startPath a container path to start with
     * possible values are: '/', '/personal/someAccountId', '/shared' (defaults to '/')
     * 
     * NOTE: container paths could not express tree paths .../otherUsers!
     */
    startPath: '/',
    /**
     * @cfg {Tine.data.Record} recordClass
     */
    recordClass: null,
    /**
     * @cfg {Array} requiredGrants
     * grants which are required to select leaf node(s)
     */
    requiredGrants: null,
    
    /**
     * @cfg {String} requiredGrant (legacy - should not be used any more)
     * grants which is required to select leaf node(s)
     */
    requiredGrant: null,
    
    /**
     *  @cfg {Number} trigger2width
     */
    trigger2width: 100,
    
    // private
    allowBlank: false,
    triggerAction: 'all',
    forceAll: true,
    lazyInit: true,
    editable: false,
    clearFilterOnReset: false,
    
    stateful: true,
    stateEvents: ['select'],
    
    mode: 'local',
    valueField: 'id',
    displayField: 'name',
    
    /**
     * is set to true if subpanels (selectionDialog) are active
     * useful in QuickAddGrid
     * @type Boolean
     */
    hasFocusedSubPanels: null,
    /**
     * @private
     */
    initComponent: function() {
        // init state
        if (this.stateful && !this.stateId) {
            if (! this.recordClass) {
                this.stateful = false;
            } else {
                this.stateId = this.recordClass.getMeta('appName') + '-tinebase-widgets-container-selectcombo-' + this.recordClass.getMeta('modelName');
            }
        }

        // legacy handling for requiredGrant
        if(this.requiredGrant && ! this.requiredGrants) {
            this.requiredGrants = [this.requiredGrant];
        } else if (! this.requiredGrant && ! this.requiredGrants) {
            // set default required Grants
            this.requiredGrants = ['readGrant', 'addGrant', 'editGrant'];
        }

        // no state saving for startPath != /
        this.on('beforestatesave', function() {return this.startPath === '/';}, this);
        
        this.initStore();
        
        this.otherRecord = new Tine.Tinebase.Model.Container({id: 'other', name: String.format(_('choose other {0}...'), this.containerName)}, 'other');
        this.store.add(this.otherRecord);
        
        this.emptyText = String.format(_('Select a {0}'), this.containerName);
        
        // init triggers (needs to be done before standard initTrigger template fn)
        if (! this.hideTrigger2) {
            if (this.triggerClass == 'x-form-arrow-trigger') {
                this.triggerClass = 'x-form-arrow-trigger-rectangle';
            }
            
            this.triggerConfig = {
                tag:'span', cls:'x-form-twin-triggers', cn:[
                {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass},
                {tag:'span', cls:'tw-containerselect-trigger2', cn:[
                    {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger tw-containerselect-trigger2-bg"},
                    {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger tw-containerselect-trigger2"},
                    {tag: "div", style: {position: 'absolute', top: 0, left: '5px'}}
                ]}
            ]};
            
        }
        
        //this.title = String.format(_('Recently used {0}:'), this.containersName);
        
        Tine.widgets.container.selectionComboBox.superclass.initComponent.call(this);
        
        if (this.defaultContainer) {
            this.selectedContainer = this.defaultContainer;
            this.value = this.defaultContainer.name;
        }
        this.on('blur', function() {
            if(this.hasFocusedSubPanels) {
                return false;
            }
        }, this);
        this.on('beforequery', this.onBeforeQuery, this);
    },
    
    /**
     * @private
     */
    initStore: function() {
        var state = this.stateful ? Ext.state.Manager.get(this.stateId) : null;
        var recentsData = state && state.recentsData || [];
        
        this.store = new Ext.data.JsonStore({
            id: 'id',
            data: recentsData,
            remoteSort: false,
            fields: Tine.Tinebase.Model.Container,
            listeners: {beforeload: this.onBeforeLoad.createDelegate(this)}
        });
        this.setStoreFilter();
    },
    
    initTrigger : function(){
        if (! this.hideTrigger2) {
            var t1 = this.trigger.first();
            var t2 = this.trigger.last();
            this.trigger2 = t2;
            
            t1.on("click", this.onTriggerClick, this, {preventDefault:true});
            t2.on("click", this.onTrigger2Click, this, {preventDefault:true});
            
            t1.addClassOnOver('x-form-trigger-over');
            t1.addClassOnClick('x-form-trigger-click');
            
            t2.addClassOnOver('x-form-trigger-over');
            t2.addClassOnClick('x-form-trigger-click');
            
            
        } else {
            Tine.widgets.container.selectionComboBox.superclass.initTrigger.call(this);
        }
    },
    
    manageRecents: function(recent) {
        recent.set('dtselect', new Date().getTime());
        
        this.store.remove(this.otherRecord);
        this.store.clearFilter();
        this.store.sort('dtselect', 'DESC');
        
        // keep 10 containers and the nodes in betwwen
        var containerKeepCount = 0;
        this.store.each(function(record) {
            if (containerKeepCount < 10) {
                if (! record.get('is_container_node')) {
                    containerKeepCount += 1;
                }
                return;
            }
            this.store.remove(record);
        }, this);
        this.setStoreFilter();
        this.store.add(this.otherRecord);
    },
    
    /**
     * prepare params for remote search (this.startPath == /personal/*
     * 
     * @param {} store
     * @param {} options
     */
    onBeforeLoad: function(store, options) {
        options.params = {
            method: 'Tinebase_Container.getContainer',
            application: this.appName,
            containerType: Tine.Tinebase.container.TYPE_PERSONAL,
            owner: Tine.Tinebase.container.pathIsPersonalNode(this.startPath)
        };
    },
    
    /**
     * if this.startPath correspondes to a personal node, 
     * directly do a remote search w.o. container tree dialog
     * 
     * @private
     * @param {} queryEvent
     */
    onBeforeQuery: function(queryEvent) {
        queryEvent.query = new Date().getTime();
        this.mode = Tine.Tinebase.container.pathIsPersonalNode(this.startPath) ? 'remote' : 'local' ;
        
        // skip combobox nativ filtering to preserv our filters
        if (this.mode == 'local') {
            this.onLoad();
            return false;
        }
    },
    
    /**
     * filter store according to this.startPath and this.allowNodeSelect
     * 
     * NOTE: If this.startPath != '/' resents are not used as we force remote loads,
     *       thus we don't need to filter in this case!
     */
    setStoreFilter: function() {
        this.store.clearFilter();
        this.store.sort('dtselect', 'DESC');
        
        var skipBoundary = this.store.getAt(Math.max(this.store.getCount(), 10) -1);
        var dtselectMin = skipBoundary ? skipBoundary.get('dtselect') : -1;
        
        this.store.filterBy(function(record) {
            var keep = (this.allowNodeSelect || !record.get('is_container_node')) && record.get('dtselect') > dtselectMin;
            return keep;
        }, this);
    },
    
    setTrigger2Text: function(text) {
        var trigger2 = this.trigger.last().last().update(text);
    },
    
    setTrigger2Disabled: function(bool) {
        if (bool) {
            this.trigger2.setOpacity(0.5);
            this.trigger2.un("click", this.onTrigger2Click, this, {preventDefault:true});
        } else {
            this.trigger2.setOpacity(1);
            this.trigger2.on("click", this.onTrigger2Click, this, {preventDefault:true});
        }
    },
    
    getTrigger2: function() {
        return this.trigger2;
    },
    
    onTrigger2Click: Ext.emptyFn,
    
    // private: only blur if dialog is closed
    onBlur: function() {
        if (!this.dlg) {
            return Tine.widgets.container.selectionComboBox.superclass.onBlur.apply(this, arguments);
        }
    },
    
    onSelect: function(record, index) {
        this.hasFocusedSubPanels = true;
        if (record == this.otherRecord) {
            this.onChoseOther();
        } else {
            this.manageRecents(record);
            Tine.widgets.container.selectionComboBox.superclass.onSelect.apply(this, arguments);
        }
    },
    
    /**
     * @private
     */
    onChoseOther: function() {
        this.collapse();
        this.dlg = new Tine.widgets.container.selectionDialog({
            allowNodeSelect: this.allowNodeSelect,
            containerName: this.containerName,
            containersName: this.containersName,
            requiredGrants: this.requiredGrants,
            TriggerField: this
        });
    },
    
    /**
     * @private
     */
    getValue: function(){
        return (this.selectedContainer !== null) ? this.selectedContainer.id : '';
    },
    
    /**
     * @private
     * @todo // records might be in from state, but value is conainerData only
     */
    setValue: function(container) {
        
        if(!container) return;
        
        if (container.hasOwnProperty('get') && Ext.isFunction(container.get)) {
            // container is a record -> already in store -> nothing to do
        } else if (this.store.getById(container)) {
            // store already has a record of this container
            container = this.store.getById(container);
            
        } else if (container.path && this.store.findExact('path', container.path) >= 0) {
            // store already has a record of this container
            container = this.store.getAt(this.store.findExact('path', container.path));
            
        } else if (container.path || container.id) {
            // ignore server name for node 'My containers'
            if (container.path && container.path === Tine.Tinebase.container.getMyNodePath()) {
                container.name = null;
            }
            container.name = container.name || Tine.Tinebase.container.path2name(container.path, this.containerName, this.containersName);
            container.id = container.id ||container.path;
            
            container = new Tine.Tinebase.Model.Container(container, container.id);
            
            this.store.add(container);
        } else {
            // reject container
            return this;
        }
        
        container.set('is_container_node', !!!Tine.Tinebase.container.pathIsContainer(container.get('path')));
        this.selectedContainer = container.data;
        
        // make sure other is _last_ entry in list
        this.store.remove(this.otherRecord);
        this.store.add(this.otherRecord);
        
        Tine.widgets.container.selectionComboBox.superclass.setValue.call(this, container.id);
        
        if (container.account_grants) {
            this.setDisabled(! container.account_grants.deleteGrant);
        }
        
        if(this.qtip) {
            this.qtip.remove();
        }
        
        return container;
    },
    
    /**
     * Resets the current field value to the originally loaded value and clears any validation messages.
     * See {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
     */
    reset : function() {
        this.supr().setValue(this.originalValue);
        this.supr().reset.call(this);
    },
    
    applyState: Ext.emptyFn,
    
    /**
     * @private
     */
    getState: function() {
        var recentsData = [];
        this.store.clearFilter();
        this.store.remove(this.otherRecord);
        
        this.store.each(function(container) {
            var data = Ext.copyTo({}, container.data, Tine.Tinebase.Model.Container.getFieldNames());
            recentsData.push(data);
        }, this);
        
        this.setStoreFilter();
        this.store.add(this.otherRecord);
        
        return {
            recentsData : recentsData
        };
    }
});
Ext.reg('tinewidgetscontainerselectcombo', Tine.widgets.container.selectionComboBox);

Tine.widgets.form.RecordPickerManager.register('Tinebase', 'Container', Tine.widgets.container.selectionComboBox);
/**
 * @namespace Tine.widgets.container
 * @class Tine.widgets.container.selectionDialog
 * @extends Ext.Component
 * 
 * This widget shows a modal container selection dialog
 */
Tine.widgets.container.selectionDialog = Ext.extend(Ext.Component, {
    /**
     * @cfg {Boolean} allowNodeSelect
     */
    allowNodeSelect: false,
    /**
     * @cfg {string} containerName
     * name of container (singular)
     */
    containerName: 'container',
    /**
     * @cfg {string} containerName
     * name of container (plural)
     */
    containersName: 'containers',
    /**
     * @cfg {string}
     * title of dialog
     */
    title: null,
    /**
     * @cfg {Number}
     */
    windowHeight: 400,
    /**
     * @property {Ext.Window}
     */
    win: null,
    /**
     * @property {Ext.tree.TreePanel}
     */
    tree: null,
    /**
     * @cfg {Array} requiredGrants
     * grants which are required to select leaf node(s)
     */
    requiredGrants: ['readGrant'],
    
    /**
     * @private
     */
    initComponent: function(){
        Tine.widgets.container.selectionDialog.superclass.initComponent.call(this);
        
        this.title = this.title ? this.title : String.format(_('please select a {0}'), this.containerName);
        
        this.cancelAction = new Ext.Action({
            text: _('Cancel'),
            iconCls: 'action_cancel',
            minWidth: 70,
            handler: this.onCancel,
            scope: this
        });
        
        this.okAction = new Ext.Action({
            disabled: true,
            text: _('Ok'),
            iconCls: 'action_saveAndClose',
            minWidth: 70,
            handler: this.onOk,
            scope: this
        });
        
        // adjust window height
        if (Ext.getBody().getHeight(true) * 0.7 < this.windowHeight) {
            this.windowHeight = Ext.getBody().getHeight(true) * 0.7;
        }

        this.tree = new Tine.widgets.container.TreePanel({
            allowMultiSelection: false,
            containerName: this.TriggerField.containerName,
            containersName: this.TriggerField.containersName,
            appName: this.TriggerField.appName,
            defaultContainer: this.TriggerField.defaultContainer,
            requiredGrants: this.requiredGrants
        });
        
        this.tree.on('click', this.onTreeNodeClick, this);
        this.tree.on('dblclick', this.onTreeNoceDblClick, this);
        
        this.win = Tine.WindowFactory.getWindow({
            title: this.title,
            closeAction: 'close',
            modal: true,
            width: 375,
            height: this.windowHeight,
            minWidth: 375,
            minHeight: this.windowHeight,
            layout: 'fit',
            plain: true,
            bodyStyle: 'padding:5px;',
            buttonAlign: 'right',
            
            buttons: [
                this.cancelAction,
                this.okAction
            ],
            
            items: [ this.tree ]
        });
    },
    
    /**
     * @private
     */
    onTreeNodeClick: function(node) {
        this.okAction.setDisabled(! (node.leaf ||this.allowNodeSelect));
        
        if (! node.leaf ) {
            node.expand();
        }
    },
    
    /**
     * @private
     */
    onTreeNoceDblClick: function(node) {
        if (! this.okAction.isDisabled()) {
            this.onOk();
        }
    },
    
    /**
     * @private
     */
    onCancel: function() {
        this.onClose();
    },
    
    /**
     * @private
     */
    onClose: function() {
        this.win.close();
    },
    
    /**
     * @private
     */
    onOk: function() {
        var  node = this.tree.getSelectionModel().getSelectedNode();
        if (node) {
            this.TriggerField.hasFocusedSubPanels = false;
            var container = this.TriggerField.setValue(node.attributes.container);
            this.TriggerField.manageRecents(container);
            this.TriggerField.fireEvent('select', this.TriggerField, node.attributes.container);
            
            if (this.TriggerField.blurOnSelect) {
                this.TriggerField.fireEvent('blur', this.TriggerField);
            }
            this.onClose();
        }
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets', 'Tine.widgets.container');

/**
 * Container Grants grid
 * 
 * @namespace   Tine.widgets.container
 * @class       Tine.widgets.container.GrantsDialog
 * @extends     Tine.widgets.dialog.EditDialog
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2011 Metaways Infosystems GmbH (http://www.metaways.de)
 * @constructor
 * @param {Object} config The configuration options.
 */
Tine.widgets.container.GrantsGrid = Ext.extend(Tine.widgets.account.PickerGridPanel, {

    /**
     * @cfg {Array} data of Tine.Tinebase.Model.Container
     * Container to manage grants for
     */
    grantContainer: null,
    
    /**
     * @cfg {Boolean}
     * always show the admin grant (default: false)
     */
    alwaysShowAdminGrant: false,
    
    /**
     * Tine.widgets.account.PickerGridPanel config values
     */
    selectType: 'both',
    selectTypeDefault: 'group',
    hasAccountPrefix: true,
    recordClass: Tine.Tinebase.Model.Grant,
    
    readGrantTitle: 'Read', // _('Read')
    readGrantDescription: 'The grant to read records of this container', // _('The grant to read records of this container')
    addGrantTitle: 'Add', // _('Add')
    addGrantDescription: 'The grant to add records to this container', // _('The grant to add records to this container')
    editGrantTitle: 'Edit', // _('Edit')
    editGrantDescription: 'The grant to edit records in this container', // _('The grant to edit records in this container')
    deleteGrantTitle: 'Delete', // _('Delete')
    deleteGrantDescription: 'The grant to delete records in this container', // _('The grant to delete records in this container')
    exportGrantTitle: 'Export', // _('Export')
    exportGrantDescription: 'The grant to export records from this container', // _('The grant to export records from this container')
    syncGrantTitle: 'Sync', // _('Sync')
    syncGrantDescription: 'The grant to synchronise records with this container', // _('The grant to synchronise records with this container')
    adminGrantTitle: 'Admin', // _('Admin')
    adminGrantDescription: 'The grant to administrate this container', // _('The grant to administrate this container')
    
    freebusyGrantTitle: 'Free Busy', // _('Free Busy')
    freebusyGrantDescription: 'The grant to access free busy information of events in this calendar', // _('The grant to access free busy information of events in this calendar')
    privateGrantTitle: 'Private', // _('Private')
    privateGrantDescription: 'The grant to access records marked as private in this container', // _('The grant to access records marked as private in this container')
    
    
    /**
     * @private
     */
    initComponent: function () {
        this.initColumns();
        
        this.recordDefaults = {
            readGrant: true,
            exportGrant: true,
            syncGrant: true
        };
        
        Tine.widgets.container.GrantsGrid.superclass.initComponent.call(this);
        
        this.getStore().on('update', this.onStoreUpdate, this);
        
        this.getStore().on('load', function(store) {
            store.each(function(r) {this.onStoreUpdate(store, r)}, this);
        }, this);
    },

    onStoreUpdate: function(store, record, operation) {
        if (this.alwaysShowAdminGrant || (this.grantContainer && this.grantContainer.type == 'shared')) {
            if (record.get('adminGrant')) {
                // set all grants and mask other checkboxes
                Ext.each(this.getColumnModel().columns, function(col, colIdx) {
                    var matches;
                    if ((matches = col.dataIndex.match(/^([a-z]+)Grant$/)) && matches[1] != 'admin') {
//                          //Ext.fly(this.getView().getCell(store.indexOf(record), colIdx)).mask('test');
                        record.set(col.dataIndex, true);
                    }
                }, this);
                
            } else {
                // make sure grants are not masked
            }
        }
    },
    
    /**
     * init grid columns
     */
    initColumns: function() {
        var grants = ['read', 'add', 'edit', 'delete', 'export', 'sync'];
        
        if (this.alwaysShowAdminGrant || (this.grantContainer && this.grantContainer.type == 'shared')) {
            grants.push('admin');
        }

        if (this.grantContainer) {
            // @todo move this to cal app when apps can cope with their own grant models
            if (this.grantContainer.application_id && this.grantContainer.application_id.name) {
                var isCalendar = (this.grantContainer.application_id.name == 'Calendar');
            } else {
                var calApp = Tine.Tinebase.appMgr.get('Calendar'),
                    calId = calApp ? calApp.id : 'none',
                    isCalendar = this.grantContainer.application_id === calId;
            }
            if (this.grantContainer.type == 'personal' && isCalendar) {
                grants.push('freebusy');
            }
            
            if (this.grantContainer.type == 'personal' && this.grantContainer.capabilites_private) {
                grants.push('private');
            }
        }
        
        this.configColumns = [];
        
        Ext.each(grants, function(grant) {
            this.configColumns.push(new Ext.ux.grid.CheckColumn({
                header: _(this[grant + 'GrantTitle']),
                tooltip: _(this[grant + 'GrantDescription']),
                dataIndex: grant + 'Grant',
                width: 55
            }));
        }, this);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets', 'Tine.widgets.container');

/**
 * Container Grants dialog
 * 
 * @namespace   Tine.widgets.container
 * @class       Tine.widgets.container.GrantsDialog
 * @extends     Tine.widgets.dialog.EditDialog
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 * @constructor
 * @param {Object} config The configuration options.
 */
Tine.widgets.container.GrantsDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
    
    /**
     * @cfg {Tine.Tinebase.container.models.container}
     * Container to manage grants for
     */
    grantContainer: null,
    
    /**
     * @cfg {string}
     * Name of container folders, e.g. Addressbook
     */
    containerName: null,
    
    /**
     * @private {Ext.data.JsonStore}
     */
    grantsStore: null,
    
    /**
     * @private
     */
    windowNamePrefix: 'ContainerGrantsWindow_',
    loadRecord: false,
    tbarItems: [],
    evalGrants: false,
    
    /**
     * @private
     */
    initComponent: function() {
        this.containerName = this.containerName ? this.containerName : _('Folder');

        this.grantsStore =  new Ext.data.JsonStore({
            baseParams: {
                method: 'Tinebase_Container.getContainerGrants',
                containerId: this.grantContainer.id
            },
            root: 'results',
            totalProperty: 'totalcount',
            //id: 'id',
            // use account_id here because that simplifies the adding of new records with the search comboboxes
            id: 'account_id',
            fields: Tine.Tinebase.Model.Grant
        });
        this.grantsStore.load();
        
        Tine.widgets.container.GrantsDialog.superclass.initComponent.call(this);
    },
    
    /**
     * init record to edit
     * 
     * - overwritten: we don't have a record here 
     */
    initRecord: function() {
    },
    
    /**
     * returns dialog
     */
    getFormItems: function() {
        this.grantsGrid = new Tine.widgets.container.GrantsGrid({
            store: this.grantsStore,
            grantContainer: this.grantContainer
        });
        
        return this.grantsGrid;
    },
    
    /**
     * @private
     */
    onApplyChanges: function(closeWindow) {
        Ext.MessageBox.wait(_('Please wait'), _('Updating Grants'));
        
        var grants = [];
        this.grantsStore.each(function(_record){
            grants.push(_record.data);
        });
        
              
        Ext.Ajax.request({
            params: {
                method: 'Tinebase_Container.setContainerGrants',
                containerId: this.grantContainer.id,
                grants: grants
            },
            scope: this,
            success: function(_result, _request){
                Ext.MessageBox.hide();
                if (closeWindow) {
                    this.purgeListeners();
                    this.window.close();
                    return;
                }
                var grants = Ext.util.JSON.decode(_result.responseText);
                this.grantsStore.loadData(grants, false);
            },
            failure: function(response, options) {
                var responseText = Ext.util.JSON.decode(response.responseText);
                
                if (responseText.data.code == 505) {
                    Ext.Msg.show({
                       title:   _('Error'),
                       msg:     _('You are not allowed to remove all admins for this container!'),
                       icon:    Ext.MessageBox.ERROR,
                       buttons: Ext.Msg.OK
                    });
                    
                } else {
                    // call default exception handler
                    var exception = responseText.data ? responseText.data : responseText;
                    Tine.Tinebase.ExceptionHandler.handleRequestException(exception);
                }                
            }
        });
    }
});

/**
 * grants dialog popup / window
 */
Tine.widgets.container.GrantsDialog.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: 700,
        height: 450,
        name: Tine.widgets.container.GrantsDialog.windowNamePrefix + Ext.id(),
        contentPanelConstructor: 'Tine.widgets.container.GrantsDialog',
        contentPanelConstructorConfig: config,
        modal: true
    });
    return window;
};
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets', 'Tine.widgets.container');

/**
 * @namespace   Tine.widgets.container
 * @class       Tine.widgets.container.TreePanel
 * @extends     Ext.tree.TreePanel
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @param       {Object} config Configuration options
 * @description
 * <p>Utility class for generating container trees as used in the apps tree panel</p>
 * <p>This widget handles all container related actions like add/rename/delte and manager permissions<p>
 *<p>Example usage:</p>
<pre><code>
var taskPanel =  new Tine.containerTreePanel({
    app: Tine.Tinebase.appMgr.get('Tasks'),
    recordClass: Tine.Tasks.Model.Task
});
</code></pre>
 */
Tine.widgets.container.TreePanel = function(config) {
    Ext.apply(this, config);
    
    this.addEvents(
        /**
         * @event containeradded
         * Fires when a container was added
         * @param {container} the new container
         */
        'containeradd',
        /**
         * @event containerdelete
         * Fires when a container got deleted
         * @param {container} the deleted container
         */
        'containerdelete',
        /**
         * @event containerrename
         * Fires when a container got renamed
         * @param {container} the renamed container
         */
        'containerrename',
        /**
         * @event containerpermissionchange
         * Fires when a container got renamed
         * @param {container} the container whose permissions where changed
         */
        'containerpermissionchange',
        /**
         * @event containercolorset
         * Fires when a container color got changed
         * @param {container} the container whose color where changed
         */
        'containercolorset'
    );
        
    Tine.widgets.container.TreePanel.superclass.constructor.call(this);
};

Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     */
    app: null,
    /**
     * @cfg {Boolean} allowMultiSelection (defaults to true)
     */
    allowMultiSelection: true,
    /**
     * @cfg {String} defaultContainerPath
     */
    defaultContainerPath: null,
    /**
     * @cfg {array} extraItems additional items to display under all
     */
    extraItems: null,
    /**
     * @cfg {String} filterMode one of:
     *   - gridFilter: hooks into the grids.store
     *   - filterToolbar: hooks into the filterToolbar (container filterModel required)
     */
    filterMode: 'gridFilter',
    /**
     * @cfg {Tine.data.Record} recordClass
     */
    recordClass: null,
    /**
     * @cfg {Array} requiredGrants
     * grants which are required to select leaf node(s)
     */
    requiredGrants: null,
    
    /**
     * @cfg {Boolean} useContainerColor
     * use container colors
     */
    useContainerColor: false,
    /**
     * @cfg {Boolean} useContainerColor
     * use container properties
     */
    useProperties: true,
    
    useArrows: true,
    border: false,
    autoScroll: true,
    enableDrop: true,
    ddGroup: 'containerDDGroup',
    
    /**
     * @fixme not needed => all events hand their events over!!!
     * 
     * @property ctxNode holds treenode which got a contextmenu
     * @type Ext.tree.TreeNode
     */
    ctxNode: null,
    
    /**
     * init this treePanel
     */
    initComponent: function() {
        if (! this.appName && this.recordClass) {
            this.appName = this.recordClass.getMeta('appName');
        }
        if (! this.app) {
            this.app = Tine.Tinebase.appMgr.get(this.appName);
        }
        
        if (this.allowMultiSelection) {
            this.selModel = new Ext.tree.MultiSelectionModel({});
        }
        
        var containerName = this.recordClass ? this.recordClass.getContainerName() : 'container';
        var containersName = this.recordClass ? this.recordClass.getContainersName() : 'containers';

        //ngettext('container', 'containers', n);
        this.containerName = this.containerName || this.app.i18n.n_hidden(containerName, containersName, 1);
        this.containersName = this.containersName || this.app.i18n._hidden(containersName);
        
        this.loader = this.loader || new Tine.widgets.tree.Loader({
            getParams: this.onBeforeLoad.createDelegate(this),
            inspectCreateNode: this.onBeforeCreateNode.createDelegate(this)
        });
        
        this.root = {
            path: '/',
            cls: 'tinebase-tree-hide-collapsetool',
            expanded: true,
            children: [{
                path: this.getRootPath(),
                id: 'personal'
            }, {
                path: '/shared',
                id: 'shared'
            }, {
                path: '/personal',
                id: 'otherUsers'
            }].concat(this.getExtraItems())
        };
        
        // init drop zone
        this.dropConfig = {
            ddGroup: this.ddGroup || 'TreeDD',
            appendOnly: this.ddAppendOnly === true,
            /**
             * @todo check acl!
             */
            onNodeOver : function(n, dd, e, data) {
                var node = n.node;
                
                // auto node expand check
                if(node.hasChildNodes() && !node.isExpanded()){
                    this.queueExpand(node);
                }
                return node.attributes.allowDrop ? 'tinebase-tree-drop-move' : false;
            },
            isValidDropPoint: function(n, dd, e, data){
                return n.node.attributes.allowDrop;
            },
            completeDrop: Ext.emptyFn
        };
        
        this.initContextMenu();
              
        this.getSelectionModel().on('beforeselect', this.onBeforeSelect, this);
        this.getSelectionModel().on('selectionchange', this.onSelectionChange, this);
        this.on('click', this.onClick, this);
        this.on('contextmenu', this.onContextMenu, this);
        this.on('beforenodedrop', this.onBeforeNodeDrop, this);
        this.on('append', this.onAppendNode, this);
        
        Tine.widgets.container.TreePanel.superclass.initComponent.call(this);
        return;
    },
    
    /**
     * delivers the personal root node path
     * 
     * @returns personal root node path
     */
    getRootPath: function() {
        return Tine.Tinebase.container.getMyNodePath();
    },
    
    /**
     * template fn for subclasses to set default path
     * 
     * @return {String}
     */
    getDefaultContainerPath: function() {
        return this.defaultContainerPath || '/';
    },
    
    /**
     * template fn for subclasses to append extra items
     * 
     * @return {Array}
     */
    getExtraItems: function() {
        return this.extraItems || [];
    },
    
    /**
     * returns a filter plugin to be used in a grid
     */
    getFilterPlugin: function() {
        if (!this.filterPlugin) {
            this.filterPlugin = new Tine.widgets.tree.FilterPlugin({
                treePanel: this
            });
        }
        
        return this.filterPlugin;
    },
    
    /**
     * returns object of selected container/filter or null/default
     * 
     * @param {Array} [requiredGrants]
     * @param {Tine.Tinebase.Model.Container} [defaultContainer]
     * @param {Boolean} onlySingle use default if more than one container in selection
     * @return {Tine.Tinebase.Model.Container}
     */
    getSelectedContainer: function(requiredGrants, defaultContainer, onlySingle) {
        var container = defaultContainer,
            sm = this.getSelectionModel(),
            selection = typeof sm.getSelectedNodes == 'function' ? sm.getSelectedNodes() : [sm.getSelectedNode()];
        
        if (Ext.isArray(selection) && selection.length > 0 && (! onlySingle || selection.length === 1 || ! container)) {
            container = this.getContainerFromSelection(selection, requiredGrants) || container;
        } 
        // postpone this as we don't get the whole container record here
//        else if (this.filterMode == 'filterToolbar' && this.filterPlugin) {
//            container = this.getContainerFromFilter() || container;
//        }
        
        return container;
    },
    
    /**
     * get container from selection
     * 
     * @param {Array} selection
     * @param {Array} requiredGrants
     * @return {Tine.Tinebase.Model.Container}
     */
    getContainerFromSelection: function(selection, requiredGrants) {
        var result = null;

        Ext.each(selection, function(node) {
            if (node && Tine.Tinebase.container.pathIsContainer(node.attributes.container.path)) {
                if (! requiredGrants || this.hasGrant(node, requiredGrants)) {
                    result = node.attributes.container;
                    // take the first one
                    return false;
                }
            }
        }, this);
        
        return result;
    },

    /**
     * get container from filter toolbar
     * 
     * @param {Array} requiredGrants
     * @return {Tine.Tinebase.Model.Container}
     * 
     * TODO make this work -> atm we don't get the account grants here (why?)
     */
    getContainerFromFilter: function(requiredGrants) {
        var result = null;
        
        // check if single container is selected in filter toolbar 
        var ftb = this.filterPlugin.getGridPanel().filterToolbar,
            filterValue = null;

        ftb.filterStore.each(function(filter) {
            if (filter.get('field') == this.recordClass.getMeta('containerProperty')) {
                filterValue = filter.get('value');
                if (filter.get('operator') == 'equals') {
                    result = filterValue;
                } else if (filter.get('operator') == 'in' && filterValue.length == 1){
                    result = filterValue[0];
                }
                // take the first one
                return false;
            }
        }, this);
        
        return result;
    },
    
    /**
     * convert containerPath to treePath
     * 
     * @param {String}  containerPath
     * @return {String} treePath
     */
    getTreePath: function(containerPath) {
        var treePath = '/' + this.getRootNode().id + (containerPath !== '/' ? containerPath : '');

        // replace personal with otherUsers if personal && ! personal/myaccountid
        var matches = containerPath.match(/^\/personal\/{0,1}([0-9a-z_\-]*)\/{0,1}/i);
        if (matches) {
            if (matches[1] != Tine.Tinebase.registry.get('currentAccount').accountId) {
                treePath = treePath.replace('personal', 'otherUsers');
            } else {
                treePath = treePath.replace('personal/'  + Tine.Tinebase.registry.get('currentAccount').accountId, 'personal');
            }
        }
        
        return treePath;
    },
    
    /**
     * checkes if user has requested grant for given container represented by a tree node 
     * 
     * @param {Ext.tree.TreeNode} node
     * @param {Array} grant
     * @return {}
     */
    hasGrant: function(node, grants) {
        var attr = node.attributes,
            condition = false;

        if(attr && attr.leaf) {
            condition = true;
            Ext.each(grants, function(grant) {
                condition = condition && attr.container.account_grants[grant];
            }, this);
        }

        return condition;
    },
    
    /**
     * @private
     * - select default path
     */
    afterRender: function() {
        Tine.widgets.container.TreePanel.superclass.afterRender.call(this);
        
        var defaultContainerPath = this.getDefaultContainerPath();
        
        if (defaultContainerPath && defaultContainerPath != '/') {
            var root = '/' + this.getRootNode().id;
            
            this.expand();
            // @TODO use getTreePath() when filemanager is fixed
            this.selectPath.defer(100, this, [root + defaultContainerPath]);
        }
        
        if (this.filterMode == 'filterToolbar' && this.filterPlugin) {
            this.filterPlugin.getGridPanel().filterToolbar.on('change', this.onFilterChange, this);
        }
    },
    
    /**
     * @private
     */
    initContextMenu: function() {
        
        this.contextMenuUserFolder = Tine.widgets.tree.ContextMenu.getMenu({
            nodeName: this.containerName,
            actions: ['add'],
            scope: this,
            backend: 'Tinebase_Container',
            backendModel: 'Container'
        });
        
        this.contextMenuSingleContainer = Tine.widgets.tree.ContextMenu.getMenu({
            nodeName: this.containerName,
            actions: ['delete', 'rename', 'grants'].concat(
                this.useProperties ? ['properties'] : []
            ).concat(
                this.useContainerColor ? ['changecolor'] : []
            ),
            scope: this,
            backend: 'Tinebase_Container',
            backendModel: 'Container'
        });
        
        this.contextMenuSingleContainerProperties = Tine.widgets.tree.ContextMenu.getMenu({
            nodeName: this.containerName,
            actions: ['properties'],
            scope: this,
            backend: 'Tinebase_Container',
            backendModel: 'Container'
        });
    },
    
    /**
     * called when node is appended to this tree
     */
    onAppendNode: function(tree, parent, appendedNode, idx) {
        if (appendedNode.leaf && this.hasGrant(appendedNode, this.requiredGrants)) {
            if (this.useContainerColor) {
                appendedNode.ui.render = appendedNode.ui.render.createSequence(function() {
                    this.colorNode = Ext.DomHelper.insertAfter(this.iconNode, {tag: 'span', html: '&nbsp;&#9673;&nbsp', style: {color: appendedNode.attributes.container.color || '#808080'}}, true);
                }, appendedNode.ui);
            }
        }
    },
    
    /**
     * expand automatically on node click
     * 
     * @param {} node
     * @param {} e
     */
    onClick: function(node, e) {
        var sm = this.getSelectionModel(),
            selectedNode = sm.getSelectedNode();
    
        // NOTE: in single select mode, a node click on a selected node does not trigger 
        //       a selection change. We need to do this by hand here
        if (! this.allowMultiSelection && node == selectedNode) {
            this.onSelectionChange(sm, node);
        }
        
        node.expand();
    },
    
    /**
     * show context menu
     * 
     * @param {} node
     * @param {} event
     */
    onContextMenu: function(node, event) {
        this.ctxNode = node;
        var container = node.attributes.container,
            path = container.path,
            owner;
        
        if (! Ext.isString(path)) {
            return;
        }
        
        if (Tine.Tinebase.container.pathIsContainer(path)) {
            if (container.account_grants && container.account_grants.adminGrant) {
                this.contextMenuSingleContainer.showAt(event.getXY());
            } else {
                this.contextMenuSingleContainerProperties.showAt(event.getXY());
            }
        } else if (path.match(/^\/shared$/) && (Tine.Tinebase.common.hasRight('admin', this.app.appName) || Tine.Tinebase.common.hasRight('manage_shared_folders', this.app.appName))){
            this.contextMenuUserFolder.showAt(event.getXY());
        } else if (Tine.Tinebase.registry.get('currentAccount').accountId == Tine.Tinebase.container.pathIsPersonalNode(path)){
            this.contextMenuUserFolder.showAt(event.getXY());
        }
    },

    /**
     * adopt attr
     * 
     * @param {Object} attr
     */
    onBeforeCreateNode: function(attr) {
        if (attr.accountDisplayName) {
            attr.name = attr.accountDisplayName;
            attr.path = '/personal/' + attr.accountId;
            attr.id = attr.accountId;
        }
        
        if (! attr.name && attr.path) {
            attr.name = Tine.Tinebase.container.path2name(attr.path, this.containerName, this.containersName);
        }
        
        Ext.applyIf(attr, {
            text: Ext.util.Format.htmlEncode(attr.name),
            qtip: Tine.Tinebase.common.doubleEncode(attr.name),
            leaf: !!attr.account_grants,
            allowDrop: !!attr.account_grants && attr.account_grants.addGrant
        });
        
        // copy 'real' data to container space
        attr.container = Ext.copyTo({}, attr, Tine.Tinebase.Model.Container.getFieldNames());
    },
    
    /**
     * returns params for async request
     * 
     * @param {Ext.tree.TreeNode} node
     * @return {Object}
     */
    onBeforeLoad: function(node) {
        var path = node.attributes.path;
        var type = Tine.Tinebase.container.path2type(path);
        var owner = Tine.Tinebase.container.pathIsPersonalNode(path);
        
        if (type === 'personal' && ! owner) {
            type = 'otherUsers';
        }
        
        var params = {
            method: 'Tinebase_Container.getContainer',
            application: this.app.appName,
            containerType: type,
            requiredGrants: this.requiredGrants,
            owner: owner
        };
        
        return params;
    },
    
    /**
     * permit selection of nodes with missing required grant
     * 
     * @param {} sm
     * @param {} newSelection
     * @param {} oldSelection
     * @return {Boolean}
     */
    onBeforeSelect: function(sm, newSelection, oldSelection) {

        if (this.requiredGrant && newSelection.isLeaf()) {
            var accountGrants =  newSelection.attributes.container.account_grants || {};
            if (! accountGrants[this.requiredGrant]) {
                var message = '<b>' +String.format(_("You are not allowed to select the {0} '{1}':"), this.containerName, newSelection.attributes.text) + '</b><br />' +
                              String.format(_("{0} grant is required for desired action"), this.requiredGrant);
                Ext.Msg.alert(_('Insufficient Grants'), message);
                return false;
            }
        }
    },
    
    /**
     * record got dropped on container node
     * 
     * @param {Object} dropEvent
     * @private
     * 
     * TODO use Ext.Direct
     */
    onBeforeNodeDrop: function(dropEvent) {
        var targetContainerId = dropEvent.target.id;
        
        // get selection filter from grid
        var sm = this.app.getMainScreen().getCenterPanel().getGrid().getSelectionModel();
        if (sm.getCount() === 0) {
            return false;
        }
        var filter = sm.getSelectionFilter();
        
        // move messages to folder
        Ext.Ajax.request({
            params: {
                method: 'Tinebase_Container.moveRecordsToContainer',
                targetContainerId: targetContainerId,
                filterData: filter,
                model: this.recordClass.getMeta('modelName'),
                applicationName: this.recordClass.getMeta('appName')
            },
            scope: this,
            success: function(result, request){
                // update grid
                this.app.getMainScreen().getCenterPanel().loadGridData();
            }
        });
        
        // prevent repair actions
        dropEvent.dropStatus = true;
        return true;
    },
    
    /**
     * called on filtertrigger of filter toolbar
     * clears selection silently
     */
    onFilterChange: function() {
        /* 2012-01-30 cweiss: i have no idea what this was for.
         * if its needed please document here!
        var sm = this.getSelectionModel();
        
        sm.suspendEvents();
        sm.clearSelections();
        sm.resumeEvents();
        */
    },
    
    /**
     * called when tree selection changes
     * 
     * @param {} sm
     * @param {} node
     */
    onSelectionChange: function(sm, nodes) {
                
        if (this.filterMode == 'gridFilter' && this.filterPlugin) {
            this.filterPlugin.onFilterChange();
        }
        if (this.filterMode == 'filterToolbar' && this.filterPlugin) {
            // get filterToolbar
            var ftb = this.filterPlugin.getGridPanel().filterToolbar;
            
            // in case of filterPanel
            ftb = ftb.activeFilterPanel ? ftb.activeFilterPanel : ftb;

            // remove all ftb container and /toberemoved/ filters
            ftb.supressEvents = true;
            ftb.filterStore.each(function(filter) {
                var field = filter.get('field');
                // @todo find criteria what to remove
                if (field === 'container_id' || field === 'attender' || field === 'path') {
                    ftb.deleteFilter(filter);
                }
            }, this);
            ftb.supressEvents = false;
            
            // set ftb filters according to tree selection
            var containerFilter = this.getFilterPlugin().getFilter();
            ftb.addFilter(new ftb.record(containerFilter));
        
            ftb.onFiltertrigger();
            
            // finally select the selected node, as filtertrigger clears all selections
            sm.suspendEvents();
            Ext.each(nodes, function(node) {
                sm.select(node, Ext.EventObject, true);
            }, this);
            sm.resumeEvents();
        }
    },
    
    /**
     * selects path by container Path
     * 
     * @param {String} containerPath
     * @param {String} [attr]
     * @param {Function} [callback]
     */
    selectContainerPath: function(containerPath, attr, callback) {
        return this.selectPath(this.getTreePath(containerPath), attr, callback);
    },
    
    /**
     * get default container for new records
     * 
     * @param {String} default container registry key
     * @return {Tine.Tinebase.Model.Container}
     */
    getDefaultContainer: function(registryKey) {
        if (! registryKey) {
            registryKey = 'defaultContainer';
        }
        
        var container = Tine[this.appName].registry.get(registryKey);
        
        return this.getSelectedContainer('addGrant', container, true);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets', 'Tine.widgets.container');

/**
 * Container Properties dialog
 * 
 * @namespace   Tine.widgets.container
 * @class       Tine.widgets.container.PropertiesDialog
 * @extends     Tine.widgets.dialog.EditDialog
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 * @constructor
 * @param {Object} config The configuration options.
 */
Tine.widgets.container.PropertiesDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
    
    /**
     * @cfg {Tine.Tinebase.container.models.container}
     * Container to manage grants for
     */
    grantContainer: null,
    
    /**
     * @cfg {string}
     * Name of container folders, e.g. Addressbook
     */
    containerName: null,
    
    /**
     * @private
     */
    windowNamePrefix: 'ContainerPropertiesWindow_',
    loadRecord: false,
    tbarItems: [],
    evalGrants: false,
    
    /**
     * @private
     */
    initComponent: function() {
        
        // my grants
        this.myGrants = [];
        for (var grant in this.grantContainer.account_grants) {
            if (this.grantContainer.account_grants.hasOwnProperty(grant) && this.grantContainer.account_grants[grant] && Tine.widgets.container.GrantsGrid.prototype[grant + 'Title']) {
                this.myGrants.push({
                    title: _(Tine.widgets.container.GrantsGrid.prototype[grant + 'Title']),
                    description: _(Tine.widgets.container.GrantsGrid.prototype[grant + 'Description'])
                })
            }
        }
        
        this.myGrantsTemplate = new Ext.XTemplate(
            '<tpl for=".">',
                '<span class="tine-wordbox" ext:qtip="','{[this.encode(values.description)]}','">','{[this.encode(values.title)]}','</span>',
            '</tpl>',
            {
                encode: function(description) { return Tine.Tinebase.common.doubleEncode(description); }
            }
        ).compile();
        
        Tine.widgets.container.PropertiesDialog.superclass.initComponent.call(this);
    },
    
    /**
     * init record to edit
     * 
     * - overwritten: we don't have a record here 
     */
    initRecord: function() {
    },
    
    /**
     * returns dialog
     */
    getFormItems: function() {
        return {
            xtype: 'tabpanel',
            border: false,
            plain:true,
            activeTab: 0,
            border: false,
            items:[{
                title: _('Properties'),
                border: false,
                frame: true,
                layout: 'form',
                labelAlign: 'top',
                labelSeparator: '',
                layoutConfig: {
                    trackLabels: true
                },
                plugins: [{
                    ptype: 'ux.itemregistry',
                    key:   'Tine.widgets.container.PropertiesDialog.FormItems.Properties'
                }],
                items: [{
                    xtype: 'textfield',
                    anchor: '100%',
                    readOnly: true,
                    fieldLabel: _('Name'),
                    value: this.grantContainer.name
                }, {
                    xtype: 'colorfield',
                    width: 40,
                    readOnly: true,
                    fieldLabel: _('Color'),
                    value: this.grantContainer.color
                }, {
                    xtype: 'label',
                    anchor: '100%',
                    readOnly: true,
                    fieldLabel: _('My Grants'),
                    html: this.myGrantsTemplate.applyTemplate(this.myGrants)
                }]
            }]
        };
    },
    
    /**
     * @private
     */
    onApplyChanges: function() {
        this.purgeListeners();
        this.window.close();
    }
});

/**
 * grants dialog popup / window
 */
Tine.widgets.container.PropertiesDialog.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: 700,
        height: 450,
        name: Tine.widgets.container.PropertiesDialog.windowNamePrefix + Ext.id(),
        contentPanelConstructor: 'Tine.widgets.container.PropertiesDialog',
        contentPanelConstructorConfig: config,
        modal: true
    });
    return window;
};
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * TODO         extend Tine.widgets.grid.PickerFilter
 */
Ext.ns('Tine.widgets.container');

/**
 * @namespace   Tine.widgets.container
 * @class       Tine.widgets.container.FilterModel
 * @extends     Tine.widgets.grid.FilterModel
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.widgets.container.FilterModel = Ext.extend(Tine.widgets.grid.FilterModel, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     */
    app: null,
    
    /**
     * @cfg {Tine.Tinebase.data.Record} recordClass
     * record definition class  (required)
     */
    recordClass: null,
    
    /**
     * @cfg {Array} operators allowed operators
     */
    operators: ['equals', 'in'],
    
    /**
     * @cfg {String} field container field (defaults to container_id)
     */
    field: 'container_id',
    
    /**
     * @cfg {String} defaultOperator default operator, one of <tt>{@link #operators} (defaults to equals)
     */
    defaultOperator: 'equals',
    
    /**
     * @cfg {String} defaultValue default value (defaults to all)
     */
    defaultValue: {path: '/'},
    
    /**
     * @private
     */
    initComponent: function() {
        
        // get recordClass if string is given
        if(Ext.isString(this.recordClass)) {
            var split = this.recordClass.split('.');
            this.recordClass = Tine[split[0]].Model[split[1]];
        }
        
        // get application if string is given
        if(Ext.isString(this.app)) {
            this.app = Tine[this.app];
        }
        
        Tine.widgets.container.FilterModel.superclass.initComponent.call(this);
        
        this.containerName = this.app.i18n.n_hidden(this.recordClass.getMeta('containerName'), this.recordClass.getMeta('containersName'), 1);
        this.containersName = this.app.i18n._hidden(this.recordClass.getMeta('containersName'));
        
        this.label = this.containerName;
    },
    
    /**
     * returns valueType of given filter
     * 
     * @param {Record} filter
     * @return {String}
     */
    getValueType: function(filter) {
        var operator = filter.get('operator') ? filter.get('operator') : this.defaultOperator;
        
        var valueType = 'selectionComboBox';
        switch (operator) {
            case 'in':
                valueType = 'FilterModelMultipleValueField';
                break;
        }
        
        return valueType;
    },
    
    /**
     * called on operator change of a filter row
     * @private
     */
    onOperatorChange: function(filter, newOperator) {
        this.supr().onOperatorChange.call(this, filter, newOperator);
        
        var valueType = this.getValueType(filter);
        
        for (var valueField in filter.valueFields) {
            filter.valueFields[valueField][valueField == valueType ? 'show' : 'hide']();
        };
        
        filter.formFields.value = filter.valueFields[valueType];
        filter.formFields.value.setWidth(filter.formFields.value.width);
        filter.formFields.value.wrap.setWidth(filter.formFields.value.width);
        filter.formFields.value.syncSize();
    },
    
    /**
     * value renderer
     * 
     * @param {Ext.data.Record} filter line
     * @param {Ext.Element} element to render to 
     */
    valueRenderer: function(filter, el) {
        var valueType = this.getValueType(filter);
        
        filter.valueFields = {};
        filter.valueFields.selectionComboBox = new Tine.widgets.container.selectionComboBox({
            hidden: valueType != 'selectionComboBox',
            app: this.app,
            filter: filter,
            width: 200,
            listWidth: 200,
            //id: 'tw-ftb-frow-valuefield-' + filter.id,
            value: filter.data.value ? filter.data.value : this.defaultValue,
            renderTo: el,
            allowNodeSelect: true,
            recordClass: this.recordClass,
            appName: this.recordClass.getMeta('appName'),
            containerName: this.containerName,
            containersName: this.containersName,
            getValue: function() {
                return this.selectedContainer ? this.selectedContainer.path : null;
            },
            setValue: function(value) {
                if (this.filter.get('operator') !== 'in') {
                    var operatorText = this.filter.data.operator === 'personalNode' ? _('is personal of') : _('is equal to');
                    
                    // use equals for node 'My containers'
                    if (value.path && value.path === Tine.Tinebase.container.getMyNodePath()) {
                        operatorText = _('is equal to')
                    }
                    this.filter.formFields.operator.setRawValue(operatorText);
                }
            
                return Tine.widgets.container.selectionComboBox.prototype.setValue.call(this, value);
            },
            listeners: {
                scope: this, 
                select: this.onFiltertrigger,
                specialkey: function(field, e){
                     if(e.getKey() == e.ENTER){
                         this.onFiltertrigger();
                     }
                }
            }
        });
        
        filter.valueFields.FilterModelMultipleValueField = new Tine.widgets.container.FilterModelMultipleValueField({
            hidden: valueType != 'FilterModelMultipleValueField',
            app: this.app,
            recordClass: this.recordClass,
            containerName: this.containerName,
            containersName: this.containersName,
            filter: filter,
            width: 200,
            //id: 'tw-ftb-frow-valuefield-' + filter.id,
            value: filter.data.value ? filter.data.value : this.defaultValue,
            renderTo: el,
            listeners: {
                scope: this, 
                select: this.onFiltertrigger,
                specialkey: function(field, e){
                     if(e.getKey() == e.ENTER){
                         this.onFiltertrigger();
                     }
                }
            }
        });
        
        return filter.valueFields[valueType];
    }
});

Tine.widgets.grid.FilterToolbar.FILTERS['tine.widget.container.filtermodel'] = Tine.widgets.container.FilterModel;


/**
 * @namespace   Tine.widgets.container
 * @class       Tine.widgets.container.FilterModelMultipleValueField
 * @extends     Ext.ux.form.LayerCombo
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.widgets.container.FilterModelMultipleValueField = Ext.extend(Ext.ux.form.LayerCombo, {
    /**
     * @cfg {string} containerName
     * name of container (singular)
     */
    containerName: 'container',
    
    /**
     * @cfg {string} containerName
     * name of container (plural)
     */
    containersName: 'containers',
    
    /**
     * @cfg {Tine.Tinebase.data.Record} recordClass
     * record definition class  (required)
     */
    recordClass: null,
    
    hideButtons: false,
    layerAlign : 'tr-br?',
    minLayerWidth: 400,
    layerHeight: 300,
    
    lazyInit: true,
    
    formConfig: {
        labelAlign: 'left',
        labelWidth: 30
    },
    
    initComponent: function() {
        this.on('beforecollapse', this.onBeforeCollapse, this);
        this.store = new Ext.data.SimpleStore({
            fields: this.recordClass
        });
        
        this.supr().initComponent.call(this);
    },
    
    /**
     * @return Ext.grid.ColumnModel
     */
    getColumnModel: function() {
        return new Ext.grid.ColumnModel({
            defaults: {
                sortable: false
            },
            columns:  [
                {id: 'name', header: String.format(_('Selected  {0}'), this.containersName), dataIndex: 'name'}
            ]
        });
    },
    
    /**
     * 
     * @return {Array} of containerData
     */
    getFormValue: function() {
        var container = [];
        
        this.store.each(function(containerRecord) {
            container.push(containerRecord.data);
        }, this);
        
        return container;
    },
    
    getItems: function() {
        var containerSelectionCombo = this.containerSelectionCombo = new Tine.widgets.container.selectionComboBox({
            allowNodeSelect: true,
            allowBlank: true,
            blurOnSelect: true,
            recordClass: this.recordClass,
            appName: this.recordClass.getMeta('appName'),
            containerName: this.containerName,
            containersName: this.containersName,
            listeners: {
                scope: this, 
                select: this.onContainerSelect
            }
        });
                
        this.containerGridPanel = new Tine.widgets.grid.PickerGridPanel({
            height: this.layerHeight || 'auto',
            recordClass: Tine.Tinebase.Model.Container,
            store: this.store,
            autoExpandColumn: 'name',
            getColumnModel: this.getColumnModel.createDelegate(this),
            initActionsAndToolbars: function() {
                Tine.widgets.grid.PickerGridPanel.prototype.initActionsAndToolbars.call(this);
                
                this.tbar = new Ext.Toolbar({
                    layout: 'fit',
                    items: [
                        containerSelectionCombo
                    ]
                });
            }
        });
        var items = [this.containerGridPanel];
        
        return items;
    },
    
    /**
     * cancel collapse if ctx menu or container selection is shown
     * 
     * @return Boolean
     */
    onBeforeCollapse: function() {
        var contextMenuVisible = this.containerGridPanel.contextMenu && ! this.containerGridPanel.contextMenu.hidden,
            containerSelectionVisible = this.containerSelectionCombo.dlg && ! this.containerSelectionCombo.dlg.win.isDestroyed;
            
        return ! (contextMenuVisible || this.containerSelectionCombo.isExpanded() || containerSelectionVisible);
    },
    
    onContainerSelect: function(field, containerData) {
        var existingRecord = this.store.getById(containerData.id);
        if (! existingRecord) {
            var data = (containerData.data) ? containerData.data : containerData;
            this.store.add(new Tine.Tinebase.Model.Container(data));
            
        } else {
            var idx = this.store.indexOf(existingRecord);
            var row = this.containerGridPanel.getView().getRow(idx);
            Ext.fly(row).highlight();
        }
        
        this.containerSelectionCombo.clearValue();
    },
    
    /**
     * @param {Array/Object} value
     * @return {Ext.form.Field} this
     */
    setValue: function(value) {
        value = Ext.isArray(value) ? value : [value];
        this.currentValue = [];
        
        this.store.removeAll();
        var containerNames = [];
        Ext.each(value, function(containerData) {
            // ignore server name for node 'My containers'
            if (containerData.path && containerData.path === Tine.Tinebase.container.getMyNodePath()) {
                containerData.name = null;
            }
            containerData.name = containerData.name || Tine.Tinebase.container.path2name(containerData.path, this.containerName, this.containersName);
            containerData.id = containerData.id ||containerData.path;
            
            this.store.add(new Tine.Tinebase.Model.Container(containerData));
            this.currentValue.push(containerData);
            containerNames.push(containerData.name);
        }, this);
        
        this.setRawValue(containerNames.join(', '));
        return this;
    },
    
    /**
     * sets values to innerForm
     */
    setFormValue: function(value) {
        // nothing to do
    }
});

/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * TODO make initial color work again in Ext.menu.ColorMenu
 */
 
Ext.ns('Tine.widgets', 'Tine.widgets.tags');

/**
 * Class for a single tag panel
 * 
 * @namespace   Tine.widgets.tags
 * @class       Tine.widgets.tags.TagPanel
 * @extends     Ext.Panel
 */
Tine.widgets.tags.TagPanel = Ext.extend(Ext.Panel, {
    /**
     * @cfg {Tine.Tinebase.Application} app Application which uses this panel
     */
    app: null,
    /**
     * @cfg {String} recordId Id of record this panel is displayed for
     */
    recordId: '',
    /**
     * @cfg {Array} tags Initial tags
     */
    tags: null,
    /**
     * @var {Ext.data.JsonStore}
     * Holds tags of the record this panel is displayed for
     */
    recordTagsStore: null,
    /**
     * @var {Ext.data.JsonStore} Store for available tags
     */
    availableTagsStore: false,
    /**
     * @var {Ext.form.ComboBox} live search field to search tags to add
     */
    searchField: null,
    
    iconCls: 'action_tag',
    layout: 'fit',
    bodyStyle: 'padding: 2px 2px 2px 2px',
    collapsible: true,
    border: false,
    
    /**
     * @private
     */
    initComponent: function(){
        this.title =  _('Tags');
        this.app = Ext.isString(this.app) ? Tine.Tinebase.appMgr.get(this.app) : this.app;
        
        // init recordTagsStore
        this.tags = [];
        this.recordTagsStore = new Ext.data.JsonStore({
            id: 'id',
            fields: Tine.Tinebase.Model.Tag,
            data: this.tags
        });
        
        // init availableTagsStore
        this.availableTagsStore = new Ext.data.JsonStore({
            id: 'id',
            root: 'results',
            totalProperty: 'totalCount',
            fields: Tine.Tinebase.Model.Tag,
            baseParams: {
                method: 'Tinebase.searchTags',
                filter: {
                    application: this.app.appName,
                    grant: 'use'
                },
                paging : {}
            }
        });
        
        this.initSearchField();
        
        this.bottomBar = new Ext.Container({
            layout: 'column',
            items: [
                Ext.apply(this.searchField, {columnWidth: .99}),
                new Ext.Button({
                    text: '',
                    width: 16,
                    iconCls: 'action_add',
                    tooltip: _('Add a new personal tag'),
                    scope: this,
                    handler: function() {
                        Ext.Msg.prompt(_('Add New Personal Tag'),
                                       _('Please note: You create a personal tag. Only you can see it!') + ' <br />' + _('Enter tag name:'), 
                            function(btn, text) {
                                if (btn == 'ok'){
                                    this.onTagAdd(text);
                                }
                            }, 
                        this, false, this.searchField.lastQuery);
                    }
                })
            ]
        
        });
        
        var tagTpl = new Ext.XTemplate(
            '<tpl for=".">',
               '<div class="x-widget-tag-tagitem" id="{id}">',
                    '<div class="x-widget-tag-tagitem-color" style="background-color: {color};">&#160;</div>', 
                    '<div class="x-widget-tag-tagitem-text" ext:qtip="', 
                        '{[this.encode(values.name)]}', 
                        '<tpl if="type == \'personal\' ">&nbsp;<i>(' + _('personal') + ')</i></tpl>',
                        '</i>&nbsp;[{occurrence}]',
                        '<tpl if="description != null && description.length &gt; 1"><hr>{[this.encode(values.description)]}</tpl>" >',
                        
                        '&nbsp;{[this.encode(values.name)]}',
                    '</div>',
                '</div>',
            '</tpl>' ,{
                encode: function(value) {
                    return Tine.Tinebase.common.doubleEncode(value);
                }
            }
        );
        
        this.dataView = new Ext.DataView({
            store: this.recordTagsStore,
            tpl: tagTpl,
            autoHeight:true,
            multiSelect: true,
            overClass:'x-widget-tag-tagitem-over',
            selectedClass:'x-widget-tag-tagitem-selected',
            itemSelector:'div.x-widget-tag-tagitem',
            emptyText: _('No Tags to display')
        });
        this.dataView.on('contextmenu', function(dataView, selectedIdx, node, event){
            if (!this.dataView.isSelected(selectedIdx)) {
                this.dataView.clearSelections();
                this.dataView.select(selectedIdx);
            }
            event.preventDefault();
            
            var selectedTags = this.dataView.getSelectedRecords();
            var selectedTag = selectedTags.length == 1 ? selectedTags[0] : null;
            
            var allowDelete = true;
            for (var i=0; i<selectedTags.length; i++) {
                if (selectedTags[i].get('type') == 'shared') {
                    allowDelete = false;
                }
            }
            
            var menu = new Ext.menu.Menu({
                items: [
                    new Ext.Action({
                        scope: this,
                        text: Tine.Tinebase.translation.ngettext('Detach tag', 'Detach tags', selectedTags.length),
                        iconCls: 'x-widget-tag-action-detach',
                        handler: function() {
                            for (var i=0,j=selectedTags.length; i<j; i++){
                                this.recordTagsStore.remove(selectedTags[i]);
                            }
                        }
                    }),
                    '-',
                    {
                        text: _('Edit tag'),
                        disabled: !(selectedTag && allowDelete),
                        menu: {
                            items: [
                                new Ext.Action({
                                    text: _('Rename Tag'),
                                    selectedTag: selectedTag,
                                    scope: this,
                                    handler: function(action) {
                                        var tag = action.selectedTag;
                                        Ext.Msg.prompt(_('Rename Tag') + ' "'+ tag.get('name') +'"', _('Please enter a new name:'), function(btn, text){
                                            if (btn == 'ok'){
                                                tag.set('name', text);
                                                this.onTagUpdate(tag);
                                            }
                                        }, this, false, tag.get('name'));
                                    }
                                }),
                                new Ext.Action({
                                    text: _('Edit Description'),
                                    selectedTag: selectedTag,
                                    scope: this,
                                    handler: function(action) {
                                        var tag = action.selectedTag;
                                        Ext.Msg.prompt(_('Description for tag') + ' "'+ tag.get('name') +'"', _('Please enter new description:'), function(btn, text){
                                            if (btn == 'ok'){
                                                tag.set('description', text);
                                                this.onTagUpdate(tag);
                                            }
                                        }, this, 30, tag.get('description'));
                                    }
                                }),
                                new Ext.Action({
                                    text: _('Change Color'),
                                    iconCls: 'action_changecolor',
                                    scope: this,
                                    menu: new Ext.menu.ColorMenu({
                                        // not working any longer ->
                                        //value: selectedTag ? selectedTag.get('color') : '#FFFFFF',
                                        // something like this should work -> 
                                        // (from extjs api doc: (value) The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that the hex codes are case-sensitive.)
                                        //value: selectedTag ? Ext.util.Format.lowercase(selectedTag.get('color').substr(1)) : 'ffffff',
                                        scope: this,
                                        listeners: {
                                            select: function(menu, color) {
                                                color = '#' + color;
                                                
                                                if (selectedTag.get('color') != color) {
                                                    selectedTag.set('color', color);
                                                    this.onTagUpdate(selectedTag);
                                                }
                                            },
                                            scope: this
                                        }
                                    })                                        
                                })                                    
                            ]
                        }
                    },
                    new Ext.Action({
                        disabled: !allowDelete,
                        scope: this,
                        text: Tine.Tinebase.translation.ngettext('Delete Tag', 'Delete Tags', selectedTags.length),
                        iconCls: 'action_delete',
                        handler: function() {
                            var tagsToDelete = [];
                            for (var i=0,j=selectedTags.length; i<j; i++){
                                // don't request to delete non existing tags
                                if (selectedTags[i].id.length > 20) {
                                    tagsToDelete.push(selectedTags[i].id);
                                }
                            }
                            
                            // @todo use correct strings: Realy -> Really / disapear -> disappear
                            Ext.MessageBox.confirm(
                                Tine.Tinebase.translation.ngettext('Realy Delete Selected Tag?', 'Realy Delete Selected Tags?', selectedTags.length), 
                                Tine.Tinebase.translation.ngettext('the selected tag will be deleted and disapear for all entries', 
                                                        'The selected tags will be removed and disapear for all entries', selectedTags.length), 
                                function(btn) {
                                    if (btn == 'yes'){
                                        Ext.MessageBox.wait(_('Please wait a moment...'), Tine.Tinebase.translation.ngettext('Deleting Tag', 'Deleting Tags', selectedTags.length));
                                        Ext.Ajax.request({
                                            params: {
                                                method: 'Tinebase.deleteTags', 
                                                ids: tagsToDelete
                                            },
                                            success: function(_result, _request) {
                                                // reset avail tag store
                                                this.availableTagsStore.lastOptions = null;
                                                
                                                for (var i=0,j=selectedTags.length; i<j; i++){
                                                    this.recordTagsStore.remove(selectedTags[i]);
                                                }
                                                Ext.MessageBox.hide();
                                            },
                                            failure: function ( result, request) {
                                                Ext.MessageBox.alert(_('Failed'), _('Could not delete Tag(s).'));
                                            },
                                            scope: this 
                                        });
                                    }
                            }, this);
                        }
                    })
                ]
            });
            menu.showAt(event.getXY());
        },this);
        
        this.formField = {
            layout: 'form',
            items: new Tine.widgets.tags.TagFormField({
                recordTagsStore: this.recordTagsStore
            })
        };
        
        this.items = [{
            xtype: 'panel',
            layout: 'fit',
            bbar: this.bottomBar,
            items: [
                this.dataView,
                this.formField
            ]
        }];
        
        Tine.widgets.dialog.MultipleEditDialogPlugin.prototype.registerSkipItem(this);
        Tine.widgets.tags.TagPanel.superclass.initComponent.call(this);
    },
    
    getFormField: function() {
        return this.formField.items;
    },
    
    /**
     * @private
     */
    onTagAdd: function(tagName) {
        if (tagName.length < 3) {
            Ext.Msg.show({
               title: _('Notice'),
               msg: _('The minimum tag length is three.'),
               buttons: Ext.Msg.OK,
               animEl: 'elId',
               icon: Ext.MessageBox.INFO
            });
        } else {
            var isAttached = false;
            this.recordTagsStore.each(function(tag){
                if(tag.data.name == tagName) {
                    isAttached = true;
                }
            },this);
            
            if (!isAttached) {
                var tagToAttach = false;
                this.availableTagsStore.each(function(tag){
                    if(tag.data.name == tagName) {
                        tagToAttach = tag;
                    }
                }, this);
                
                if (!tagToAttach) {
                    tagToAttach = new Tine.Tinebase.Model.Tag({
                        name: tagName,
                        type: 'personal',
                        description: '',
                        color: '#FFFFFF'
                    });
                    
                    if (! Ext.isIE) {
                        this.el.mask();
                    }
                    Ext.Ajax.request({
                        params: {
                            method: 'Tinebase.saveTag', 
                            tag: tagToAttach.data
                        },
                        success: function(_result, _request) {
                            var tagData = Ext.util.JSON.decode(_result.responseText);
                            var newTag = new Tine.Tinebase.Model.Tag(tagData, tagData.id);
                            this.recordTagsStore.add(newTag);
                            
                            // reset avail tag store
                            this.availableTagsStore.lastOptions = null;
                            this.el.unmask();
                        },
                        failure: function ( result, request) {
                            Ext.MessageBox.alert(_('Failed'), _('Could not create tag.'));
                            this.el.unmask();
                        },
                        scope: this 
                    });
                } else {
                    this.recordTagsStore.add(tagToAttach);
                }
            }
        }
    },
    onTagUpdate: function(tag) {
        if (tag.get('name').length < 3) {
            Ext.Msg.show({
               title: _('Notice'),
               msg: _('The minimum tag length is three.'),
               buttons: Ext.Msg.OK,
               animEl: 'elId',
               icon: Ext.MessageBox.INFO
            });
        } else {
            this.el.mask();
            Ext.Ajax.request({
                params: {
                    method: 'Tinebase.saveTag', 
                    tag: tag.data
                },
                success: function(_result, _request) {
                    // reset avail tag store
                    this.availableTagsStore.lastOptions = null;
                    this.el.unmask();
                },
                failure: function ( result, request) {
                    Ext.MessageBox.alert(_('Failed'), _('Could not update tag.'));
                    this.el.unmask();
                },
                scope: this 
            });
        }
    },
    
    /**
     * @private
     */
    initSearchField: function() {
        var tpl = new Ext.XTemplate(
            '<tpl for="."><div class="x-combo-list-item" ext:qtip="', 
                '{[this.encode(values.name)]}', 
                '<tpl if="type == \'personal\' ">&nbsp;<i>(' + _('personal') + ')</i></tpl>',
                '</i>&nbsp;[{occurrence}]',
                '<tpl if="description != null && description.length &gt; 1"><hr>{[this.encode(values.description)]}</tpl>">',
                
                '{[this.encode(values.name)]} <tpl if="type == \'personal\' "><i>(' + _('personal') + ')</i></tpl>',
            '</div></tpl>',{
                encode: function(value) {
                    return Tine.Tinebase.common.doubleEncode(value);
                }
            }
        );
        
        this.searchField = new Ext.form.ComboBox({
            store: this.availableTagsStore,
            mode: 'local',
            //width: 170,
            enableKeyEvents: true,
            displayField:'name',
            typeAhead: true,
            emptyText: _('Enter tag name'),
            loadingText: _('Searching...'),
            typeAheadDelay: 10,
            minChars: 1,
            hideTrigger:false,
            triggerAction: 'all',
            forceSelection: true,
            tpl: tpl
            //expand: function(){}
        });
        
        // load data once
        this.searchField.on('focus', function(searchField){
            if (! searchField.store.lastOptions) {
                // hack to supress selecting the first item from the freshly
                // retrieved store
                searchField.hasFocus = false;
                this.availableTagsStore.load({
                    scope: this,
                    callback: function() {
                        searchField.hasFocus = true;
                    }
                });
            }
        }, this);
        
        this.searchField.on('select', function(searchField, selectedTag){
            if(this.recordTagsStore.getById(selectedTag.id) === undefined) {
                this.recordTagsStore.add(selectedTag);
            }
            searchField.emptyText = '';
            searchField.clearValue();
        },this);
        
        /*
        // filter invalid
        this.searchField.on('beforequery', function(qe) {
            var query = qe.query;
            if (this.searchField.store.find('name', query) < 0) {
                console.log(query);
            }
            //var val = searchField.el.dom.value;
        }, this);
        */
        /*
        this.searchField.on('specialkey', function(searchField, e){
             if(e.getKey() == e.ENTER){
                var value = searchField.getValue();
                
                }
                searchField.emptyText = '';
                searchField.clearValue();
             }
        }, this);
        */
        
        // workaround extjs bug:
        this.searchField.on('blur', function(searchField){
            searchField.emptyText = _('Enter tag name');
            searchField.clearValue();
        },this);
    }
    

});

/**
 * @private Helper class to have tags processing in the standard form/record cycle
 */
Tine.widgets.tags.TagFormField = Ext.extend(Ext.form.Field, {
    /**
     * @cfg {Ext.data.JsonStore} recordTagsStore a store where the record tags are in.
     */
    recordTagsStore: null,
    
    name: 'tags',
    hidden: true,
    labelSeparator: '',
    /**
     * @private
     */
    initComponent: function() {
        Tine.widgets.tags.TagFormField.superclass.initComponent.call(this);
        //this.hide();
    },
    /**
     * returns tags data of the current record
     */
    getValue: function() {
        var value = [];
        this.recordTagsStore.each(function(tag){
            if(tag.id.length > 5 && ! String(tag.id).match(/ext-record/)) {
                //if we have a valid id we just return the id
                value.push(tag.id);
            } else {
                //it's a new tag and will be saved on the fly
                value.push(tag.data);
            }
        });
        return value;
    },
    /**
     * sets tags from an array of tag data objects (not records)
     */
    setValue: function(value){
        // replace template fields
        Tine.Tinebase.Model.Tag.replaceTemplateField(value);
        
        this.recordTagsStore.loadData(value);
    }

});

/**
 * Dialog for editing a tag itself
 */
Tine.widgets.tags.TagEditDialog = Ext.extend(Ext.Window, {
    width: 200,
    height: 300,
    layout: 'fit',
    margins: '0px 5px 0px 5px',
    
    initComponent: function() {
        this.items = new Ext.form.FormPanel({
            defaults: {
                xtype: 'textfield',
                anchor: '100%'
            },
            labelAlign: 'top',
            items: [
                {
                    name: 'name',
                    fieldLabel: 'Name'
                },
                {
                    name: 'description',
                    fieldLabel: _('Description')
                },
                {
                    name: 'color',
                    fieldLabel: _('Color')
                }
            ]
            
        });
        Tine.widgets.tags.TagEditDialog.superclass.initComponent.call(this);
    }
});

Tine.widgets.tags.EditDialog = Ext.extend(Ext.Window, {
    layout:'border',
    width: 640,
    heigh: 480,
    
    initComponent: function() {
        this.items = [
        {
            region: 'west',
            split: true
        },
        {
            region: 'center',
            split: true
        }
        ];
        Tine.widgets.tags.EditDialog.superclass.call(this);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * TODO         use new filter syntax in onBeforeQuery when TagFilter is refactored and extends Tinebase_Model_Filter_FilterGroup 
 */
 
Ext.ns('Tine.widgets', 'Tine.widgets.tags');

/**
 * @namespace   Tine.widgets.tags
 * @class       Tine.widgets.tags.TagCombo
 * @extends     Ext.ux.form.ClearableComboBox
 */
Tine.widgets.tags.TagCombo = Ext.extend(Ext.ux.form.ClearableComboBox, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     */
    app: null,
    
    /**
     * @cfg {Bool} findGlobalTags true to find global tags during search (default: true)
     */
    findGlobalTags: true,

    /**
     * @cfg {Bool} onlyUsableTags true to find only usable flags for the user (default: true)
     */
    onlyUsableTags: false,
    
    emptyText: null,
    typeAhead: true,
    mode: 'remote',
    triggerAction: 'all',
    displayField: 'name',
    valueField: 'id',
    minChars: 3,
    
    /**
     * @private
     */
    initComponent: function() {
        this.emptyText = this.emptyText ? this.emptyText : _('tag name');
        
        this.initStore();
        this.initTemplate();
        
        Tine.widgets.tags.TagCombo.superclass.initComponent.call(this);
        
        this.on('select', this.onSelectRecord, this);
        
        this.on('beforequery', this.onBeforeQuery, this);
    },
    
    /**
     * hander of select event
     * NOTE: executed after native onSelect method
     */
    onSelectRecord: function(){
        var v = this.getValue();
        
        if(String(v) !== String(this.startValue)){
            this.fireEvent('change', this, v, this.startValue);
        }
        
    },
    
    /**
     * use beforequery to set query filter
     * 
     * @param {Event} qevent
     */
    onBeforeQuery: function(qevent){
        
        var filter = {
            name: (qevent.query && qevent.query != '') ? '%' + qevent.query + '%' : '',
            application: this.app ? this.app.appName : '',
            grant: (this.onlyUsableTags) ? 'use' : 'view' 
        };
        
        this.store.baseParams.filter = filter;
    },

    /**
     * set value
     * 
     * @param {} value
     */
    setValue: function(value) {
        
        if (typeof value === 'object' && Object.prototype.toString.call(value) === '[object Object]') {
            if (! this.store.getById(value.id)) {
                this.store.addSorted(new Tine.Tinebase.Model.Tag(value));
            }
            value = value.id;
        }
        
        Tine.widgets.tags.TagCombo.superclass.setValue.call(this, value);
        
    },
    
    /**
     * init store
     */
    initStore: function() {
        var baseParams = {
            method: 'Tinebase.searchTags',
            paging: {}
        };
        
        this.store = new Ext.data.JsonStore({
            id: 'id',
            root: 'results',
            totalProperty: 'totalCount',
            fields: Tine.Tinebase.Model.Tag,
            baseParams: baseParams
        });

    },
    
    /**
     * init template
     */
    initTemplate: function() {
        this.tpl = new Ext.XTemplate(
            '<tpl for=".">', 
                '<div class="x-combo-list-item">',
                    '<div class="tb-grid-tags" style="background-color:{values.color};">&#160;</div>',
                    '<div class="x-widget-tag-tagitem-text" ext:qtip="', 
                        '{[this.encode(values.name)]}', 
                        '<tpl if="type == \'personal\' ">&nbsp;<i>(' + _('personal') + ')</i></tpl>',
                        '</i>&nbsp;[{occurrence}]',
                        '<tpl if="description != null && description.length &gt; 1"><hr>{[this.encode(values.description)]}</tpl>" >',
                        
                        '&nbsp;{[this.encode(values.name)]}',
                        '<tpl if="type == \'personal\' ">&nbsp;<i>(' + _('personal') + ')</i></tpl>',
                    '</div>',
                '</div>', 
            '</tpl>',
            {
                encode: function(value) {
                     if (value) {
                        return Tine.Tinebase.common.doubleEncode(value);
                    } else {
                        return '';
                    }
                }
            }
        );
    }
});

Ext.reg('Tine.widgets.tags.TagCombo', Tine.widgets.tags.TagCombo);
/*
 * Tine 2.0
 * 
 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3 
 * @author Alexander Stintzing <alex@stintzing.net>
 * @copyright Copyright (c) 2007-2009
 * Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * TODO use new filter syntax in onBeforeQuery when TagFilter is refactored and
 * extends Tinebase_Model_Filter_FilterGroup
 */

Ext.ns('Tine.widgets', 'Tine.widgets.tags');

/**
 * @namespace Tine.widgets.tags
 * @class Tine.widgets.tags.TagToggleBox
 * @extends Ext.tree.TreePanel
 */
Tine.widgets.tags.TagToggleBox = Ext.extend(Ext.form.FormPanel, {

    layout : 'fit',
    border : false,
    cls : 'tw-editdialog',
    store : null,
    buttonAlign : 'right',
    win: null,
    mode: null,
    
    anchor : '100% 100%',
    
    loadMask: null,

    initComponent : function() {
        
        this.addEvents('cancel', 'update', 'close');
        this.initStore();
        this.initTemplate();
        this.initView();

        this.initActions();

        this.initButtons();

        this.items = this.getFormItems();

        Tine.widgets.tags.TagToggleBox.superclass.initComponent.call(this);
    },

    initActions : function() {
        this.action_update = new Ext.Action({
                    text : (this.mode == 'detach') ? _('Detach Tags') : _('Attach Tags'),
                    minWidth : 70,
                    scope : this,
                    handler : this.onUpdate,
                    iconCls : 'action_saveAndClose'
                });
        this.action_cancel = new Ext.Action({
                    text : _('Cancel'),
                    minWidth : 70,
                    scope : this,
                    handler : this.onCancel,
                    iconCls : 'action_cancel'
                });

    },

    initStore : function() {
            
        this.store = new Ext.data.JsonStore({
                    id : 'id',
                    root : 'results',
                    totalProperty : 'totalCount',
                    fields : Tine.Tinebase.Model.Tag,
                    baseParams : {
                        method : 'Tinebase.searchTagsByForeignFilter',
                        filterData: this.selectionModel.getSelectionFilter(),
                        filterName: this.recordClass.getMeta('appName') + '_Model_' +  this.recordClass.getMeta('modelName') + 'Filter'
                        }
                });
        
        this.store.load();
        
    },

    initButtons : function() {
        this.fbar = ['->', this.action_cancel, this.action_update];
    },
    
    getFormItems : function() {
        return {
            border : false,
            frame : true,
            layout : 'form',
            items : [{
                region : 'center',
                layout : {
                    align : 'stretch',
                    type : 'vbox'
                }
            }, this.view]
        };
    },
    
    initTemplate: function() {
        this.tpl = new Ext.XTemplate(
            '<tpl for=".">', 
                '<div class="x-combo-list-item">',
                    '<input class="tagcheckel" type="checkbox" style="margin-right:3px;float:left" id="{values.id}" /><div class="tb-grid-tags" style="margin-top:2px;background-color:{values.color};">&#160;</div>',
                    '<div class="x-widget-tag-tagitem-text" style="margin-top:1px" ext:qtip="', 
                        '{[this.encode(values.name)]}', 
                        '<tpl if="type == \'personal\' ">&nbsp;<i>(' + _('personal') + ')</i></tpl>',
                        '</i>&nbsp;[{occurrence}]',
                        '<tpl if="description != null && description.length &gt; 1"><hr>{[this.encode(values.description)]}</tpl>" >',
                        
                        '&nbsp;{[this.encode(values.name)]}&nbsp;({values.selection_occurrence})',
                        '<tpl if="type == \'personal\' ">&nbsp;<i>(' + _('personal') + ')</i></tpl>',
                    '</div>',
                '</div>', 
            '</tpl>',
            {
                encode: function(value) {
                     if (value) {
                        return Tine.Tinebase.common.doubleEncode(value);
                    } else {
                        return '';
                    }
                }
            }
        );
    },
    
    
    initView : function() {
        this.view = new Ext.DataView({
            anchor : '100% 100%',
            autoScroll : true,
            store : this.store,
            tpl : this.tpl,
            emptyText : _('No Tags to detach found in the selected records'),
            loadingText : _('Please Wait...'),
            style : 'background:#fff;'
        });
    },
    
    onCancel: function() {
        this.fireEvent('cancel');
    },
    // TODO: formitems could not be fetched by formpanel->getform->getvalues
    onUpdate: function() {
        
        this.loadMask = new Ext.LoadMask(this.getEl(), {msg: _('Detaching Tags')});
        this.loadMask.show();
        
        var els = Ext.select('input.tagcheckel');
        var checked = [];
        els.each(function(el){
            if(el.dom.checked) checked.push(el.id);
        });

        var filter = this.selectionModel.getSelectionFilter();
        var filterModel = this.recordClass.getMeta('appName') + '_Model_' +  this.recordClass.getMeta('modelName') + 'Filter';
        
        Tine.Tinebase.detachTagsFromMultipleRecords(filter, filterModel, checked, this.onUpdated.createDelegate(this));

    },
    
    onUpdated: function() {
        this.loadMask.hide();
        this.fireEvent('updated');
    }
    

});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Tine.widgets', 'Tine.widgets.tags');

/**
 * @namespace   Tine.widgets.tags
 * @class       Tine.widgets.tags.TagFilter
 * @extends     Tine.widgets.grid.PickerFilter
 */
Tine.widgets.tags.TagFilter = Ext.extend(Tine.widgets.grid.PickerFilter, {
    
    field: 'tag',
    defaultOperator: 'equals',
    
    /**
     * @private
     */
    initComponent: function() {
        this.picker = Tine.widgets.tags.TagCombo;
        this.recordClass = Tine.Tinebase.Model.Tag;
        
        Tine.widgets.tags.TagFilter.superclass.initComponent.call(this);
        
        this.label = _('Tag');
    }
});
Tine.widgets.grid.FilterToolbar.FILTERS['tinebase.tag'] = Tine.widgets.tags.TagFilter;

/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.widgets', 'Tine.widgets.tags');

/**
 * @namespace   Tine.widgets.tags
 * @class       Tine.widgets.tags.TagsMassAttachAction
 * @extends     Ext.Action
 */
Tine.widgets.tags.TagsMassAttachAction = function(config) {
    config.text = config.text ? config.text : _('Add Tag');
    config.iconCls = 'action_tag';
    config.handler = this.handleClick.createDelegate(this);
    Ext.apply(this, config);
    
    Tine.widgets.tags.TagsMassAttachAction.superclass.constructor.call(this, config);
};

Ext.extend(Tine.widgets.tags.TagsMassAttachAction, Ext.Action, {
    
    /**
     * called when tags got updates
     * 
     * @type Function
     */
    updateHandler: Ext.emptyFn,
    
    /**
     * scope of update handler
     * 
     * @type Object
     */
    updateHandlerScope: null,
    
    loadMask: null,
    
    /**
     * @cfg {mixed} selectionModel
     * 
     * selection model (required)
     */
    selectionModel: null,
    
    /**
     * @cfg {function} recordClass
     * 
     * record class of records to filter for (required)
     */
    recordClass: null,
    
    getFormItems: function() {
        this.tagSelect = new Tine.widgets.tags.TagCombo({
            hideLabel: true,
            anchor: '100%',
            onlyUsableTags: true,
            app: this.app,
            forceSelection: true,
            listeners: {
                scope: this,
                render: function(field){field.focus(false, 500);},
                select: function() {
                    this.onOk();
                }
            }
        });
        
        return [{
            xtype: 'label',
            text: _('Attach the following tag to all selected items:')
        }, this.tagSelect
        ];
    },
    
    handleClick: function() {
        
        this.okButton = new Ext.Button({
            text: _('Ok'),
            minWidth: 70,
            scope: this,
            handler: this.onOk,
            disabled: true,
            iconCls: 'action_saveAndClose'
        });
        
        this.win = Tine.WindowFactory.getWindow({
            layout: 'fit',
            width: 300,
            height: 150,
            padding: '5px',
            modal: true,
            title: _('Select Tag'),
            items: [{
                xtype: 'form',
                buttonAlign: 'right',
                padding: '5px',
                items: this.getFormItems(),
                buttons: [{
                    text: _('Cancel'),
                    minWidth: 70,
                    scope: this,
                    handler: this.onCancel,
                    iconCls: 'action_cancel'
                }, this.okButton]
            }]
        });
    },
    
    onCancel: function() {
        this.win.close();
    },
    
    onOk: function() {
        var tag = this.tagSelect.getValue();
        
        if(! tag) {
            return;
        }
        
        this.okButton.enable();
        
        this.loadMask = new Ext.LoadMask(this.win.getEl(), {msg: _('Attaching Tag')});
        this.loadMask.show();
        
        var filter = this.selectionModel.getSelectionFilter();
        var filterModel = this.recordClass.getMeta('appName') + '_Model_' +  this.recordClass.getMeta('modelName') + 'Filter';
        
        // can't use Ext direct because the timeout is not configurable
        //Tine.Tinebase.attachTagToMultipleRecords(filter, filterModel, tag, this.onSuccess.createDelegate(this));
        Ext.Ajax.request({
            scope: this,
            timeout: 1800000, // 30 minutes
            success: this.onSuccess.createDelegate(this),
            params: {
                method: 'Tinebase.attachTagToMultipleRecords',
                filterData: filter,
                filterName: filterModel,
                tag: tag
            },
            failure: function(response, options) {
                this.loadMask.hide();
                Tine.Tinebase.ExceptionHandler.handleRequestException(response, options);
            }
        });
    },
    
    onSuccess: function() {
        this.updateHandler.call(this.updateHandlerScope || this);
        this.loadMask.hide();
        this.win.close();
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.widgets', 'Tine.widgets.tags');

/**
 * @namespace   Tine.widgets.tags
 * @class       Tine.widgets.tags.TagsMassDetachAction
 * @extends     Ext.Action
 */
Tine.widgets.tags.TagsMassDetachAction = function(config) {
    config.text = config.text ? config.text : _('Detach tag(s)');
    config.iconCls = 'action_tag_delete';
    config.handler = this.handleClick.createDelegate(this);
    Ext.apply(this, config);
    
    Tine.widgets.tags.TagsMassDetachAction.superclass.constructor.call(this, config);
};

Ext.extend(Tine.widgets.tags.TagsMassDetachAction, Ext.Action, {
    
    /**
     * called when tags got updates
     * 
     * @type Function
     */
    updateHandler: Ext.emptyFn,
    
    /**
     * scope of update handler
     * 
     * @type Object
     */
    updateHandlerScope: null,
    
    /**
     * @cfg {mixed} selectionModel
     * 
     * selection model (required)
     */
    selectionModel: null,
    
    /**
     * @cfg {function} recordClass
     * 
     * record class of records to filter for (required)
     */
    recordClass: null,
    
    formPanel: null,
    
    initFormPanel: function() {
        this.formPanel = new Tine.widgets.tags.TagToggleBox({
            selectionModel: this.selectionModel,
            recordClass: this.recordClass,
            callingAction: this,
            mode: 'detach'
        });
        
        this.formPanel.on('cancel', function() {
            this.purgeListeners();
            this.win.close();
        });
        
        this.formPanel.on('updated', function() {
            this.callingAction.onSuccess();
        });
        
    },
     
    handleClick: function() {
        this.initFormPanel();
        this.win = Tine.WindowFactory.getWindow({
            layout: 'fit',
            width: 300,
            height: 150,
            modal: true,       
            title: _('Select Tag(s) to detach'),
            items: this.formPanel

        });
        
        this.formPanel.win = this.win;
        
    },
 
    onSuccess: function() {
        this.updateHandler.call(this.updateHandlerScope || this);
        this.win.close();
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.mainscreen');

/**
 * @namespace   Tine.widgets.mainscreen
 * @class       Tine.widgets.mainscreen.WestPanel
 * @extends     Ext.ux.Portal
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * 
 * @todo make save button working again -> move to here
 * 
 * @constructor
 * @xtype       tine.widgets.mainscreen.westpanel
 */
Tine.widgets.mainscreen.WestPanel = function(config) {
    Ext.apply(this, config);
    
    this.defaults = {};
    
    Tine.widgets.mainscreen.WestPanel.superclass.constructor.apply(this, arguments);
};

Ext.extend(Tine.widgets.mainscreen.WestPanel, Ext.ux.Portal, {
    
    /**
     * @cfg {String} moduleTreeClass
     * name of module tree class in namespace of this app (defaults to ContentTypeTreePanel)
     * the class name will be expanded to Tine[this.appName][this.ContentTypeTreePanelClassName]
     */
    moduleTreePanelClassName: 'ContentTypeTreePanel',
    
    /**
     * @cfg {Array} contentTypes
     * Array of Objects
     * Object Properties: "model", "requiredRight"
     * Prop. model (e.g. "Abc") will be expanded to Tine.Application.Model.Abc
     * Prop. requiredRight (e.g. "read") will be expanded to Tine.Tinebase.common.hasRight('read', this.app.appName, recordClass.getMeta('recordsName').toLowerCase())
     * Prop. singularContainerMode (bool): when true, the records of the model doesn't have containers, so no containertreepanel is rendered
     *                                     but a containertreenode must be defined in Application.js
     * Prop. genericCtxActions (Array of Strings e.g. "['rename','grant']") Creates a Tine.widgets.tree.ContextMenu with the actions in this array                                    
     */
    contentTypes: null,

    /**
     * @cfg {String} contentType
     * defines the contentType (e.g. Xyz), which will be expanded to Tine.Application.Model.Xyz, this panel controls 
     */
    contentType: null,
    
    /**
     * @cfg {String} containerTreeClass
     * name of container tree class in namespace of this app (defaults to TreePanel)
     * the class name will be expanded to Tine[this.appName][this.containerTreePanelClassName]
     */
    containerTreePanelClassName: 'TreePanel',
    
    /**
     * @cfg {String} favoritesPanelClassName
     * name of favorites class in namespace of this app (defaults to FilterPanel)
     * the class name will be expanded to Tine[this.appName][this.favoritesPanelClassName]
     */
    favoritesPanelClassName: 'FilterPanel',
    
    /**
     * @cfg {Bool} hasContentTypeTreePanel
     * west panel has modulePanel (defaults to null -> autodetection)
     */
    hasContentTypeTreePanel: null,
    
    /**
     * @cfg {Bool} hasContainerTreePanel
     * west panel has containerTreePanel (defaults to null -> autodetection)
     */
    hasContainerTreePanel: null,
    
    /**
     * @cfg {Bool} hasFavoritesPanel
     * west panel has favorites panel (defaults to null -> autodetection)
     */
    hasFavoritesPanel: null,
    
    layout: 'column',
    cls : 'x-portal',
    defaultType : 'portalcolumn',
    border: false,

    stateful: true,
    stateEvents: ['collapse', 'expand', 'drop'],

    /**
     * inits this west panel
     */
    initComponent: function() {
        this.stateId = this.app.appName + this.getContentType() + '-mainscreen-westpanel';
        var fpcn = this.getContentType() + this.favoritesPanelClassName;
        this.hasFavoritesPanel = Ext.isBoolean(this.hasFavoritesPanel) ? this.hasFavoritesPanel : !! Tine[this.app.appName][fpcn];
        this.hasContentTypeTreePanel = Ext.isArray(this.contentTypes) && this.contentTypes.length > 1;
        
        if (this.hasContainerTreePanel === null) {
            this.hasContainerTreePanel = true;
            if(this.contentTypes) {
                Ext.each(this.contentTypes, function(ct) {
                    if (((ct.hasOwnProperty('model') && ct.model == this.contentType) || (ct.hasOwnProperty('meta') && ct.meta.modelName == this.contentType)) && (ct.singularContainerMode)) {
                        this.hasContainerTreePanel = false;
                        return false;
                    }
                }, this);
            }
        }
        
        this.items = this.getPortalColumn();
        Tine.widgets.mainscreen.WestPanel.superclass.initComponent.apply(this, arguments);
    },    
    
    /**
     * called after rendering process
     */
    afterRender: function() {
        Tine.widgets.mainscreen.WestPanel.superclass.afterRender.apply(this, arguments);
        
        this.getPortalColumn().items.each(function(item, idx) {
            // kill x-scrollers
            if (item.getEl && item.getEl()) {
                this.xScrollKiller(item);
            } else {
                item.on('afterrender', this.xScrollKiller, this);
            }
            
            //bubble state events
            item.enableBubble(['collapse', 'expand', 'selectionchange']);
        }, this);
        
        // enable vertical scrolling
        this.body.applyStyles('overflow-y: auto');
    },
    
    /**
     * initializes the stateif no state is saved
     * overwrites the Ext.Component initState method
     */
    initState: function() {
        var state = Ext.state.Manager.get(this.stateId) ? Ext.state.Manager.get(this.stateId) : this.getState();
        
        if(this.fireEvent('beforestaterestore', this, state) !== false){
            this.applyState(Ext.apply({}, state));
            this.fireEvent('staterestore', this, state);
        }
    },
    
    /**
     * applies state to cmp
     * 
     * @param {Object} state
     */
    applyState: function(state) {
        var collection = this.getPortalColumn().items,
            c = new Array(collection.getCount()), k = collection.keys, items = collection.items;
        
        Ext.each(state.order, function(position, idx) {
            c[idx] = {key: k[position], value: items[position], index: position};
        }, this);
        
        for(var i = 0, len = collection.length; i < len; i++){
            if (c[i]) {
                items[i] = c[i].value;
                k[i] = c[i].key;
            }
        }
        collection.fireEvent('sort', collection);
        
        collection.each(function(item, idx) {
            if (typeof item.addFill === 'function') return;
            if (item.getEl()) {
                item[state.collapsed[idx] ? 'collapse' : 'expand'](false);
            } else {
                item.collapsed = !!state.collapsed[idx];
            }
        }, this);
        
    },
    
    /**
     * returns additional items for the westpanel
     * template fn to be overrwiten by subclasses
     * 
     * @return {Array} of Ext.Panel
     */
    getAdditionalItems: function() {
        return this.additionalItems || [];
    },

    getContentType: function() {
        return (this.contentType) ? this.contentType : '';
    },
    
    /**
     * returns module panel
     * 
     * @return {Tine.Tinebase.widgets.ContentTypeTreePanel}
     */
    getContentTypeTreePanel: function() {
        if (this.hasContentTypeTreePanel && !this.contentTypeTreePanel) {
            this.contentTypeTreePanel = new Tine.widgets.ContentTypeTreePanel({
                app: this.app, 
                contentTypes: this.contentTypes,
                contentType: this.contentType
                });

            this.contentTypeTreePanel.on('click', function (node, event) {
                if(node != this.lastClickedNode) {
                    this.lastClickedNode = node;
                    this.fireEvent('selectionchange');
                }
            });
            
        }
        return this.contentTypeTreePanel;
    },    
    
    /**
     * returns containerTree panel
     * 
     * @return {Tine.Tinebase.widgets.ContainerTreePanel}
     */
    getContainerTreePanel: function() {
        var panelName = this.app.getMainScreen().getActiveContentType() + 'TreePanel';
        if(!this[panelName]) {
            if(Tine[this.app.appName].hasOwnProperty(panelName)) this[panelName] = new Tine[this.app.appName][panelName]({app: this.app});
            else this[panelName] = new Tine.widgets.persistentfilter.PickerPanel({app: this.app});
            this[panelName].on('click', function (node, event) {
                if(node != this.lastClickedNode) {
                    this.lastClickedNode = node;
                    this.fireEvent('selectionchange');
                }
            });
        }
        
        return this[panelName];

    },
    
    /**
     * returns favorites panel
     * 
     * @return {Ext.Panel}
     */
    getFavoritesPanel: function() {
        var ct = this.app.getMainScreen().getActiveContentType(),
            panelName = ct + 'FilterPanel';
        
        try {
            if(!this[panelName]) {
                this[panelName] = new Tine[this.app.appName][panelName]({
                    
                    rootVisible : false,
                    border : false,
                    collapsible : true,
                
                    root: null,
                    
                    titleCollapse: true,
                    title: '',
                    baseCls: 'ux-arrowcollapse',
                    
                    app: this.app,
                    contentType: ct,

                    treePanel: (this.hasContainerTreePanel) ? this.getContainerTreePanel() : this.getContentTypeTreePanel(),
                    listeners: {
                        scope: this,
                        click: function (node, event) {
                            if(node != this.lastClickedNode) {
                                this.lastClickedNode = node;
                                this.fireEvent('selectionchange');
                            }
                        }
                    }
                });
            }
        } catch(e) {
            Tine.log.info('No Favorites Panel created');
        }
        
        return this[panelName];
    },
    
    /**
     * returns filter plugin of west panel for given content type
     * 
     * @param {String} contentType
     * @return {Tine.widgets.grid.FilterPlugin}
     */
    getFilterPlugin: function(contentType) {
        if(this.hasContainerTreePanel) {
            return this.getContainerTreePanel().getFilterPlugin();
        } else {
            return new Tine.widgets.grid.FilterPlugin({
                getValue: function() {
                    return [
                    ];
                }
            });
        }
    },
    
    /**
     * returns the one and only portalcolumn of this west panel
     * 
     * @return {Ext.ux.PortalColumn}
     */
    getPortalColumn: function() {
        if (! this.portalColumn) {

            var items = [];
            
            if (this.hasContainerTreePanel) {
                var containerTreePanel = this.getContainerTreePanel();
                
                var containersName = containerTreePanel.recordClass
                    ? containerTreePanel.recordClass.getContainersName()
                    : _('containers');
                
                // recheck if container tree is a container tree as in apps not dealing
                // with containers we don't want a collapsed arrow header
                var isContainerTreePanel = typeof containerTreePanel.selectContainerPath === 'function';
                
                if (isContainerTreePanel) {
                    this.defaults = {
                        collapsible: true,
                        baseCls: 'ux-arrowcollapse',
                        animCollapse: true,
                        titleCollapse:true,
                        draggable : true,
                        autoScroll: false
                    };
                }
                
                items.push(Ext.apply(this.getContainerTreePanel(), {
                    title: isContainerTreePanel ? containersName : false,
                    collapsed: isContainerTreePanel
                }, this.defaults));
                
            }
            
            if (this.hasFavoritesPanel) {
                // favorites panel
                items.unshift(Ext.apply(this.getFavoritesPanel(), {
                    title: _('Favorites')
                }, this.defaults));
                
            if (this.hasContentTypeTreePanel) {
                
                this.defaults = {
                    collapsible: true,
                    baseCls: 'ux-arrowcollapse',
                    animCollapse: true,
                    titleCollapse:true,
                    draggable : true,
                    autoScroll: false
                };
                
                items.unshift(Ext.apply(this.getContentTypeTreePanel(), {
                    title: _('Modules'),
                    collapsed: false
                }));
                
            }    
                
            // save as favorite btn
            items.unshift(new Ext.Toolbar({
                buttonAlign : 'center',
                items: [{
                    xtype: 'button',
                    text: _('Save current view as favorite'),
                    iconCls: 'action_saveFilter',
                    handler: this.getFavoritesPanel().saveFilter.createDelegate(this.getFavoritesPanel())
                }]
            }));
            }
            
            items = items.concat(this.getAdditionalItems());

            // save origianl/programatical position
            // NOTE: this has to be done before applyState!
            Ext.each(items, function(item, idx) {
                item.startPosition = idx;
            }, this);
        
            this.portalColumn = new Ext.ux.PortalColumn({
                columnWidth: 1,
                items: items
            });
            
            this.portalColumn.on('resize', function(cmp, width) {
                this.portalColumn.items.each(function(item) {
                    item.setWidth(width);
                }, this);
            }, this)
        }
        
        return this.portalColumn;
    },
    
    /**
     * gets state of this cmp
     */
    getState: function() {
        var state = {
            order: [],
            collapsed: []
        };
        
        this.getPortalColumn().items.each(function(item, idx) {
            state.order.push(item.startPosition);
            state.collapsed.push(!!item.collapsed);
        }, this);
        
        return state;
    },
    
    /**
     * kill x scrollers of given component
     * 
     * @param {Ext.Component} cmp
     */
    xScrollKiller:  function(cmp) {
        var panelEls = cmp.getEl().child('div[class^=' + this.defaults.baseCls + '-body]');
        if (panelEls) {
            panelEls.applyStyles('overflow-x: hidden');
        }
    }
});

Ext.reg('tine.widgets.mainscreen.westpanel', Tine.widgets.mainscreen.WestPanel);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets');

/**
 * @namespace   Tine.widgets
 * @class       Tine.widgets.MainScreen
 * @extends     Ext.util.Observable
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * 
 * @constructor
 */
Tine.widgets.MainScreen = function(config) {
    Ext.apply(this, config);
    
    this.addEvents(
        /**
         * @event beforeshow
         * Fires before the component is shown. Return false to stop the show.
         * @param {Ext.Component} this
         */
        'beforeshow',
        /**
         * @event show
         * Fires after the component is shown.
         * @param {Ext.Component} this
         */
        'show'
    );
    
    Tine.widgets.MainScreen.superclass.constructor.call(this);
};

Ext.extend(Tine.widgets.MainScreen, Ext.util.Observable, {
    /**
     * @cfg {Tine.Tinebase.Application} app
     * instance of the app object (required)
     */
    app: null,
    
    /**
     * @cfg {String} activeContentType
     */
    activeContentType: null,
    /**
     * @cfg {Array} contentTypes
     */
    contentTypes: null,
    
    /**
     * @cfg {String} centerPanelClassName
     * name of centerpanel class name suffix in namespace of this app (defaults to GridPanel)
     * the class name will be expanded to Tine[this.appName][contentType + this.centerPanelClassNameSuffix]
     */
    centerPanelClassNameSuffix: 'GridPanel',

    /**
     * returns active content type
     * 
     * @return {String}
     */
    getActiveContentType: function() {
        return (this.activeContentType) ? this.activeContentType : '';
    },
     
    /**
     * get center panel for given contentType
     * 
     * template method to be overridden by subclasses to modify default behaviour
     * 
     * @param {String} contentType
     * @return {Ext.Panel}
     */
    getCenterPanel: function(contentType) {
        contentType = contentType || this.getActiveContentType();
        
        if (! this[contentType + this.centerPanelClassNameSuffix]) {
            try {
                this[contentType + this.centerPanelClassNameSuffix] = new Tine[this.app.appName][contentType + this.centerPanelClassNameSuffix]({
                    app: this.app,
                    plugins: [this.getWestPanel().getFilterPlugin(contentType)]
                });
            } catch (e) {
                Tine.log.err('Could not create centerPanel "Tine.' + this.app.appName + '.' + contentType + this.centerPanelClassNameSuffix + '"');
                Tine.log.err(e.stack ? e.stack : e);
                this[contentType + this.centerPanelClassNameSuffix] = new Ext.Panel({html: 'ERROR'});
            }
        }
        
        return this[contentType + this.centerPanelClassNameSuffix];
    },
    
    /**
     * convinience fn to get container tree panel from westpanel
     * 
     * @return {Tine.widgets.container.containerTreePanel}
     *
    getContainerTreePanel: function() {
        return this.getWestPanel().getContainerTreePanel();
    },
    */
    
    /**
     * get north panel for given contentType
     * 
     * template method to be overridden by subclasses to modify default behaviour
     * 
     * @param {String} contentType
     * @return {Ext.Panel}
     */
    getNorthPanel: function(contentType) {
        contentType = contentType || this.getActiveContentType();
        
        if (! this[contentType + 'ActionToolbar']) {
            try {
                this[contentType + 'ActionToolbar'] = this[contentType + this.centerPanelClassNameSuffix].getActionToolbar();
            } catch (e) {
                Tine.log.err('Could not create northPanel');
                Tine.log.err(e.stack ? e.stack : e);
                this[contentType + 'ActionToolbar'] = new Ext.Panel({html: 'ERROR'});
            }
        }

        return this[contentType + 'ActionToolbar'];
    },
    
    /**
     * get west panel for given contentType
     * 
     * template method to be overridden by subclasses to modify default behaviour
     * 
     * @return {Ext.Panel}
     */
    getWestPanel: function() {

        var contentType = this.getActiveContentType(),
            wpName = contentType + 'WestPanel';
            
        if (! this[wpName]) {
            var wpconfig = {
                    app: this.app, 
                    contentTypes: this.contentTypes,
                    contentType: contentType,
                    listeners: {
                        scope: this,
                        selectionchange: function() {
                            var cp = this.getCenterPanel();
                            if(cp) {
                                try {
                                    var grid = cp.getGrid();
                                    if(grid) {
                                        var sm = grid.getSelectionModel();
                                        if(sm) {
                                            sm.clearSelections();
                                            cp.actionUpdater.updateActions(sm.getSelectionsCollection());
                                        }
                                    }
                                } catch (e) {
                                    // do nothing - no grid
                                }
                            }
                        }
                    }
                };
            try {
                if(Tine[this.app.name].hasOwnProperty(wpName)) this[wpName] = new Tine[this.app.appName][wpName](wpconfig);
                else this[wpName] = new Tine.widgets.mainscreen.WestPanel(wpconfig);
            } catch (e) {
                Tine.log.err('Could not create westPanel');
                Tine.log.err(e.stack ? e.stack : e);
                this[wpName] = new Ext.Panel({html: 'ERROR'});
            }
        }
        return this[wpName];
    },
    
    /**
     * shows/activates this app mainscreen
     * 
     * @return {Tine.widgets.MainScreen} this
     */
    show: function() {
        if(this.fireEvent("beforeshow", this) !== false){
            this.showWestPanel();
            this.showCenterPanel();
            this.showNorthPanel();
            
            this.fireEvent('show', this);
        }
        return this;
    },
    
    /**
     * shows center panel in mainscreen
     */
    showCenterPanel: function() {
        Tine.Tinebase.MainScreen.setActiveContentPanel(this.getCenterPanel(this.getActiveContentType()), true);
    },
    
    /**
     * shows west panel in mainscreen
     */
    showWestPanel: function() {
        Tine.Tinebase.MainScreen.setActiveTreePanel(this.getWestPanel(), true);
    },
    
    /**
     * shows north panel in mainscreen
     */
    showNorthPanel: function() {
        Tine.Tinebase.MainScreen.setActiveToolbar(this.getNorthPanel(this.getActiveContentType()), true);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.Tinebase');
/**
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.LicenseScreen
 * @extends     Ext.Window
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * 
 * @param {Object} config The configuration options.
 */

Tine.Tinebase.LicenseScreen = Ext.extend(Ext.Window, {
    
    closeAction: 'close',
    modal: true,
    width: 400,
    height: 360,
    minWidth: 400,
    minHeight: 360,
    layout: 'fit',
    title: null,
    
    /**
     * init component
     */
    initComponent: function() {

        this.title = _('License');
        
        this.items = {
            layout: 'fit',
            border: false,
            padding: 10,
            autoScroll:true,
            autoLoad: {
                url: 'LICENSE',
                isUpload: true,
                method: 'GET',
                callback: function(el, s, response) {
                    el.update(Ext.util.Format.nl2br(response.responseText));
                }
            },
            buttons: [{
                text: _('Ok'),
                iconCls: 'action_saveAndClose',
                handler: this.close,
                scope: this
            }]
        };
        
        Tine.Tinebase.LicenseScreen.superclass.initComponent.call(this);
    }
});/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.Tinebase');

/**
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.CreditsScreen
 * @extends     Ext.Window
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * 
 * @param {Object} config The configuration options.
 */

Tine.Tinebase.CreditsScreen = Ext.extend(Ext.Window, {
    
    closeAction: 'close',
    modal: true,
    width: 400,
    height: 360,
    minWidth: 400,
    minHeight: 360,
    layout: 'fit',
    title: null,
    
    /**
     * init component
     */
    initComponent: function() {

        this.title = _('Credits');
        
        this.items = {
            layout: 'fit',
            border: false,
            
            padding: 10,
            autoScroll:true,
            autoLoad: {
                url: 'CREDITS',
                isUpload: true,
                method: 'GET',
                callback: function(el, s, response) {
                    el.update(Ext.util.Format.nl2br(response.responseText));
                }
            },
            buttons: [{
                text: _('Ok'),
                iconCls: 'action_saveAndClose',
                handler: this.close,
                scope: this
            }]
        };
        
        Tine.Tinebase.CreditsScreen.superclass.initComponent.call(this);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.widgets');

/**
 * Widget for country selection
 *
 * @namespace   Tine.widgets
 * @class       Tine.widgets.CountryCombo
 * @extends     Ext.form.ComboBox
 */
Tine.widgets.CountryCombo = Ext.extend(Ext.form.ComboBox, {
    fieldLabel: 'Country',
    displayField:'translatedName',
    valueField:'shortName',
    typeAhead: true,
    forceSelection: true,
    mode: 'local',
    triggerAction: 'all',
    selectOnFocus:true,
    
    /**
     * @private
     */
    initComponent: function() {
        this.store = this.getCountryStore();
        this.emptyText = _('Select a country...');
        
        Tine.widgets.CountryCombo.superclass.initComponent.call(this);
    },
    /**
     * @private store has static content
     */
    getCountryStore: function(){
        var store = Ext.StoreMgr.get('Countries');
        if(!store) {
            store = new Ext.data.JsonStore({
                baseParams: {
                    method:'Tinebase.getCountryList'
                },
                root: 'results',
                id: 'shortName',
                fields: ['shortName', 'translatedName'],
                remoteSort: false,
                sortInfo: {
                    field: 'translatedName',
                    direction: 'ASC'
                }
            });
            Ext.StoreMgr.add('Countries', store);
        }
        
        var countryList = Locale.getTranslationList('CountryList');
        if (countryList) {
            var storeData = {results: []};
            for (var shortName in countryList) {
                storeData.results.push({shortName: shortName, translatedName: countryList[shortName]});
            }
            store.loadData(storeData);
        }
        return store;
    }
});

Ext.reg('widget-countrycombo', Tine.widgets.CountryCombo);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2007-2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * TODO         add to extdoc
 */

/*global Ext, Tine, Locale*/

Ext.ns('Tine.widgets', 'Tine.widgets.activities');

/************************* panel *********************************/

/**
 * Class for a single activities panel
 * 
 * @namespace   Tine.widgets.activities
 * @class       Tine.widgets.activities.ActivitiesPanel
 * @extends     Ext.Panel
 */
Tine.widgets.activities.ActivitiesPanel = Ext.extend(Ext.Panel, {
    /**
     * @cfg {String} app Application which uses this panel
     */
    app: '',
    
    /**
     * @cfg {Boolean}
     */
    showAddNoteForm: true,
    
    /**
     * @cfg {Array} notes Initial notes
     */
    notes: [],
    
    /**
     * the translation object
     */
    translation: null,

    /**
     * @var {Ext.data.JsonStore}
     * Holds activities of the record this panel is displayed for
     */
    recordNotesStore: null,
    
    title: null,
    iconCls: 'notes_noteIcon',
    layout: 'hfit',
    bodyStyle: 'padding: 2px 2px 2px 2px',
    autoScroll: true,
    
    /**
     * event handler
     */
    handlers: {
        
        /**
         * add a new note
         * 
         */
        addNote: function (button, event) {
            //var note_type_id = Ext.getCmp('note_type_combo').getValue();
            var note_type_id = button.typeId,
                noteTextarea = Ext.getCmp('note_textarea'),
                note = noteTextarea.getValue(),
                notesStore,
                newNote;
            
            if (note_type_id && note) {
                notesStore = Ext.StoreMgr.lookup('NotesStore');
                newNote = new Tine.Tinebase.Model.Note({note_type_id: note_type_id, note: note});
                notesStore.insert(0, newNote);
                
                // clear textarea
                noteTextarea.setValue('');
                noteTextarea.emptyText = noteTextarea.emptyText;
            }
        }        
    },
    
    /**
     * init activities data view
     */
    initActivitiesDataView: function () {
        var ActivitiesTpl = new Ext.XTemplate(
            '<tpl for=".">',
               '<div class="x-widget-activities-activitiesitem" id="{id}">',
                    '<div class="x-widget-activities-activitiesitem-text"',
                    '   ext:qtip="{[this.encode(values.note)]} - {[this.render(values.creation_time, "timefull")]} - {[this.render(values.created_by, "user")]}" >', 
                        '{[this.render(values.note_type_id, "icon")]}&nbsp;{[this.render(values.creation_time, "timefull")]}<br/>',
                        '{[this.encode(values.note, true)]}<hr color="#aaaaaa">',
                    '</div>',
                '</div>',
            '</tpl>' , {
                encode: function (value, ellipsis) {
                    var result = Ext.util.Format.nl2br(Tine.Tinebase.common.doubleEncode(value));
                    return (ellipsis) ? Ext.util.Format.ellipsis(result, 300) : result;
                },
                render: function (value, type) {
                    switch (type) 
                    {
                    case 'icon':
                        return Tine.widgets.activities.getTypeIcon(value);
                    case 'user':
                        if (!value) {
                            value = Tine.Tinebase.registry.map.currentAccount.accountDisplayName;
                        }
                        var username = value;
                        return '<i>' + username + '</i>';
                    case 'time':
                        if (!value) {
                            return '';
                        }
                        return value.format(Locale.getTranslationData('Date', 'medium'));
                    case 'timefull':
                        if (!value) {
                            return '';
                        }
                        return value.format(Locale.getTranslationData('Date', 'medium')) + ' ' +
                            value.format(Locale.getTranslationData('Time', 'medium'));
                    }
                }
            }
        );
        
        this.activities = new Ext.DataView({
            tpl: ActivitiesTpl,       
            id: 'grid_activities_limited',
            store: this.recordNotesStore,
            overClass: 'x-view-over',
            itemSelector: 'activities-item-small'
        });
    },
    
    /**
     * init note form
     * 
     */
    initNoteForm: function () {
        var noteTextarea =  new Ext.form.TextArea({
            id: 'note_textarea',
            emptyText: this.translation.gettext('Add a Note...'),
            grow: false,
            preventScrollbars: false,
            anchor: '100%',
            height: 55,
            hideLabel: true
        });
        
        var subMenu = [];
        var typesStore = Tine.widgets.activities.getTypesStore();
        var defaultTypeRecord = typesStore.getAt(typesStore.find('is_user_type', '1'));
        
        typesStore.each(function (record) {
            if (record.data.is_user_type === 1) {
                var action = new Ext.Action({
                    text: this.translation.gettext('Add') + ' ' + this.translation.gettext(record.data.name) + ' ' + this.translation.gettext('Note'),
                    tooltip: this.translation.gettext(record.data.description),
                    handler: this.handlers.addNote,
                    iconCls: 'notes_' + record.data.name + 'Icon',
                    typeId: record.data.id,
                    scope: this
                });
                subMenu.push(action);
            }
        }, this);
        
        var addButton = new Ext.SplitButton({
            text: this.translation.gettext('Add'),
            tooltip: this.translation.gettext('Add new note'),
            iconCls: 'action_saveAndClose',
            menu: {
                items: subMenu
            },
            handler: this.handlers.addNote,
            typeId: defaultTypeRecord.data.id
        });

        this.formFields = {
            layout: 'form',
            items: [
                noteTextarea
            ],
            bbar: [
                '->',
                addButton
            ]
        };
    },

    /**
     * @private
     */
    initComponent: function () {
        
        // get translations
        this.translation = new Locale.Gettext();
        this.translation.textdomain('Tinebase');
        
        // translate / update title
        this.title = this.translation.gettext('Notes');
        
        // init recordNotesStore
        this.notes = [];
        this.recordNotesStore = new Ext.data.JsonStore({
            id: 'id',
            fields: Tine.Tinebase.Model.Note,
            data: this.notes,
            sortInfo: {
                field: 'creation_time',
                direction: 'DESC'
            }
        });
        
        Ext.StoreMgr.add('NotesStore', this.recordNotesStore);
        
        // set data view with activities
        this.initActivitiesDataView();
        
        if (this.showAddNoteForm) {
            // set add new note form
            this.initNoteForm();
                
            this.items = [
                this.formFields,
                // this form field is only for fetching and saving notes in the record
                new Tine.widgets.activities.NotesFormField({
                    recordNotesStore: this.recordNotesStore
                }),                 
                this.activities
            ];
        } else {
            this.items = [
                new Tine.widgets.activities.NotesFormField({
                    recordNotesStore: this.recordNotesStore
                }),                 
                this.activities
            ];
        }
        
        Tine.widgets.activities.ActivitiesPanel.superclass.initComponent.call(this);
    }
});
Ext.reg('tineactivitiespanel', Tine.widgets.activities.ActivitiesPanel);

/************************* add note button ***************************/

/**
 * button for adding notes
 * 
 * @namespace   Tine.widgets.activities
 * @class       Tine.widgets.activities.ActivitiesAddButton
 * @extends     Ext.SplitButton
 */
Tine.widgets.activities.ActivitiesAddButton = Ext.extend(Ext.SplitButton, {

    iconCls: 'notes_noteIcon',

    /**
     * event handler
     */
    handlers: {
        
        /**
         * add a new note (show prompt)
         */
        addNote: function (button, event) {

            this.formPanel = new Ext.FormPanel({
                layout: 'form',
                labelAlign: 'top',
                border: true,
                frame: true,
                items: [{
                    xtype: 'textarea',
                    name: 'notification',
                    fieldLabel: this.translation.gettext('Enter new note:'),
                    labelSeparator: '',
                    anchor: '100% 100%'
                }]
            });
            
            this.onClose = function () {
                this.window.close();
            };

            this.onCancel = function () {
                this.onClose();
            };

            this.onOk = function () {
                var text = this.formPanel.getForm().findField('notification').getValue();
                this.handlers.onNoteAdd(text, button.typeId);
                this.onClose();
            };
            
            this.cancelAction = new Ext.Action({
                text: _('Cancel'),
                iconCls: 'action_cancel',
                minWidth: 70,
                handler: this.onCancel,
                scope: this
            });
            
            this.okAction = new Ext.Action({
                text: _('Ok'),
                iconCls: 'action_saveAndClose',
                minWidth: 70,
                handler: this.onOk,
                scope: this
            });
           
            this.window = Tine.WindowFactory.getWindow({
                title: this.translation.gettext('Add Note'),
                width: 500,
                height: 260,
                modal: true,
                
                layout: 'fit',
                buttonAlign: 'right',
                plain: true,
                bodyStyle: 'padding:5px;',
                                
                buttons: [
                    this.cancelAction,
                    this.okAction                                    
                ],
                
                items: [
                    this.formPanel
                ]
            });
        },       
        
        /**
         * on add note
         * - add note to activities panel
         */
        onNoteAdd: function (text, typeId) {
            if (text && typeId) {
                var notesStore = Ext.StoreMgr.lookup('NotesStore');
                var newNote = new Tine.Tinebase.Model.Note({note_type_id: typeId, note: text});
                notesStore.insert(0, newNote);
            }
        }
    },
    
    /**
     * @private
     */
    initComponent: function () {

        // get translations
        this.translation = new Locale.Gettext();
        this.translation.textdomain('Tinebase');

        // get types for split button
        var subMenu = [],
            typesStore = Tine.widgets.activities.getTypesStore(),
            defaultTypeRecord = typesStore.getAt(typesStore.find('is_user_type', '1'));
        
        typesStore.each(function (record) {
            if (record.data.is_user_type === '1') {
                var action = new Ext.Action({
                    requiredGrant: 'editGrant',
                    text: String.format(this.translation.gettext('Add a {0} Note'), record.data.name),
                    tooltip: this.translation.gettext(record.data.description),
                    handler: this.handlers.addNote,
                    //iconCls: 'notes_' + record.data.name + 'Icon',
                    iconCls: record.data.icon_class,
                    typeId: record.data.id,
                    scope: this
                });
                subMenu.push(action);
            }
        }, this);
        
        this.requiredGrant = 'editGrant';
        this.text = this.translation.gettext('Add Note');
        this.tooltip = this.translation.gettext('Add new note');
        this.menu = {
            items: subMenu
        };
        this.handler = this.handlers.addNote;
        this.typeId = defaultTypeRecord.data.id;
        
        Tine.widgets.activities.ActivitiesAddButton.superclass.initComponent.call(this);
    }
});
Ext.reg('widget-activitiesaddbutton', Tine.widgets.activities.ActivitiesAddButton);

/************************* tab panel *********************************/

/**
 * Class for a activities tab with notes/activities grid
 * 
 * TODO add more filters to filter toolbar
 * 
 * 
 * @namespace   Tine.widgets.activities
 * @class       Tine.widgets.activities.ActivitiesTabPanel
 * @extends     Ext.Panel
 */
Tine.widgets.activities.ActivitiesTabPanel = Ext.extend(Ext.Panel, {

    /**
     * @cfg {String} app Application which uses this panel
     */
    app: '',
    
    /**
     * @var {Ext.data.JsonStore}
     * Holds activities of the record this panel is displayed for
     */
    store: null,
    
    /**
     * the translation object
     */
    translation: null,

    /**
     * @cfg {Object} paging defaults
     */
    paging: {
        start: 0,
        limit: 20,
        sort: 'creation_time',
        dir: 'DESC'
    },

    /**
     * the record id
     */
    record_id: null,
    
    /**
     * the record model
     */
    record_model: null,
    
    /**
     * other config options
     */
    title: null,
    layout: 'fit',
    
    getActivitiesGrid: function () {
        // @todo add row expander on select ?
        // @todo add context menu ?
        // @todo add buttons ?        
        // @todo add more renderers ?
        
        // the columnmodel
        var columnModel = new Ext.grid.ColumnModel([
            { resizable: true, id: 'note_type_id', header: this.translation.gettext('Type'), dataIndex: 'note_type_id', width: 15, 
                renderer: Tine.widgets.activities.getTypeIcon },
            { resizable: true, id: 'note', header: this.translation.gettext('Note'), dataIndex: 'note'},
            { resizable: true, id: 'created_by', header: this.translation.gettext('Created By'), dataIndex: 'created_by', width: 70},
            { resizable: true, id: 'creation_time', header: this.translation.gettext('Timestamp'), dataIndex: 'creation_time', width: 50, 
                renderer: Tine.Tinebase.common.dateTimeRenderer }
        ]);

        columnModel.defaultSortable = true; // by default columns are sortable
        
        // the rowselection model
        var rowSelectionModel = new Ext.grid.RowSelectionModel({multiSelect: true});

        // the paging toolbar
        var pagingToolbar = new Ext.PagingToolbar({
            pageSize: 20,
            store: this.store,
            displayInfo: true,
            displayMsg: this.translation.gettext('Displaying history records {0} - {1} of {2}'),
            emptyMsg: this.translation.gettext("No history to display")
        });

        // the gridpanel
        var gridPanel = new Ext.grid.GridPanel({
            id: 'Activities_Grid',
            cls: 'tw-activities-grid',
            store: this.store,
            cm: columnModel,
            tbar: pagingToolbar,     
            selModel: rowSelectionModel,
            border: false,                  
            //autoExpandColumn: 'note',
            //enableColLock:false,
            //autoHeight: true,
            viewConfig: {
                autoFill: true,
                forceFit: true,
                ignoreAdd: true,
                autoScroll: true
            }  
        });
        
        return gridPanel;
    },
    
    /**
     * init the contacts json grid store
     */
    initStore: function () {

        this.store = new Ext.data.JsonStore({
            id: 'id',
            autoLoad: false,
            root: 'results',
            totalProperty: 'totalcount',
            fields: Tine.Tinebase.Model.Note,
            remoteSort: true,
            baseParams: {
                method: 'Tinebase.searchNotes'
            },
            sortInfo: {
                field: this.paging.sort,
                direction: this.paging.dir
            }
        });
        
        // register store
        Ext.StoreMgr.add('NotesGridStore', this.store);
        
        // prepare filter
        this.store.on('beforeload', function (store, options) {
            if (!options.params) {
                options.params = {};
            }
            
            // paging toolbar only works with this properties in the options!
            options.params.sort  = store.getSortState() ? store.getSortState().field : this.paging.sort;
            options.params.dir   = store.getSortState() ? store.getSortState().direction : this.paging.dir;
            options.params.start = options.params.start ? options.params.start : this.paging.start;
            options.params.limit = options.params.limit ? options.params.limit : this.paging.limit;
            
            options.params.paging = Ext.copyTo({}, options.params, 'sort,dir,start,limit');
            
            var filterToolbar = Ext.getCmp('activitiesFilterToolbar');
            var filter = filterToolbar ? filterToolbar.getValue() : [];
            filter.push(
                {field: 'record_model', operator: 'equals', value: this.record_model },
                {field: 'record_id', operator: 'equals', value: (this.record_id) ? this.record_id : 0 },
                {field: 'record_backend', operator: 'equals', value: 'Sql' }
            );
                        
            options.params.filter = filter;
        }, this);
        
        // add new notes from notes store
        this.store.on('load', function (store, operation) {
            var notesStore = Ext.StoreMgr.lookup('NotesStore');
            if (notesStore) {
                notesStore.each(function (note) {
                    if (!note.data.creation_time) {
                        store.insert(0, note);
                    }
                });
            }
        }, this);
                        
        //this.store.load({});
    },

    /**
     * @private
     */
    initComponent: function () {
        
        // get translations
        this.translation = new Locale.Gettext();
        this.translation.textdomain('Tinebase');
        
        // translate / update title
        this.title = this.translation.gettext('History');
        
        // get store
        this.initStore();

        // get grid
        this.activitiesGrid = this.getActivitiesGrid();
        
        // the filter toolbar
        var filterToolbar = new Tine.widgets.grid.FilterToolbar({
            id : 'activitiesFilterToolbar',
            filterModels: [
                {label: _('Quick search'), field: 'query',         operators: ['contains']},
                //{label: this.translation._('Time'), field: 'creation_time', operators: ['contains']}
                {label: this.translation.gettext('Time'), field: 'creation_time', valueType: 'date', pastOnly: true}
                // user search is note working yet -> see NoteFilter.php
                //{label: this.translation._('User'), field: 'created_by', defaultOperator: 'contains'},
                // type search isn't implemented yet
                //{label: this.translation._('Type'), field: 'note_type_id', defaultOperator: 'contains'}
            ],
            defaultFilter: 'query',
            filters: []
        });
        
        filterToolbar.on('change', function () {
            this.store.load({});
        }, this);
                                                
        this.items = [        
            new Ext.Panel({
                layout: 'border',
                items: [{
                    region: 'center',
                    xtype: 'panel',
                    layout: 'fit',
                    border: false,
                    items: this.activitiesGrid
                }, {
                    region: 'north',
                    border: false,
                    items: filterToolbar,
                    listeners: {
                        scope: this,
                        afterlayout: function (ct) {
                            ct.suspendEvents();
                            ct.setHeight(filterToolbar.getHeight());
                            ct.ownerCt.layout.layout();
                            ct.resumeEvents();
                        }
                    }
                }]
            })
        ];
                
        // load store on activate
        this.on('activate', function (panel) {
            panel.store.load({});
        });
        
        // no support for multiple edit
        Tine.widgets.dialog.MultipleEditDialogPlugin.prototype.registerSkipItem(this);
        
        Tine.widgets.activities.ActivitiesTabPanel.superclass.initComponent.call(this);
    }
});
Ext.reg('tineactivitiestabpanel', Tine.widgets.activities.ActivitiesTabPanel);

/************************* helper *********************************/

/**
 * @private Helper class to have activities processing in the standard form/record cycle
 */
Tine.widgets.activities.NotesFormField = Ext.extend(Ext.form.Field, {
    /**
     * @cfg {Ext.data.JsonStore} recordNotesStore a store where the record notes are in.
     */
    recordNotesStore: null,
    
    name: 'notes',
    hidden: true,
    hideLabel: true,
    
    /**
     * @private
     */
    initComponent: function () {
        Tine.widgets.activities.NotesFormField.superclass.initComponent.call(this);
        this.hide();
    },
    /**
     * returns notes data of the current record
     */
    getValue: function () {
        var value = [];
        this.recordNotesStore.each(function (note) {
            value.push(note.data);
        });
        return value;
    },
    /**
     * sets notes from an array of note data objects (not records)
     */
    setValue: function (value) {
        this.recordNotesStore.loadData(value);
    }

});

/**
 * get note / activities types store
 * if available, load data from initial data
 * 
 * @return Ext.data.JsonStore with activities types
 * 
 * @todo translate type names / descriptions
 */
Tine.widgets.activities.getTypesStore = function () {
    var store = Ext.StoreMgr.get('noteTypesStore');
    if (!store) {
        store = new Ext.data.JsonStore({
            fields: Tine.Tinebase.Model.NoteType,
            baseParams: {
                method: 'Tinebase.getNoteTypes'
            },
            root: 'results',
            totalProperty: 'totalcount',
            id: 'id',
            remoteSort: false
        });
        /*if (Tine.Tinebase.registry.get('NoteTypes')) {
            store.loadData(Tine.Tinebase.registry.get('NoteTypes'));
        } else*/ 
        if (Tine.Tinebase.registry.get('NoteTypes')) {
            store.loadData(Tine.Tinebase.registry.get('NoteTypes'));
        }
        Ext.StoreMgr.add('noteTypesStore', store);
    }
    
    return store;
};

/**
 * get type icon
 * 
 * @param   id of the note type record
 * @returns img tag with icon source
 * 
 * @todo use icon_class here
 */
Tine.widgets.activities.getTypeIcon = function (id) {
    var typesStore = Tine.widgets.activities.getTypesStore();
    var typeRecord = typesStore.getById(id);
    if (typeRecord) {
        return '<img src="' + typeRecord.data.icon + '" ext:qtip="' + Tine.Tinebase.common.doubleEncode(typeRecord.data.description) + '"/>';
    } else {
        return '';
    }
};
/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
 */

/*global Ext, Tine*/

Ext.ns('Tine.Tinebase.widgets.form');

/**
 * @namespace   Tine.Tinebase.widgets.form
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @class       Tine.Tinebase.widgets.form.RecordPickerComboBox
 * @extends     Ext.form.ComboBox
 * 
 * <p>Abstract base class for recordPickers like account/group pickers </p>
 * 
 * Usage:
 * <pre><code>
var resourcePicker = new Tine.Tinebase.widgets.form.RecordPickerComboBox({
    recordClass: Tine.Calendar.Model.Resource
});
   </code></pre>
 */
Tine.Tinebase.widgets.form.RecordPickerComboBox = Ext.extend(Ext.ux.form.ClearableComboBox, {
    /**
     * @cfg {bool} blurOnSelect
     * blur this combo when record got selected, useful to be used in editor grids (defaults to false)
     */
    blurOnSelect: false,
    
    /**
     * @cfg {Tine.Tinebase.data.Record} recordClass
     * model of record to be picked (required) 
     */
    recordClass: null,
    
    /**
     * @cfg {Tine.Tinebase.data.RecordProxy} recordProxy
     * record backend 
     */
    recordProxy: null,
    
    /**
     * @property app
     * @type Tine.Tinebase.Application
     */
    app: null,
    
    /**
     * @type Tine.Tinebase.data.Record selectedRecord
     * @property selectedRecord 
     * The last record which was selected
     */
    selectedRecord: null,
    
    /**
     * sort by field
     * 
     * @type String 
     */
    sortBy: null,
    
    /**
     * @type string
     * @property lastStoreTransactionId
     */
    lastStoreTransactionId: null,
    
    /**
     * if set to false, it is not possible to add the same record handled in this.editDialog
     * this.editDialog must also be set
     * 
     * @cfg {Boolean} allowLinkingItself
     */
    allowLinkingItself: null,
    
    /**
     * the editDialog, the form is nested in. Just needed if this.allowLinkingItself is set to false
     * 
     * @cfg Tine.widgets.dialog.EditDialog editDialog
     */
    editDialog: null,
    
    triggerAction: 'all',
    pageSize: 10,
    minChars: 3,
    forceSelection: true,
    
    initComponent: function () {
        this.app = Tine.Tinebase.appMgr.get(this.recordClass.getMeta('appName'));
        this.displayField = this.recordClass.getMeta('titleProperty');
        this.valueField = this.recordClass.getMeta('idProperty');
        this.disableClearer = ! this.allowBlank;
        
        this.loadingText = _('Searching...');
        
        this.store = new Tine.Tinebase.data.RecordStore(Ext.copyTo({
            readOnly: true,
            proxy: this.recordProxy || undefined
        }, this, 'totalProperty,root,recordClass'));
        
        this.on('beforequery', this.onBeforeQuery, this);
        this.store.on('beforeloadrecords', this.onStoreBeforeLoadRecords, this);
        
        this.initTemplate();
        
        Tine.Tinebase.widgets.form.RecordPickerComboBox.superclass.initComponent.call(this);
    },
    
    /**
     * respect record.getTitle method
     */
    initTemplate: function() {
        if (! this.tpl) {
            this.tpl = new Ext.XTemplate('<tpl for="."><div class="x-combo-list-item">{[this.getTitle(values.' + this.recordClass.getMeta('idProperty') + ')]}</div></tpl>', {
                getTitle: (function(id) {
                    var record = this.getStore().getById(id),
                        title = record ? record.getTitle() : '&nbsp';
                    
                    return Ext.util.Format.htmlEncode(title);
                }).createDelegate(this)
            });
        }
    },
    
    
    /**
     * prepare paging and sort
     * 
     * @param {Ext.data.Store} store
     * @param {Object} options
     */
    onBeforeLoad: function (store, options) {
        Tine.Tinebase.widgets.form.RecordPickerComboBox.superclass.onBeforeLoad.call(this, store, options);
        
        this.lastStoreTransactionId = options.transactionId = Ext.id();
        
        var paging = {
            // TODO do we need to set start & limit here?
            start: options.params.start,
            limit: options.params.limit,
            sort: (this.sortBy) ? this.sortBy : this.valueField,
            dir: 'ASC'
        };
        
        Ext.applyIf(options.params, paging);
        
        // TODO is this needed?
        options.params.paging = paging;
    },
    
    /**
     * onStoreBeforeLoadRecords
     * 
     * @param {Object} o
     * @param {Object} options
     * @param {Boolean} success
     * @param {Ext.data.Store} store
     */
    onStoreBeforeLoadRecords: function(o, options, success, store) {
        if (! this.lastStoreTransactionId || options.transactionId && this.lastStoreTransactionId !== options.transactionId) {
            Tine.log.debug('Tine.Tinebase.widgets.form.RecordPickerComboBox::onStoreBeforeLoadRecords cancelling old transaction request.');
            return false;
        }
    },
    
    /**
     * use beforequery to set query filter
     * 
     * @param {Object} qevent
     */
    onBeforeQuery: function (qevent) {
        this.store.baseParams.filter = [
            {field: 'query', operator: 'contains', value: qevent.query }
        ];
    },
    
    /**
     * relay contextmenu events
     * 
     * @param {Ext.Container} ct
     * @param {Number} position
     * @private
     */
    onRender : function(ct, position){
        Tine.Tinebase.widgets.form.RecordPickerComboBox.superclass.onRender.call(this, ct, position);
        
        var c = this.getEl();
 
        this.mon(c, {
            scope: this,
            contextmenu: Ext.emptyFn
        });
 
        this.relayEvents(c, ['contextmenu']);
    },
    
    /**
     * store a copy of the selected record
     * 
     * @param {Tine.Tinebase.data.Record} record
     * @param {Number} index
     */
    onSelect: function (record, index) {
        this.selectedRecord = record;
        return Tine.Tinebase.widgets.form.RecordPickerComboBox.superclass.onSelect.call(this, record, index);
    },
    
    /**
     * on keypressed("enter") event to add record
     * 
     * @param {Tine.Addressbook.SearchCombo} combo
     * @param {Event} event
     */ 
    onSpecialkey: function (combo, event) {
        if (event.getKey() === event.ENTER) {
            var id = combo.getValue();
            var record = this.store.getById(id);
            this.onSelect(record);
        }
    },
    
    /**
     * set value and prefill store if needed
     * 
     * @param {mixed} value
     */
    setValue: function (value) {
        if (value) {
            
            // value is a record
            if (typeof(value.get) === 'function') {
                if (this.store.indexOf(value) < 0) {
                    this.store.addSorted(value);
                }
                value = value.get(this.valueField);
            }
            
            // value is a js object
            else if (Ext.isObject(value)) {
                if (! this.store.getById(value)) {
                    var record = this.recordProxy ? this.recordProxy.recordReader({responseText: Ext.encode(value)}) : new this.recordClass(value)
                    this.store.addSorted(record);
                }
                value = value[this.valueField] || '';
            }
            
            // value is the current id
            else if (Ext.isPrimitive(value) && value == this.getValue()) {
                return this.setValue(this.selectedRecord);
            }
        }
        
        var r = this.findRecord(this.valueField, value),
            text = value;
        
        if (r){
            text = r.getTitle();
            this.selectedRecord = r;
            if (this.allowLinkingItself === false) {
                if (r.getId() == this.editDialog.record.getId()) {
                    Ext.MessageBox.show({
                        title: _('Failure'),
                        msg: _('You tried to link a record with itself. This is not allowed!'),
                        buttons: Ext.MessageBox.OK,
                        icon: Ext.MessageBox.ERROR  
                    });
                    return;
                }
            }
            
        } else if (Ext.isDefined(this.valueNotFoundText)){
            text = this.valueNotFoundText;
        }
        this.lastSelectionText = text;
        if (this.hiddenField){
            this.hiddenField.value = Ext.value(value, '');
        }
        Tine.Tinebase.widgets.form.RecordPickerComboBox.superclass.setValue.call(this, text);
        this.value = value;
        return this;
    }
});
Ext.reg('tinerecordpickercombobox', Tine.Tinebase.widgets.form.RecordPickerComboBox);
/*
 * Tine 2.0
 *
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <alex@stintzing.net>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

Ext.ns('Tine.widgets.relation');

/**
 * @namespace   Tine.widgets.relation
 * @class       Tine.widgets.relation.PickerCombo
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 */

Tine.widgets.relation.PickerCombo = Ext.extend(Ext.Container, {

    // private
    items: null,
    combo: null,
    layout: 'fit',
    /**
     * @cfg {Tine.widgets.dialog.EditDialog}
     */
    editDialog: null,
    /**
     * @cfg {Tine.Tinebase.data.Record} recordClass
     */
    recordClass: null,
    /**
     * @cfg {Ext.data.SimpleStore} store 
     */
    store: null,

    /**
     * initializes the component
     */
    initComponent: function() {
        this.combo = Tine.widgets.form.RecordPickerManager.get(this.app, this.recordClass, Ext.applyIf({},this));
        this.items = [this.combo];

        this.deferredLoading();
    },

    /**
     * wait until the relationPanel is created
     */
    deferredLoading: function() {

        if(!this.editDialog.relationsPanel) {
            this.deferredLoading.defer(100, this);
            return;
        }

        this.store = this.editDialog.relationsPanel.getStore();
        this.fullModelName = this.recordClass.getMeta('appName') + '_Model_' + this.recordClass.getMeta('modelName');

        this.combo.on('select', function() {
            var value = this.combo.getValue();

            if(value.length) {
                var recordToAdd = this.combo.selectedRecord;

                // check if another relationPicker has the same record selected
                if (this.modelUnique) {
                    var hasDuplicate = false,
                        fieldName;

                    Ext.each(this.editDialog.relationPickers, function(picker) {
                        if(picker != this) {
                            if(picker.getValue() == this.getValue()) {
                                hasDuplicate = true;
                                fieldName = picker.fieldLabel;
                                return false;
                            }
                        }
                    }, this);
                }

                if(hasDuplicate) {
                    if(this.combo.startRecord) {
                        var split = this.fullModelName.split('_Model_');
                        var startRecord = this.combo.startRecord;
                    } else {
                        startRecord = null;
                    }
                    this.combo.reset();
                    this.combo.setValue(startRecord);
                    this.combo.selectedRecord = startRecord;
                    this.combo.markInvalid(String.format(_('The {1} "{2}" is already used in the Field "{0}" and can be linked only once!'), fieldName, this.recordClass.getRecordName(), recordToAdd.get(this.recordClass.getMeta('titleProperty'))));
                    return false;
                }

                if(value != this.combo.startValue) {
                    this.removeIdFromStore(this.combo.startValue);
                }

                if(this.store.findBy(function(record, id) {
                    if(record) {
                        if(record.get('related_id') == value && record.get('type') == this.relationType) return true;
                    }
                }, this) == -1) {
                    var relationRecord = new Tine.Tinebase.Model.Relation(Ext.apply(this.editDialog.relationsPanel.getRelationDefaults(), {
                        related_record: recordToAdd.data,
                        related_id: recordToAdd.id,
                        related_model: this.fullModelName,
                        type: this.relationType,
                        own_degree: this.relationDegree
                    }), recordToAdd.id);

                    this.combo.startRecord = recordToAdd;
                    this.store.add(relationRecord);
                    }
            } else {
                this.removeIdFromStore(this.combo.startValue);
            }
        }, this);

        if(!this.editDialog.relationPickers) this.editDialog.relationPickers = [];
        this.editDialog.relationPickers.push(this);

        Tine.widgets.relation.PickerCombo.superclass.initComponent.call(this);
    },

    /**
     * removes the relation record by the related record id, if the type is the same handled by this combo
     * @param {String} id
     */
    removeIdFromStore: function(id) {
        if(id) {
            var oldRecPos = this.store.findBy(function(record) {
                if(record) {
                    if(record.get('related_id') == id && record.get('type') == this.relationType) return true;
                } else {
                    return false;
                }
            }, this);
            this.store.removeAt(oldRecPos);
        }
    },

    /**
     * Shortcut for the equivalent method of the combo
     */
    setValue: function(v) {
        return this.combo.setValue(v);
    },
    /**
     * Shortcut for the equivalent method of the combo
     */
    clear: function() {
        this.combo.startRecord = null;
        return this.combo.setValue(null);
    },
    /**
     * Shortcut for the equivalent method of the combo
     */
    getValue: function() {
        return this.combo.getValue();
    }
});

Ext.reg('tinerelationpickercombo', Tine.widgets.relation.PickerCombo);/*
 * Tine 2.0
 * 
 * @package     form
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schüle <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
Ext.ns('Tine.Tinebase.widgets.form');
 
/**
 * Configuration Panel
 * 
 * @namespace   Tine.Tinebase.widgets.form
 * @class       Tine.Tinebase.widgets.form.ConfigPanel
 * @extends     Ext.FormPanel
 * 
 * <p>Configuration Panel</p>
 * <p><pre>
 * NOTE: 
 * - For each section in the config file, a group in the form is added.
 * - Form names are constructed <section>_<subsection>... and transformed transparently by this component
 * - Enabling and Disabling of sections is handled automatically if you give the id 'setup-<section>-group' 
 *   to the checkboxToggle enabled fieldset
 *   
 * TODO         make TABS work
 * TODO         add reload button
 * </pre></p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * @param       {Object} config
 * @constructor
 * Create a new Tine.Tinebase.widgets.form.ConfigPanel
 */
Tine.Tinebase.widgets.form.ConfigPanel = Ext.extend(Ext.FormPanel, {
    
    /**
     * @cfg {String} saveMethod 
     * save method (for example 'Setup.saveConfig')
     */
    saveMethod: '',
    
    /**
     * @cfg {String} registryKey 
     * registry key (for example 'configData')
     */
    registryKey: '',
    
    /**
     * @cfg {Array} actionToolbarItems
     * additional items for actionToolbar
     */
    actionToolbarItems: [],

    /**
     * form panel cfg
     * 
     * @private
     */
    border: false,
    bodyStyle:'padding:5px 5px 0',
    labelAlign: 'left',
    labelSeparator: ':',
    labelWidth: 150,
    autoScroll: true,
    
    /**
     * application object
     * 
     * @property app
     * @type Tine.Tinebase.Application
     */
    app: null,

    /**
     * save config and update setup registry
     */
    onSaveConfig: function() {
        if (this.isValid()) {
            var configData = this.form2config();
            
            this.loadMask.show();
            Ext.Ajax.request({
                scope: this,
                params: {
                    method: this.saveMethod,
                    data: configData
                },
                success: function(response) {
                    var regData = Ext.util.JSON.decode(response.responseText);
                    // replace some registry data
                    for (key in regData) {
                        if (key != 'status') {
                            Tine.Setup.registry.replace(key, regData[key]);
                        }
                    }
                    this.loadMask.hide();
                },
                failure: function(response, options) {
                    this.loadMask.hide();
                    var responseText = Ext.util.JSON.decode(response.responseText),
                        exception = responseText.data ? responseText.data : responseText;
                        
                    switch (exception.code) {
                        // Service Unavailable! / configuration problem
                        case 503:
                            Ext.MessageBox.show({
                                title: _('Configuration Problem'), 
                                msg: exception.message,
                                buttons: Ext.Msg.OK,
                                icon: Ext.MessageBox.WARNING,
                                fn: function() {
                                    // TODO only reload own config data
                                    window.location.reload();
                                }
                            });
                            break;
                        default:
                            Tine.Tinebase.ExceptionHandler.handleRequestException(exception);
                    }
                }
            });
        } else {
            this.alertInvalidData();
        }
    },
    
    alertInvalidData: function() {
        Ext.Msg.alert(this.app.i18n._('Invalid configuration'), this.app.i18n._('You need to correct the red marked fields before config could be saved'));
    },
    
    /**
     * @private
     */
    initComponent: function() {
        this.initActions();
        this.items = this.getFormItems();

        Tine.Tinebase.widgets.form.ConfigPanel.superclass.initComponent.call(this);
    },
    
    /**
     * @private
     */
    onRender: function(ct, position) {
        Tine.Tinebase.widgets.form.ConfigPanel.superclass.onRender.call(this, ct, position);
        
        // always the same shit! when form panel is rendered, the form fields are not yet rendered ;-(
        var formData = this.config2form.defer(250, this, [Tine.Setup.registry.get(this.registryKey)]);
        
        Tine.Setup.registry.on('replace', this.applyRegistryState, this);
        
        this.loadMask = new Ext.LoadMask(ct, {msg: this.app.i18n._('Transferring Configuration...')});
    },
    
    /**
     * applies registry state to this cmp
     */
    applyRegistryState: function() {
    
    },

    /**
     * returns config form (extending classes need to overwrite this)
     * 
     * @private
     * @return {Array} items
     */
    getFormItems: function() {
        return [];
    },
    
    /**
     * transforms form data into a config object
     * 
     * @return {Object} configData
     */
    form2config: function() {
        // getValues only returns RAW HTML content... and we don't want to 
        // define a record here
        var formData = {};
        this.getForm().items.each(function(field) {
            formData[field.name] = field.getValue();
        });
        
        var configData = {};
        var keyParts, keyPart, keyGroup, dataPath;
        for (key in formData) {
            keyParts = key.split('_');
            dataPath = configData;
            
            while (keyPart = keyParts.shift()) {
                if (keyParts.length == 0) {
                    dataPath[keyPart] = formData[key];
                } else {
                    if (!dataPath[keyPart]) {
                        dataPath[keyPart] = {};
                        
                        // is group active?
                        keyGroup = Ext.getCmp('setup-' + keyPart + '-group');
                        if (keyGroup && keyGroup.checkboxToggle) {
                            dataPath[keyPart].active = !keyGroup.collapsed;
                        }
                    }
                
                    dataPath = dataPath[keyPart];
                }
            }
        }
        return configData;
    },
    
    /**
     * loads form with config data
     * 
     * @param  {Object} configData
     */
    config2form: function(configData) {
        var formData = arguments[1] ? arguments[1] : {};
        var currKey  = arguments[2] ? arguments[2] : '';
        
        var keyGroup;
        for (key in configData) {
            if(typeof configData[key] == 'object') {
                this.config2form(configData[key], formData, currKey ? currKey + '_' + key : key);
            } else {
                formData[currKey + '_' + key] = configData[key];
                
                // activate group?
                keyGroup = Ext.getCmp('setup-' + currKey + '-group');
                if (keyGroup && key == 'active' && configData.active) {
                    keyGroup.expand();
                }
            }
        }
        
        // skip transform calls
        if (! currKey) {
            this.getForm().setValues(formData);
            this.applyRegistryState();
        }
    },
    
    /**
     * @private
     */
    initActions: function() {
        this.action_saveConfig = new Ext.Action({
            text: this.app.i18n._('Save config'),
            iconCls: 'setup_action_save_config',
            scope: this,
            handler: this.onSaveConfig,
            disabled: true
        });
        
        // TODO add this to reload registry and update form / button
        /*
        this.action_reloadConfig = new Ext.Action({
            text: this.app.i18n._('Reload config'),
            handler: function() {
                Tine.Setup.registry.get(this.registryKey).on('replace', this.config2form, this, [Tine.Setup.registry.get(this.registryKey)]);
                Tine.Tinebase.tineInit.initRegistry();
            },
            iconCls: 'x-tbar-loading',
            scope: this
        });
        */

        this.actions = [
            this.action_saveConfig
            //this.action_reloadConfig
        ];
        
        this.actionToolbar = new Ext.Toolbar({
            items: this.actions.concat(this.actionToolbarItems)
        });
    },
    
    /**
     * checks if form is valid
     * 
     * @return {Boolean}
     */
    isValid: function() {
        var form = this.getForm();
        return form.isValid();
    },
    
    /**
     * change card layout card according to combo value
     * 
     * @param {Ext.form.ComboBox} combo
     * @param {String} backendIdPrefix
     */
    changeCard: function(combo, backendIdPrefix) {
        var value = combo.getValue();
        Tine.log.debug('changeCard() -> value: ' + value);
        
        var cardPanel = Ext.getCmp(backendIdPrefix + 'CardLayout'),
            cardLayout = (cardPanel) ? cardPanel.getLayout() : null;
        
        if (cardLayout && cardLayout !== 'card') {
            Tine.log.debug('changeCard() -> set active item: ' + backendIdPrefix + value);
            cardLayout.setActiveItem(backendIdPrefix + value);
        }
    }
});
/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Tine.Tinebase.widgets.form');

Tine.Tinebase.widgets.form.AutoCompleteField = Ext.extend(Ext.form.ComboBox, {
    
    /**
     * @cfg {String} property 
     * property to autocomplete (required)
     */
    property: null,
    
    /**
     * @cfg {Ext.data.Record} recordClass
     * record definition class  (required)
     */
    recordClass: null,
    
    /**
     * config
     */
    forceSelection: false,
    triggerAction: 'all',
    minChars: 3,
    queryParam: 'startswith',
    hideTrigger: true,
    
    /**
     * @private
     */
    initComponent: function() {
        if (! this.property && this.name) {
            this.property = this.name;
        }
        
        this.displayField = this.valueField = this.property;
        
        this.store = new Ext.data.JsonStore({
            fields: [this.property],
            baseParams: {
                method: this.recordClass.getMeta('appName') + '.autoComplete' + this.recordClass.getMeta('modelName') + 'Property',
                property: this.property,
                sort: this.property,
                dir: 'ASC'
            },
            root: 'results',
            totalProperty: 'totalcount'
        });
        
        
        Tine.Tinebase.widgets.form.AutoCompleteField.superclass.initComponent.call(this);
    }
});

Ext.reg('tine.widget.field.AutoCompleteField', Tine.Tinebase.widgets.form.AutoCompleteField);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.form');

/**
 * Upload Button
 * 
 * @namespace   Tine.widgets.form
 * @class       Tine.widgets.form.FileUploadButton
 * @extends     Ext.Button
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @constructor
 * @param {Object} config The configuration options.
 */
Tine.widgets.form.FileUploadButton = Ext.extend(Ext.Button, {
    /**
     * @cfg {Array} allowedTypes
     * array with allowed file types (for example: ['jpg', 'gif', 'png']
     */
    allowedTypes: null,
    
    /**
     * @property browsePlugin
     * @type Ext.ux.file.BrowsePlugin
     */
    browsePlugin: null,
    
    // private config overrides
    iconCls: 'action_import',
    
    /**
     * init this upload button
     */
    initComponent: function() {
        this.origHandler = this.handler;
        this.origScope = this.scope;
        this.origIconCls = this.iconCls;
        
        this.handler = this.onFileSelect;
        this.scope = this;
        
        this.browsePlugin = new Ext.ux.file.BrowsePlugin({
            multiple: false
        });
        
        this.plugins = this.plugins || [];
        this.plugins.push(this.browsePlugin);
        
        Tine.widgets.form.FileUploadButton.superclass.initComponent.call(this);
    },
    
    /**
     * called when a file got selected
     * 
     * @param {ux.BrowsePlugin} fileSelector
     * @param {Ext.EventObj} e
     */
    onFileSelect: function(fileSelector, event) {
        if (Ext.isArray(this.allowedTypes) && this.allowedTypes.indexOf(fileSelector.getFileCls()) < 0) {
            Ext.MessageBox.alert(_('Wrong File Type'), [_('Please select a file with one of the following extensions:'), '<br />', this.allowedTypes].join('')).setIcon(Ext.MessageBox.ERROR);
            return;
        }
        
        this.upload = new Ext.ux.file.Upload({
            fileSelector: fileSelector
        });
        this.upload.on('uploadcomplete', this.onUploadComplete, this);
        
        this.setIconClass('x-tinebase-uploading');
        this.upload.upload();
    },
    
    /**
     * called when the upload completed
     * 
     * @param {Ext.ux.file.Upload} upload
     * @param {Ext.ux.file.Upload.file} fileRecord
     */
    onUploadComplete: function(upload, fileRecord) {
        this.fileRecord = fileRecord;
        
        this.setText([fileRecord.get('name'), ' (', Tine.Tinebase.common.byteRenderer(fileRecord.get('size')), ')'].join(''));
//        this.setIconClass(this.origIconCls);
        this.setIconClass('action_saveAndClose');
        
        // autowidth does not work
        this.setWidth(Ext.util.TextMetrics.measure(this.btnEl, this.text).width + 20);
        
        if (Ext.isFunction(this.origHandler)) {
            this.origHandler.apply(this.origScope, arguments);
        }
    },
    
    getTempFileId: function() {
        return this.fileRecord.get('tempFile').id;
    }
    
});

Ext.reg('tw.uploadbutton', Tine.widgets.form.FileUploadButton);/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Michael Spahn <m.spahn@metaways.de>
 * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
 */

/*global Ext, Tine*/

Ext.ns('Tine.Tinebase.widgets.form');

/**
 * This is a TriggerField which generates a random UID on click
 * (UID generated by Tine.Tinebase.data.Record.generateUID())
 * 
 * @namespace   Tine.Tinebase.widgets.form
 * @author      Michael Spahn <m.spahn@metaways.de>
 * @class       Tine.Tinebase.widgets.form.UidTriggerField
 * @extends     Ext.form.TriggerField
 */
Tine.Tinebase.widgets.form.UidTriggerField = Ext.extend(Ext.form.TriggerField, {

    itemCls: 'tw-uidTriggerField',
    enableKeyEvents: true,

    /**
     * Overrides initComponent to reenable field if it is empty
     */
    initComponent: function () {
        this.on('keyup', this.manageTriggerField);

        Tine.Tinebase.widgets.form.UidTriggerField.superclass.initComponent.call(this);
    },

    /**
     * Shows trigger field if value is empty and hides if it is filled
     */
    manageTriggerField: function () {
        this.setHideTrigger(this.getValue());
    },

    /**
     * Overrides setValue and shows trigger field if value is empty and hides if it is filled
     * 
     * @param Mixed
     * @return {Ext.form.Field} this
     */
    setValue: function (value) {
        var ret = Tine.Tinebase.widgets.form.UidTriggerField.superclass.setValue.call(this, value);

        this.manageTriggerField();
        return ret;
    },

    /**
     * Fills field on click with a random, unique checksum and hides trigger button
     */
    onTriggerClick: function () {
        if (!this.getValue()) {
            this.setValue(Tine.Tinebase.data.Record.generateUID());
            this.setHideTrigger(true);
        }
    }
});

Ext.reg('tw-uidtriggerfield',Tine.Tinebase.widgets.form.UidTriggerField);
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.persistentfilter.model');


/**
 * @namespace   Tine.widgets.persistentfilter
 * @class       Tine.widgets.persistentfilter.model.PersistentFilter
 * @extends     Tine.Tinebase.data.Record
 * 
 * <p>Model of a Persistent Filter</p>
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * 
 * @param       {Object} data
 * @constructor
 * Create a new Tine.widgets.persistentfilter.model.PersistentFilter Record
 */
Tine.widgets.persistentfilter.model.PersistentFilter = Tine.Tinebase.data.Record.create(Tine.Tinebase.Model.genericFields.concat([
    {name: 'id'},
    {name: 'application_id'},
    {name: 'account_id'},
    {name: 'model'},
    {name: 'filters'},
    {name: 'name'},
    {name: 'description'}
]), {
    appName: 'Tinebase',
    modelName: 'PersistentFilter',
    idProperty: 'id',
    titleProperty: 'name',

    recordName: 'Favorite',
    recordsName: 'Favorites',
    
    /**
     * is a shared persistent filter
     * 
     * @return {Boolean}
     */
    isShared: function() {
        return this.get('account_id') === null;
    },
    
    /**
     * is a persistent filter (localized) which is shipped
     * 
     * @return {Boolean}
     */
    isShipped: function() {
        return ((this.get('account_id') === null) && (this.get('created_by') === null));
    },  
    
    /**
     * is default of current user
     * 
     * @return {Boolean}
     */
    isDefault: function() {
        var app = Tine.Tinebase.appMgr.getById(this.get('application_id'));
        return this.app && this.get('id') === app.getRegistry().get('preferences').get('defaultpersistentfilter');
    }
});

/**
 * @namespace   Tine.widgets.persistentfilter
 * 
 * @param       {String} appName
 * @return      {model.PersistentFilter} or null
 */
Tine.widgets.persistentfilter.model.PersistentFilter.getDefaultFavorite = function(appName) {
    var app = Tine.Tinebase.appMgr.get(appName),
        appPrefs = app.getRegistry().get('preferences'),
        defaultFavoriteId = appPrefs ? appPrefs.get('defaultpersistentfilter') : null;
   
    return defaultFavoriteId ? Tine.widgets.persistentfilter.store.getPersistentFilterStore().getById(defaultFavoriteId) : null
};

/**
 * @namespace   Tine.widgets.persistentfilter
 * @class       Tine.Tinebase.data.RecordProxy
 * @singelton   Tine.widgets.persistentfilter.model.persistentFilterProxy
 * 
 * <p>Backend for Persistent Filter</p>
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 */
Tine.widgets.persistentfilter.model.persistentFilterProxy = new Tine.Tinebase.data.RecordProxy({
    appName: 'Tinebase_PersistentFilter',
    modelName: 'PersistentFilter',
    recordClass: Tine.widgets.persistentfilter.model.PersistentFilter
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.persistentfilter.store');

/**
 * @namespace   Tine.widgets.persistentfilter
 * @class       Tine.widgets.persistentfilter.store.PersistentFilterStore
 * @extends     Ext.data.ArrayStore
 * 
 * <p>Store for Persistent Filter Records</p>
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * 
 * @param       {Object} config
 * @constructor
 * Create a new Tine.widgets.persistentfilter.store.PersistentFilterStore
 */
Tine.widgets.persistentfilter.store.PersistentFilterStore = Ext.extend(Ext.data.ArrayStore, {

});

/**
 * @namespace   Tine.widgets.persistentfilter
 * 
 * get store of all persistent filters
 * 
 * @static
 * @singleton
 * @return {PersistentFilterStore}
 */
Tine.widgets.persistentfilter.store.getPersistentFilterStore = function() {
    if (! Tine.widgets.persistentfilter.store.persistentFilterStore) {
        if (window.isMainWindow) {
            var fields = Tine.widgets.persistentfilter.model.PersistentFilter.getFieldDefinitions();
            
            // create store
            var s = Tine.widgets.persistentfilter.store.persistentFilterStore = new Tine.widgets.persistentfilter.store.PersistentFilterStore({
                fields: fields
            });
            
            // populate store
            var persistentFiltersData = Tine.Tinebase.registry.get("persistentFilters").results;

            Ext.each(persistentFiltersData, function(data,index) {
                var filterRecord = new Tine.widgets.persistentfilter.model.PersistentFilter(data);
                s.add(filterRecord);
            }, this);
        } else {
            // TODO test this in IE!
            var mainWindow = Ext.ux.PopupWindowMgr.getMainWindow();
            Tine.widgets.persistentfilter.store.persistentFilterStore = mainWindow.Tine.widgets.persistentfilter.store.getPersistentFilterStore();
        }
    }
    
    return Tine.widgets.persistentfilter.store.persistentFilterStore;
}
/*
 * Tine 2.0
 * 
 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3 @author
 * Cornelius Weiss <c.weiss@metaways.de> @copyright Copyright (c) 2009-2011
 * Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.widgets.persistentfilter');

/**
 * @namespace Tine.widgets.persistentfilter
 * @class Tine.widgets.persistentfilter.PickerPanel
 * @extends Ext.tree.TreePanel
 * 
 * <p>
 * PersistentFilter Picker Panel
 * </p>
 * 
 * @author Cornelius Weiss <c.weiss@metaways.de>
 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * 
 * @param {Object}
 *            config
 * @constructor Create a new Tine.widgets.persistentfilter.PickerPanel
 */
Tine.widgets.persistentfilter.PickerPanel = Ext.extend(Ext.tree.TreePanel, {

    /**
     * @cfg {application}
     */
    app : null,

    /**
     * @cfg {String} filterMountId mount point of persistent filter folder
     *      (defaults to null -> root node)
     */
    filterMountId : null,
    /**
     * @cfg {String} contentType mainscreen.activeContentType  
     */
    contentType: null,
    /**
     * @private
     */
    autoScroll : true,
    border : false,
    rootVisible : false,

    enableDD : true,
    stateId : null,

    /**
     * grid favorites panel belongs to
     * 
     * @type Tine.widgets.grid.GridPanel
     */
    grid : null,

    /**
     * @private
     */
    initComponent : function() {
        
        this.stateId = 'widgets-persistentfilter-pickerpanel_' + this.app.name + '_' + this.contentType;

        this.store = this.store || Tine.widgets.persistentfilter.store.getPersistentFilterStore();
        var state = Ext.state.Manager.get(this.stateId, {});
        
        this.recordCollection = this.store.queryBy(function(record, id) {
            if(record.get('application_id') == this.app.id) {
                if(this.contentType) {
                    var modelName = this.app.appName + '_Model_' + this.contentType + 'Filter';
                    if(record.get('model') == modelName) {
                        return true;
                    } else {
                        return false;
                    }
                } else {
                    return true;
                }
            }
            return false;
        }, this);
        
        var sorting = 10000;
        
        this.recordCollection.each(function(record) {
            if(state[record.get('id')]) record.set('sorting', state[record.get('id')]);
            else {
                sorting++;
                record.set('sorting',sorting);
            }
        }, this);
                
        this.loader = new Tine.widgets.persistentfilter.PickerTreePanelLoader({
            app : this.app,
            recordCollection : this.recordCollection,
            contentType: this.contentType
        });

        new Ext.tree.TreeSorter(this, {
            property: 'sorting',                   
            sortType : function(node) {
                return node.attributes.sorting;
            }
        });

        this.on('nodedrop', function() {
            this.saveState();
        }, this);

        this.filterNode = this.filterNode || new Ext.tree.AsyncTreeNode({
            text : _('My favorites'),
            id : '_persistentFilters',
            leaf : false,
            expanded : true
        });

        if (this.filterMountId === null) {
            this.root = this.filterNode;
        }

        Tine.widgets.persistentfilter.PickerPanel.superclass.initComponent.call(this);

        this.on('click', function(node) {
            if (node.attributes.isPersistentFilter) {
                this.getFilterToolbar().fireEvent('change', this.getFilterToolbar());
                node.select();
                this.onFilterSelect(this.recordCollection.get(node.id));
            } else if (node.id == '_persistentFilters') {
                node.expand();
                return false;
            }
        }, this);

        this.on('contextmenu', this.onContextMenu, this);
    },

    /**
     * @private
     */
    afterRender : function() {
        Tine.widgets.persistentfilter.PickerPanel.superclass.afterRender.call(this);

        if (this.filterMountId !== null) {
            this.getNodeById(this.filterMountId).appendChild(this.filterNode);
        }
        // due to dependencies isues we need to wait after render
        this.getFilterToolbar().on('change', this.onFilterChange, this);
    },

    /**
     * saves the sort state of the tree
     */
    saveState: function() {
        var state = {};
        var i = 0;

        this.getRootNode().eachChild(function(node) {
            i++;
            node.attributes.sorting = i;
            var rec = this.recordCollection.get(node.attributes.id);
            var oldSort = rec.get('sorting');
            if(oldSort != i) {
                rec.set('sorting', i);
                rec.commit();
            }
            state[node.id] = i;
        }, this);

        Ext.state.Manager.set(this.stateId, state);
    },
    
    /**
     * load grid from saved filter
     * 
     * NOTE: As all filter plugins add their data on the stores beforeload event
     * we need a litte hack to only present a filterid.
     * 
     * When a filter is selected, we register ourselve as latest beforeload,
     * remove all filter data and paste our filter data. To ensure we are always
     * the last listener, we directly remove the listener afterwards
     */
    onFilterSelect : function(persistentFilter) {
        var store = this.getGrid().getStore();

        // NOTE: this can be removed when all instances of filterplugins are
        // removed
        store.on('beforeload', this.storeOnBeforeload, this);
        store.load({persistentFilter : persistentFilter});
    },

    /**
     * storeOnBeforeload
     * 
     * @param {}
     *            store
     * @param {}
     *            options
     */
    storeOnBeforeload : function(store, options) {
        options.params.filter = options.persistentFilter.get('filters');
        store.un('beforeload', this.storeOnBeforeload, this);
    },

    /**
     * called on filtertrigger of filter toolbar
     */
    onFilterChange : function() {
        this.getSelectionModel().clearSelections();
    },

    /**
     * returns additional ctx items
     * 
     * @TODO: make this a hooking approach!
     * 
     * @param {model.PersistentFilter}
     * @return {Array}
     */
    getAdditionalCtxItems : function(filter) {
        var items = [];

        var as = Tine.Tinebase.appMgr.get('ActiveSync');
        if (as) {
            items = items.concat(as.getPersistentFilterPickerCtxItems(this,    filter));
        }

        return items;
    },

    /**
     * returns filter toolbar of mainscreen center panel of app this picker
     * panel belongs to
     */
    getFilterToolbar : function() {
        if (!this.filterToolbar) {
            this.filterToolbar = this.getGrid().filterToolbar;
        }

        return this.filterToolbar;
    },

    /**
     * get grid
     * 
     * @return {Tine.widgets.grid.GridPanel}
     */
    getGrid : function() {
        if (!this.grid) {
            this.grid = this.app.getMainScreen().getCenterPanel();
        }

        return this.grid;
    },

    /**
     * returns persistent filter tree node
     * 
     * @return {Ext.tree.AsyncTreeNode}
     */
    getPersistentFilterNode : function() {
        return this.filterNode;
    },

    /**
     * handler for ctxmenu clicks on tree nodes
     * 
     * @param {Ext.tree.TreeNode}
     *            node
     * @param {Ext.EventObject}
     *            e
     */
    onContextMenu : function(node, e) {
        if (!node.attributes.isPersistentFilter) {
            return;
        }

        var record = this.store.getById(node.id);
        var isHidden = record.isShipped();
    
        var menu = new Ext.menu.Menu({
            items : [{
                text : _('Delete Favorite'),
                iconCls : 'action_delete',
                hidden : isHidden,
                handler : this.onDeletePersistentFilter.createDelegate(this, [node, e])
            }, {
                text : _('Edit Favorite'),
                iconCls : 'action_edit',
                hidden : isHidden,
                handler : this.onEditPersistentFilter.createDelegate(this, [node, e])
            }, {
                text : _('Overwrite Favorite'),
                iconCls : 'action_saveFilter',
                hidden : isHidden,
                handler : this.onOverwritePersistentFilter.createDelegate(this, [node, e])
            }].concat(this.getAdditionalCtxItems(record))
        });

        menu.showAt(e.getXY());

    },

    /**
     * handler to delete filter
     * 
     * @param {Ext.tree.TreeNode}
     *            node
     */
    onDeletePersistentFilter : function(node) {
        Ext.MessageBox.confirm(_('Confirm'), String.format(_('Do you really want to delete the favorite "{0}"?'), node.text), function(_btn) {
            if (_btn == 'yes') {
                Ext.MessageBox.wait(_('Please wait'), String.format(_('Deleting Favorite "{0}"'), this.containerName, node.text));

                var record = this.store.getById(node.id);
                Tine.widgets.persistentfilter.model.persistentFilterProxy.deleteRecords([record], {
                    scope : this,
                    success : function() {
                        this.store.remove(record);
                        this.recordCollection.remove(record);
                        this.filterNode.findChild('id', record.get('id')).remove();
                        Ext.MessageBox.hide();
                    }
                });
            }
        }, this);
    },

    /**
     * handler to rename filter
     * 
     * @param {Ext.tree.TreeNode}
     *            node
     */
    onEditPersistentFilter : function(node) {
        var record = this.store.getById(node.id);
        this.getEditWindow(record);
    },

    /**
     * handler to overwrite filter
     * 
     * @param {Ext.tree.TreeNode}
     *            node
     */
    onOverwritePersistentFilter : function(node) {
        
        var record = this.store.getById(node.id);
        Ext.MessageBox.confirm(_('Overwrite?'), String.format(_('Do you want to overwrite the favorite "{0}"?'), node.text), function(_btn) {
            if (_btn == 'yes') {
                Ext.MessageBox.wait(_('Please wait'), String.format(_('Overwriting Favorite "{0}"'), node.text));
                var ftb = this.getFilterToolbar();
                record.set('filters', ftb.getAllFilterData());
                
                this.createOrUpdateFavorite(record);
            }
        }, this, false, node.text);
    },

    /**
     * save persistent filter
     */
    saveFilter : function() {
        var ftb = this.getFilterToolbar();
        
        var record = this.getNewEmptyRecord();
        
        record.set('filters', ftb.getAllFilterData());
        
        // recheck that current ftb is saveable
        if (!ftb.isSaveAllowed()) {
            Ext.Msg.alert(_('Could not save Favorite'), _('Your current view does not support favorites'));
            return;
        }
        this.getEditWindow(record);
    },
    
    getEditWindow: function(record) {

        if(record.hasOwnProperty('modified')) {
            var title = _('Create Favorite');
        } else {
            var title = _('Edit Favorite');
        }
                
        var height = 160;
        if ((Tine.Tinebase.common.hasRight('manage_shared_favorites', this.app.name)) && (!record.isDefault())) height = 185;
        
        var newWindow = Tine.WindowFactory.getWindow({
            modal : true,
            app : this.app,
            record : record,
            title : title,
            width : 300,
            height : height,
            contentPanelConstructor : 'Tine.widgets.persistentfilter.EditPersistentFilterPanel',
            contentPanelConstructorConfig : null
        });

        newWindow.on('update', function(win) {
            Ext.MessageBox.wait(_('Please wait'), String.format(_('Saving Favorite "{0}"'), record.get('name')));
            this.createOrUpdateFavorite(newWindow.record);
        }, this);
        
    },

    /**
     * create or update a favorite
     * 
     * @param {Tine.widgets.persistentfilter.model.PersistentFilter} record
     */
    
    createOrUpdateFavorite : function(record) {
        
        Tine.widgets.persistentfilter.model.persistentFilterProxy.saveRecord(record, {
            scope : this,
            success : function(savedRecord) {

                var existing = this.recordCollection.get(savedRecord.id);

                if (existing) {
                    savedRecord.set('sorting', existing.get('sorting'));
                    this.store.remove(existing);
                    this.recordCollection.remove(existing);
                    
                    this.filterNode.findChild('id', existing.get('id')).remove();
                } else {
                    var sorting = 0;
                    savedRecord.set('sorting', sorting);
                }

                var attr = savedRecord.data;
                this.loader.inspectCreateNode(attr);
                
                this.filterNode.appendChild(new Ext.tree.TreeNode(attr));
                
                this.store.add(savedRecord);
                this.recordCollection.add(savedRecord);
                this.recordCollection.sort();
                
                if(!existing) this.saveState();
                
                Ext.Msg.hide();
                // reload this filter?
                this.selectFilter(savedRecord);
            }
        });
    },

    /**
     * select given persistent filter
     * 
     * @param {model.PersistentFilter}
     *            persistentFilter
     */
    selectFilter : function(persistentFilter) {
        if (!persistentFilter) {
            return;
        }
        this.getFilterToolbar().fireEvent('change', this.getFilterToolbar());
        var node = this.getNodeById(persistentFilter.id);
        if (node) {
            this.getSelectionModel().select(node);
        } else {
            // mark for selection
            this.getLoader().selectedFilterId = persistentFilter.id;
        }

        this.onFilterSelect(persistentFilter);
    },
    
    
    getNewEmptyRecord: function() {
        var model = this.filterModel,
            ftb = this.getFilterToolbar();
            
        if (!model) {
            var recordClass = this.recordClass || this.treePanel ? this.treePanel.recordClass : ftb.store.reader.recordType;
            model = recordClass.getMeta('appName') + '_Model_' + recordClass.getMeta('modelName') + 'Filter';
        }

        var record = new Tine.widgets.persistentfilter.model.PersistentFilter({
            application_id : this.app.id,
            account_id : Tine.Tinebase.registry.get('currentAccount').accountId,
            model : model,
            filters : null,
            name : null,
            description : null
        });
        
        return record;
    }
});

/**
 * @namespace Tine.widgets.persistentfilter
 * @class Tine.widgets.persistentfilter.PickerTreePanelLoader
 * @extends Tine.widgets.tree.Loader
 * 
 * <p>
 * PersistentFilter Picker Panel Tree Loader
 * </p>
 * 
 * @author Cornelius Weiss <c.weiss@metaways.de>
 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * 
 * @param {Object}
 *            config
 * @constructor Create a new Tine.widgets.persistentfilter.PickerTreePanelLoader
 */
Tine.widgets.persistentfilter.PickerTreePanelLoader = Ext.extend(Tine.widgets.tree.Loader, {

            /**
             * @cfg {Ext.util.MixedCollection} recordCollection
             */          
            recordCollection: null,

            /**
             * @cfg {String} selectedFilterId id to autoselect
             */
            selectedFilterId : null,

            /**
             * 
             * @param {Ext.tree.TreeNode}
             *            node
             * @param {Function}
             *            callback Function to call after the node has been
             *            loaded. The function is passed the TreeNode which was
             *            requested to be loaded.
             * @param (Object)
             *            scope The cope (this reference) in which the callback
             *            is executed. defaults to the loaded TreeNode.
             */
            requestData : function(node, callback, scope) {
                if (this.fireEvent("beforeload", this, node, callback) !== false) {
                    
                    node.beginUpdate();
                    this.recordCollection.each(function(record) {
                                var n = this.createNode(record.copy().data);
                                if (n) {
                                    node.appendChild(n);
                                }
                            }, this);
                    node.endUpdate();

                    this.runCallback(callback, scope || node, [node]);

                } else {
                    // if the load is cancelled, make sure we notify
                    // the node that we are done
                    this.runCallback(callback, scope || node, []);
                }
            },

            inspectCreateNode : function(attr) {
                var isPersistentFilter = !!attr.model && !!attr.filters;

                var isShared = (attr.account_id === null) ? true : false;

                var addText = '';
                var addClass = '';
                if (isShared) {
                    addText = _('(shared)');
                    addClass = '-shared';
                }

                if (isPersistentFilter) {
                    Ext.apply(attr, {
                        isPersistentFilter : isPersistentFilter,
                        text : Ext.util.Format.htmlEncode(this.app.i18n._hidden(attr.name)),
                        qtip : Tine.Tinebase.common.doubleEncode(attr.description ? this.app.i18n._hidden(attr.description) + ' ' + addText : addText),
                        selected : attr.id === this.selectedFilterId,
                        id : attr.id,

                        sorting : attr.sorting,
                        draggable : true,
                        allowDrop : false,

                        leaf : attr.leaf === false ? attr.leaf : true,
                        cls : 'tinebase-westpanel-node-favorite' + addClass
                    });
                }
            }
        });
/*
 * Tine 2.0
 * 
 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3 @author
 * @author Alexander Stintzing <a.stintzing@metaways.de> 
 * @copyright Copyright (c) 2009-2012
 * Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.ns('Tine.widgets.persistentfilter');

/**
 * @namespace Tine.widgets.persistentfilter
 * @class Tine.widgets.persistentfilter.EditPersistentFilterPanel
 * @extends Ext.FormPanel
 * 
 * <p>
 * PersistentFilter Edit Dialog
 * </p>
 * 
 * @author Alexander Stintzing <a.stintzing@metaways.de>
 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * 
 * @param {Object} config
 */

Tine.widgets.persistentfilter.EditPersistentFilterPanel = Ext.extend(Ext.FormPanel, {

            layout : 'fit',
            border : false,
            cls : 'tw-editdialog',

            bodyStyle : 'padding:5px',
            labelAlign : 'top',

            anchor : '100% 100%',
            deferredRender : false,
            buttonAlign : null,
            bufferResize : 500,

            // private
            initComponent : function() {
                this.addEvents('cancel', 'save', 'close');
                // init actions
                this.initActions();
                // init buttons and tbar
                this.initButtons();
                // get items for this dialog
                this.items = this.getFormItems();

                Tine.widgets.persistentfilter.EditPersistentFilterPanel.superclass.initComponent.call(this);
            },

            initActions : function() {
                this.action_save = new Ext.Action({
                    text : _('OK'),
                    minWidth : 70,
                    scope : this,
                    handler : this.onSave,
                    iconCls : 'action_saveAndClose'
                });
                this.action_cancel = new Ext.Action({
                    text : _('Cancel'),
                    minWidth : 70,
                    scope : this,
                    handler : this.onCancel,
                    iconCls : 'action_cancel'
                });
            },

            initButtons : function() {
                this.fbar = ['->', this.action_cancel, this.action_save];
            },

            onRender : function(ct, position) {
                Tine.widgets.dialog.EditDialog.superclass.onRender.call(this, ct, position);

                // generalized keybord map for edit dlgs
                new Ext.KeyMap(this.el, [{
                    key : [13],
                    ctrl : false,
                    fn : this.onSave,
                    scope : this
                }]);
            },
            
            onSave : function() {
                
                if(!this.window.record) {
                    this.window.record = this.getNewEmptyRecord();
                }

                // Name of the favorite
                if (this.inputTitle.isValid()) {
                    if (this.inputTitle.getValue().length < 40) {
                        this.window.record.set('name', this.inputTitle.getValue());
                    } else {
                        Ext.Msg.alert(_('Favorite not Saved'), _('You have to supply a shorter name! Names of favorite can only be up to 40 characters long.'));
                        this.onCancel();
                    }
                } else {
                    Ext.Msg.alert(_('Favorite not Saved'),_('You have to supply a name for the favorite!'));
                    this.onCancel();
                }

                // Description of the favorite
                if (this.inputDescription.isValid()) {
                    this.window.record.set('description', this.inputDescription.getValue());
                }

                this.window.record.set('account_id', Tine.Tinebase.registry.get('currentAccount').accountId);

                // Favorite Checkbox
                if (Tine.Tinebase.common.hasRight('manage_shared_favorites', this.window.app.name)) {
                    if (this.inputCheck.getValue()) {
                        this.window.record.set('account_id', null);
                    }
                }
                this.window.fireEvent('update');
                this.purgeListeners();
                this.window.purgeListeners();
                this.window.close();
            },

            onCancel : function() {
                this.fireEvent('cancel');
                this.purgeListeners();
                this.window.close();
            },

            getFormItems : function() {

                this.inputTitle = new Ext.form.TextField({
                    value: (this.window.record) ? this.window.record.get('name') : '',
                    allowBlank : false,
                    fieldLabel : _('Title'),
                    width : '97%',
                    minLength : 1,
                    maxLength : 40
                });

                this.inputDescription = new Ext.form.TextField({
                    value: (this.window.record) ? this.window.record.get('description') : '',
                    allowBlank : true,
                    fieldLabel : _('Description'),
                    width : '97%',
                    minLength : 0,
                    maxLength : 255
                });

                this.inputCheck = new Ext.form.Checkbox({
                    checked: (this.window.record) ? this.window.record.isShared() : false,
                    hideLabel: true,
                    boxLabel: _('Shared Favorite (visible by all users)')
                });

                var items = [
                    {
                        region : 'center',
                        layout : {
                            align : 'stretch',
                            type : 'vbox'
                        }
                    }, 
                    this.inputTitle, 
                    this.inputDescription
                ];

                if (Tine.Tinebase.common.hasRight('manage_shared_favorites', this.window.app.name)) {
                    items.push(this.inputCheck);
                }

                return {
                    border : false,
                    frame : true,
                    layout : 'form',
                    items : items
                };
            }

        });/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
 Ext.ns('Tine', 'Tine.Tinebase');
 
 /**
  * @namespace  Tine.Tinebase
  * @class      Tine.Tinebase.PasswordChangeDialog
  * @extends    Ext.Window
  */
Tine.Tinebase.PasswordChangeDialog = Ext.extend(Ext.Window, {
    
    id: 'changePassword_window',
    closeAction: 'close',
    modal: true,
    width: 350,
    height: 230,
    minWidth: 350,
    minHeight: 230,
    layout: 'fit',
    plain: true,
    title: null,

    initComponent: function() {
        this.currentAccount = Tine.Tinebase.registry.get('currentAccount');
        this.title = (this.title !== null) ? this.title : String.format(_('Change Password For "{0}"'), this.currentAccount.accountDisplayName);
        
        this.items = new Ext.FormPanel({
            bodyStyle: 'padding:5px;',
            buttonAlign: 'right',
            labelAlign: 'top',
            anchor:'100%',
            id: 'changePasswordPanel',
            defaults: {
                xtype: 'textfield',
                inputType: 'password',
                anchor: '100%',
                allowBlank: false
            },
            items: [{
                id: 'oldPassword',
                fieldLabel: _('Old Password'), 
                name:'oldPassword'
            },{
                id: 'newPassword',
                fieldLabel: _('New Password'), 
                name:'newPassword'
            },{
                id: 'newPasswordSecondTime',
                fieldLabel: _('Repeat new Password'), 
                name:'newPasswordSecondTime'
            }],
            buttons: [{
                text: _('Cancel'),
                iconCls: 'action_cancel',
                handler: function() {
                    Ext.getCmp('changePassword_window').close();
                }
            }, {
                text: _('Ok'),
                iconCls: 'action_saveAndClose',
                handler: function() {
                    var form = Ext.getCmp('changePasswordPanel').getForm();
                    var values;
                    if (form.isValid()) {
                        values = form.getValues();
                        if (values.newPassword == values.newPasswordSecondTime) {
                            Ext.Ajax.request({
                                waitTitle: _('Please Wait!'),
                                waitMsg: _('changing password...'),
                                params: {
                                    method: 'Tinebase.changePassword',
                                    oldPassword: values.oldPassword,
                                    newPassword: values.newPassword
                                },
                                success: function(_result, _request){
                                    var response = Ext.util.JSON.decode(_result.responseText);
                                    if (response.success) {
                                        Ext.getCmp('changePassword_window').close();
                                        Ext.MessageBox.show({
                                            title: _('Success'),
                                            msg: _('Your password has been changed.'),
                                            buttons: Ext.MessageBox.OK,
                                            icon: Ext.MessageBox.INFO
                                        });
                                        Ext.Ajax.request({
                                            params: {
                                                method: 'Tinebase.updateCredentialCache',
                                                password: values.newPassword
                                            }
                                        });
                                    } else {
                                        Ext.MessageBox.show({
                                            title: _('Failure'),
                                            msg: response.errorMessage,
                                            buttons: Ext.MessageBox.OK,
                                            icon: Ext.MessageBox.ERROR  
                                        });
                                    }
                                }
                            });
                        } else {
                            Ext.MessageBox.show({
                                title: _('Failure'),
                                msg: _('The new passwords mismatch, please correct them.'),
                                buttons: Ext.MessageBox.OK,
                                icon: Ext.MessageBox.ERROR 
                            });
                        }
                    }
                }                    
            }]
        });
        
        Tine.Tinebase.PasswordChangeDialog.superclass.initComponent.call(this);
    }
});
/*
 * Tine 2.0
 *
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiß <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */

 Ext.ns('Tine', 'Tine.Tinebase');

 /**
  * @namespace  Tine.Tinebase
  * @class      Tine.Tinebase.AboutDialog
  * @extends    Ext.Window
  */
Tine.Tinebase.AboutDialog = Ext.extend(Ext.Window, {

    closeAction: 'close',
    modal: true,
    width: 400,
    height: 360,
    minWidth: 400,
    minHeight: 360,
    layout: 'fit',
    title: null,

    initAboutTpl: function() {
        this.aboutTpl = new Ext.XTemplate(
            '<div class="tb-about-dlg">',
                '<div class="tb-about-img"><a href="{logoLink}" target="_blank"><img src="{logo}" /></a></div>',
                '<div class="tb-link-home"><a href="{logoLink}" target="_blank">{logoLink}</a></div>',
                '<div class="tb-about-version">Version: {codeName}</div>',
                '<div class="tb-about-build">({packageString})</div>',
                '<div class="tb-about-copyright">Copyright: 2007-{[new Date().getFullYear()]}&nbsp;<a href="http://www.metaways.de" target="_blank">Metaways Infosystems GmbH</a></div>',
                '<div class="tb-about-credits-license"><p><a href="javascript:\'void()\'" class="license" /><a href="javascript:\'void()\'" class="credits" /></p></div>',
            '</div>'
        );
    },
    
    initComponent: function() {
        this.title = String.format(_('About {0}'), Tine.title);
        
        this.initAboutTpl();
        
        var version = (Tine.Tinebase.registry.get('version')) ? Tine.Tinebase.registry.get('version') : {
            codeName: 'unknown',
            packageString: 'unknown'
        };

        this.items = {
            layout: 'fit',
            border: false,
            html: this.aboutTpl.applyTemplate({
                logo: Tine.Tinebase.LoginPanel.prototype.loginLogo,
                logoLink: Tine.weburl,
                codeName: version.codeName,
                packageString: version.packageString
            }),
            buttons: [{
                text: _('Ok'),
                iconCls: 'action_saveAndClose',
                handler: this.close,
                scope: this
            }]
        };
        
        // create links
        this.on('afterrender', function() {
            var el = this.getEl().select('div.tb-about-dlg div.tb-about-credits-license a.license');
            el.insertHtml('beforeBegin', ' ' + _('Released under different') + ' ');
            el.insertHtml('beforeEnd', _('Open Source Licenses'));
            el.on('click', function(){
                var ls = new Tine.Tinebase.LicenseScreen();
                ls.show();
            });
            
            var el = this.getEl().select('div.tb-about-dlg div.tb-about-credits-license a.credits');
            el.insertHtml('beforeBegin', ' ' + _('with the help of our') + ' ');
            el.insertHtml('beforeEnd', _('Contributors'));
            el.on('click', function() {
                var cs = new Tine.Tinebase.CreditsScreen();
                cs.show();
            });
            
            
        }, this);

        Tine.Tinebase.AboutDialog.superclass.initComponent.call(this);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.Tinebase');

/**
 * Tine 2.0 main application manager
 * 
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.AppManager
 * @extends     Ext.util.Observable
 * @consturctor
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Tinebase.AppManager = function() {
    /**
     * @property apps
     * @type Ext.util.MixedCollection
     * 
     * enabled apps
     */
    this.apps = new Ext.util.MixedCollection({});
    
    this.addEvents(
        /**
         * @event beforeactivate
         * fired before an application gets activated. Retrun false to stop activation
         * @param {Tine.Aplication} app about to activate
         */
        'beforeactivate',
        /**
         * @event activate
         * fired when an application gets activated
         * @param {Tine.Aplication} activated app
         */
        'activate',
        /**
         * @event beforedeactivate
         * fired before an application gets deactivated. Retrun false to stop deactivation
         * @param {Tine.Aplication} app about to deactivate
         */
        'beforedeactivate',
        /**
         * @event deactivate
         * fired when an application gets deactivated
         * @param {Tine.Aplication} deactivated app
         */
        'deactivate',
        /**
         * @event windowopenexception
         * windowopenexceptionated 
         * @param {} Exception
         */
        'windowopenexception'
    );
    
    
    // fill this.apps with registry data
    var enabledApps = Tine.Tinebase.registry.get('userApplications');
    var app;
    for(var i=0; i<enabledApps.length; i++) {
        app = enabledApps[i];
        
        // if the app is not in the namespace, we don't initialise it
        // we don't have a Tinebase 'Application'
        if (Tine[app.name] && ! app.name.match(/(Tinebase)/)) {
            app.appName = app.name;
            app.isInitialised = false;
            
            this.apps.add(app.appName, app);
        }
        
        this.apps.sort("ASC", function (app1, app2) {
            return parseInt(app1.order, 10) - parseInt(app2.order, 10);
        });
    }
};

Ext.extend(Tine.Tinebase.AppManager, Ext.util.Observable, {
    /**
     * @cfg {Tine.Application}
     */
    defaultApp: null,
    
    /**
     * @property activeApp
     * @type Tine.Application
     * 
     * currently active app
     */
    activeApp: null,
    
    /**
     * activate application
     * 
     * @param {Tine.Application} app
     * @return {Boolean}
     * 
     * TODO think about adding a fallback app if app mainscreen could not be fetched 
     */
    activate: function(app) {
        if (app || (app = this.getDefault()) ) {
            if (app == this.getActive()) {
                // app is already active, nothing to do
                return true;
            }
            
            if (this.activeApp) {
                if ((this.fireEvent('beforedeactivate', this.activeApp) === false || this.activeApp.onBeforeDeActivate() === false)) {
                    return false;
                }
                
                this.activeApp.onDeActivate();
                this.fireEvent('deactivate', this.activeApp);
                this.activeApp = null;
            }
            
            if (this.fireEvent('beforeactivate', app) === false || app.onBeforeActivate() === false) {
                return false;
            }
            
            var mainscreen = app.getMainScreen();
            if (mainscreen) {
                mainscreen.show();
            } else {
                // app has no mainscreen / perhaps it has been disabled
                return false;
            }
            
            this.activeApp = app;
            app.onActivate();
            this.fireEvent('activate', app);
        }
    },
    
    /**
     * returns an appObject
     * 
     * @param {String} appName
     * @return {Tine.Application}
     */
    get: function(appName) {
        if (Ext.isObject(appName) && appName.hasOwnProperty('appName')) {
            appName = appName.appName;
        }
        if (! this.isEnabled(appName)) {
            return false;
        }
        
        var app = this.apps.get(appName);
        if (! app.isInitialised) {
            var appObj = this.getAppObj(app);
            appObj.isInitialised = true;
            Ext.applyIf(appObj, app);
            this.apps.replace(appName, appObj);
        }
        
        return this.apps.get(appName);
    },
    
    /**
     * returns appObject
     * 
     * @param {String} applicationId
     * @return {Tine.Application}
     */
    getById: function(applicationId) {
        var appObj = null;
        Ext.each(Tine.Tinebase.registry.get('userApplications'), function(rawApp) {
            if (rawApp.id === applicationId) {
                appObj = this.get(rawApp.appName);
                return false;
            }
        }, this);
        
        return appObj;
    },
    
    /**
     * returns currently activated app
     * @return {Tine.Application}
     */
    getActive: function() {
        return this.activeApp;
    },
    
    /**
     * returns appObject of default app
     * 
     * @return {Tine.Application}
     */
    getDefault: function() {
        if (! this.defaultApp) {
            var defaultAppName = (Tine.Tinebase.registry.get('preferences') && Tine.Tinebase.registry.get('preferences').get('defaultapp')) 
                ? Tine.Tinebase.registry.get('preferences').get('defaultapp') 
                : this.defaultAppName;
                
            this.defaultApp = this.get(defaultAppName) || this.apps.find(function(app) {return app.hasMainScreen});
            
            if (! this.defaultApp) {
                // no global exception concept yet...
                //throw Ext.Error('no apps enabled', 620);
                Ext.MessageBox.show({
                    title: _('Missing Applications'), 
                    msg: _('There are no applications enabled for you. Please contact your administrator.'),
                    buttons: Ext.Msg.OK,
                    icon: Ext.MessageBox.WARNING
                });
            }
        }
        
        return this.defaultApp;
    },
    
    /**
     * set default app for this session
     * 
     * @param {Tine.Application/String} app
     */
    setDefault: function(app) {
        if (Ext.isString(app)) {
            app = this.get(app);
        }
        
        if (app) {
            this.defaultApp = app;
        }
    },
    
    /**
     * returns a list of all apps for current user
     */
    getAll: function() {
        this.initAll();
        return this.apps;
    },
    
    /**
     * checks wether a given app is enabled for current user or not
     */
    isEnabled: function(appName) {
        var app = this.apps.get(appName);
        return app ? app.status == 'enabled' : false;
    },
    
    /**
     * initialises all enabled apps
     * @private
     */
    initAll: function() {
        this.apps.each(function(app) {
            this.get(app.appName);
        }, this);
    },
    
    /**
     * @private
     */
    getAppObj: function(app) {
       try{
            // legacy
            if (typeof(Tine[app.appName].getPanel) == 'function') {
                // make a legacy Tine.Application
                return this.getLegacyApp(app);
            }

            return typeof(Tine[app.appName].Application) == 'function' ? new Tine[app.appName].Application(app) : new Tine.Tinebase.Application(app);
            
        } catch(e) {
            console.error('Initialising of Application "' + app.appName + '" failed with the following message:' + e);
            console.warn(e);
            return false;
        }
    },
    
    /**
     * @private
     */
    getLegacyApp: function(app) {
        var appPanel = Tine[app.appName].getPanel();
        var appObj =  new Tine.Tinebase.Application(app);
        var mainScreen = new Tine.widgets.MainScreen({app: appObj});
        
        Ext.apply(mainScreen, {
            appPanel: appPanel,
            getContainerTreePanel: function() {
                return this.appPanel;
            },
            getWestPanel: function() {
                return this.appPanel;
            },
            show: function() {
                Tine.Tinebase.MainScreen.setActiveTreePanel(appPanel, true);
                appPanel.fireEvent('beforeexpand', appPanel);
            }
        });
        Ext.apply(appObj, {
            mainScreen: mainScreen
        });
        appPanel.on('render', function(p) {
            p.header.remove();
            // additionally to removing the DOM node, we also need to reset the 
            // header class variable, as IE evals "if (this.header)" to true otherwise 
            p.header = false;
            p.doLayout();
        });
        
        return appObj;
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.Tinebase');

/**
 * Main appStarter/picker app pile panel
 * 
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.AppPile
 * @extends     Ext.Panel
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Tinebase.AppPile = Ext.extend(Ext.Panel, {
    /**
     * @property apps
     * @type Ext.util.Observable
     */
    apps: null,
    
    /**
     * @property defaultApp
     * @type Tine.Application
     */
    defaultApp: null,
    
    /**
     * @private
     * @property {Object} items
     * holds internal item elements
     */
    els: {},
    
    /**
     * @private
     */
    border: false,
    layout: 'fit',
    autoScroll: true,
    
    /**
     * @private
     * @todo: register app.on('titlechange', ...)
     */
    initComponent: function() {
        this.apps = Tine.Tinebase.appMgr.getAll();
        this.defaultApp = Tine.Tinebase.appMgr.getDefault();
        
        Tine.Tinebase.appMgr.on('activate', this.onActivateApp, this);
        
        Tine.Tinebase.AppPile.superclass.initComponent.call(this);
        
        this.tpl = new Ext.XTemplate(
            '<div class="x-panel-header x-panel-header-noborder x-unselectable x-accordion-hd">',
                '<img class="x-panel-inline-icon {iconCls}" src="' + Ext.BLANK_IMAGE_URL + '"/>',
                '<span class="x-panel-header-text app-panel-apptitle-text">{title}</span>',
            '</div>'
        ).compile();

    },
    
    /**
     * @private
     */
    onRender: function(ct, position) {
        Tine.Tinebase.AppPile.superclass.onRender.call(this, ct, position);

        this.apps.each(function(app) {
            if (app.hasMainScreen) {
                this.els[app.appName] = this.tpl.insertFirst(this.body, {title: app.getTitle(), iconCls: app.getIconCls()}, true);
                this.els[app.appName].setStyle('cursor', 'pointer');
                this.els[app.appName].addClassOnOver('app-panel-header-over');
                this.els[app.appName].on('click', this.onAppTitleClick, this, app);
            }
            
        }, this);
        
        // limit to max pile height
        this.on('resize', function() {
            var appHeaders = Ext.DomQuery.select('div[class^=x-panel-header]', this.el.dom);
            for (var i=0, height=0; i<appHeaders.length; i++) {
                height += Ext.fly(appHeaders[i]).getHeight();
            }
            if (arguments[2] && arguments[2] > height) {
                this.setHeight(height);
            }
        });
        this.setActiveItem(this.els[this.defaultApp.appName]);
    },
    
    /**
     * executed when an app get activated by mainscreen
     * 
     * @param {Tine.Application} app
     */
    onActivateApp: function(app) {
        this.setActiveItem(this.els[app.appName]);
    },
    
    /**
     * @private
     */
    onAppTitleClick: function(e, dom, app) {
        this.setActiveItem(Ext.get(dom));
        Tine.Tinebase.appMgr.activate(app);
    },
    
    /**
     * @private
     */
    setActiveItem: function(el) {
        for (var appName in this.els) {
            if (el) {
                if (el == this.els[appName] || el.parent() == this.els[appName]) {
                    this.els[appName].addClass('app-panel-header-active');
                } else {
                    this.els[appName].removeClass('app-panel-header-active');
                }
            }
        }
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.Tinebase');

/**
 * Main appStarter/picker tab panel
 * 
 * NOTE: Tab panels are not sortable yet {@see http://www.extjs.com/forum/showthread.php?p=55045#post55045}
 * 
 * @todo discuss: default app vs. last active tab
 * @todo discuss: have a set of default apps?
 * 
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.AppTabsPanel
 * @extends     Ext.TabPanel
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Tinebase.AppTabsPanel = function(config) {
    Ext.apply(this, config);
    this.plugins = [new Ext.ux.TabPanelSortPlugin({
        dragZoneConfig: {
            onBeforeDrag: this.onBeforeDrag.createDelegate(this) 
        },
        dropZoneConfig: {
            getTargetFromEvent: this.getTargetFromEvent.createDelegate(this)
        }
    })];
    
    Tine.Tinebase.AppTabsPanel.superclass.constructor.call(this, config);
};

Ext.extend(Tine.Tinebase.AppTabsPanel, Ext.TabPanel, {
    activeTab: 1,
    
    stateful: true,
    stateEvents: ['add', 'remove', 'tabchange', 'tabsort'],
    stateId: 'tinebase-mainscreen-apptabs',
    
    /**
     * @cfg {Array} of Strings currentTab ids
     */
    currentTabs: null,
    
    // private
    findTargets : function(e){
        var item = null,
            itemEl = e.getTarget('li:not(.x-tab-edge)', this.strip);

        if(itemEl){
            item = this.getComponent(itemEl.id.split(this.idDelimiter)[1]);
            if(item && item.disabled){
                return {
                    close : null,
                    item : null,
                    el : null
                };
            }
        }
        return {
            close : e.getTarget('.x-tab-strip-close', this.strip),
            item : item,
            el : itemEl
        };
    },
    
    /**
     * init appTabsPanel
     */
    initComponent: function() {
        Ext.apply(this, Ext.state.Manager.get(this.stateId));
        
        this.initMenu();
        
        this.items = [{
            id: this.app2id('menu'),
            title: Tine.title,
            iconCls: 'tine-favicon',
            closable: true,
            listeners: {
                scope: this,
                beforeclose: this.onBeforeTabClose
            }
        }].concat(this.getDefaultTabItems());
        
        // set states last active app to the sessions default app
        Tine.Tinebase.appMgr.setDefault(this.id2appName(this.activeTab));
        
        Tine.Tinebase.appMgr.on('activate', this.onActivateApp, this);
        this.on('beforetabchange', this.onBeforeTabChange, this);
        this.on('tabsort', this.onTabChange, this);
        this.on('add', this.onTabChange, this);
        this.on('remove', this.onTabChange, this);
        
        this.supr().initComponent.call(this);
        
        // fake an access stack
        for (var i=1, tabCount=this.items.getCount(); i<tabCount; i++) {
            this.stack.add(this.items.get(i));
        }
    },

    /**
     * init the combined appchooser/tine menu
     */
    initMenu: function() {
        this.menu = new Ext.menu.Menu({
            layout: 'column',
            width: 400,
            autoHeight: true,
            style: {
                'background-image': 'none'
            },
            defaults: {
                xtype: 'menu',
                floating: false,
                columnWidth: 0.5,
                hidden: false,
                listeners: {
                    scope: this,
                    itemclick: function(item, e) {
                        this.menu.hide();
                    }
                },
                style: {
                    //'border-color': 'transparent'
                    'border': '0'
                }
            },
            items: [{
                items: this.getAppItems(),
                style: {'border-right': '1px solid #E2E2E3'}
            }, {
                items: Tine.Tinebase.MainScreen.getMainMenu().getMainActions()
            }]
        });
    },
    
    /**
     * executed after render
     */
    afterRender: function() {
        this.supr().afterRender.apply(this, arguments);
        
        this.menuTabEl = Ext.get(this.getTabEl(0));
        this.menuTabEl.addClass('tine-mainscreen-apptabspanel-menu-tabel');
        
        // remove plain style
        this.header.removeClass('x-tab-panel-header-plain');
    },
    
    /**
     * get app items for the tabPanel
     * 
     * @return {Array}
     */
    getAppItems: function() {
        var appItems = [];
        Tine.Tinebase.appMgr.getAll().each(function(app) {
            if (app.hasMainScreen) {
                appItems.push({
                    text: app.getTitle(),
                    iconCls: app.getIconCls(),
                    handler: this.onAppItemClick.createDelegate(this, [app])
                });
            }
        }, this);
        
        return appItems.reverse();
    },
    
    /**
     * get default tab items configurations
     * 
     * @return {Array}
     */
    getDefaultTabItems: function() {
        if (Ext.isEmpty(this.currentTabs)) {
            this.currentTabs = [this.id2appName(Tine.Tinebase.appMgr.getDefault())];
        }
        
        var tabItems = [];
        
        Ext.each(this.currentTabs, function(appName) {
            var app = Tine.Tinebase.appMgr.get(appName);
            if (app) {
                tabItems.push(this.getTabItem(app));
            }
        }, this);
        
        return tabItems;
    },
    
    /**
     * deny drag on menuEl
     * @param {} e
     * @return {}
     */
    onBeforeDrag: function(data, e) {
        return e.getTarget('li[class*=mainscreen-apptabspanel-menu-tabel]', 10) ? false : true;
    },
    
    /**
     * deny drop on menuEl
     * @param {} e
     * @return {}
     */
    getTargetFromEvent: function(e) {
        var target = e.getTarget('ul[class^=x-tab]', 10),
            li = this.findTargets(e);
            
        if (li.el && li.el == this.menuTabEl.dom) {
            return false;
        }
        
        return target;
    },
            
    /**
     * get tabs state
     * 
     * @return {Object}
     */
    getState: function() {
        return {
            currentTabs: this.currentTabs,
            activeTab: Ext.isNumber(this.activeTab) ? this.activeTab : this.items.indexOf(this.activeTab)
        };
    },
    
    /**
     * get tab item configuration
     * 
     * @param {Tine.Application} app
     * @return {Object}
     */
    getTabItem: function(app) {
        return {
            id: this.app2id(app),
            title: app.getTitle(),
            iconCls: app.getIconCls(),
            closable: true,
            listeners: {
                scope: this,
                beforeclose: this.onBeforeTabClose,
                activate: this.onTabActivate
            }
        };
    },
    
    /**
     * executed when an app get activated by mainscreen
     * 
     * @param {Tine.Application} app
     */
    onActivateApp: function(app) {
        var tab = this.getItem(this.app2id(app)) || this.add(this.getTabItem(app));
        
        this.setActiveTab(tab);
    },
    
    /**
     * executed when an app item in this.menu is clicked
     * 
     * @param {Tine.Application} app
     */
    onAppItemClick: function(app) {
        Tine.Tinebase.appMgr.activate(app);
        
        this.menu.hide();
    },
    
    /**
     * executed on tab changes
     * 
     * @param {TabPanel} this
     * @param {Panel} newTab The tab being activated
     * @param {Panel} currentTab The current active tab
     */
    onBeforeTabChange: function(tp, newTab, currentTab) {
        if (this.id2appName(newTab) === 'menu') {
            this.menu.show(this.menuTabEl, 'tl-bl');
            return false;
        }
    },
    
    /**
     * executed before a tab panel is closed
     * 
     * @param {Ext.Panel} tab
     * @return {boolean}
     */
    onBeforeTabClose: function(tab) {
        if (this.id2appName(tab) === 'menu') {
            return this.onBeforeTabChange(this, tab, this.activeTab);
        }
        
        // don't close last app panel
        return this.items.getCount() > 2;
    },
    
    /**
     * executed when a tab gets activated
     * 
     * @param {Ext.Panel} tab
     */
    onTabActivate: function(tab) {
        var appName = this.id2appName(tab);
        var app = Tine.Tinebase.appMgr.get(appName);
        
        // fixme
        if (Ext.getCmp('treecards').rendered) {
            Tine.Tinebase.appMgr.activate(app);
        }
    },
    
    /**
     * executed when tabs chages
     */
    onTabChange: function() {
        var tabCount = this.items.getCount();
        var closable = tabCount > 2;
        
        this.currentTabs = [];
        
        for (var i=1, tab, el; i<tabCount; i++) {
            tab = this.items.get(i);
            
            // update currentTabs
            this.currentTabs.push(this.id2appName(tab.id));
            
            // handle closeables
            tab.closable = closable;
            el = this.getTabEl(i);
            if (el) {
                Ext.get(el)[closable ? 'addClass' : 'removeClass']('x-tab-strip-closable');
            }
        }
    },
    
    /**
     * returns appName of given tab/id
     * 
     * @param {Ext.Panel/String/Number} id
     * @return {String} appName
     */
    id2appName: function(id) {
        if (Ext.isNumber(id)) {
            if (Ext.isArray(this.items)) {
                id = this.items[id] ? this.items[id].id : null;
            } else {
                id = this.items.get(id);
            }
        }
        
        if (Ext.isObject(id) && ! Ext.isEmpty(id)) {
            id = id.id;
        }
        
        if (Ext.isString(id)) {
            return id.split('-').pop();
        }
        
        return null;
    },
    
    /**
     * returns tab id of given app
     * @param {Tine.Application/String} app
     */
    app2id: function(app) {
        var appName = app.appName || app;
        
        return this.id + '-' + appName;
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.Tinebase');

/**
 * Tine 2.0 jsclient main menu
 * 
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.MainMenu
 * @extends     Ext.Toolbar
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Tinebase.MainMenu = Ext.extend(Ext.Toolbar, {
    /**
     * @cfg {Boolean} showMainMenu
     */
    showMainMenu: false,    
    style: {'padding': '0px 2px'},
    cls: 'tbar-mainmenu',
    
    /**
     * @type Array
     * @property mainActions
     */
    mainActions: null,
    
    initComponent: function() {
        this.initActions();
        this.onlineStatus = new Ext.ux.ConnectionStatus({
            showIcon: false
        });
        
        this.items = this.getItems();
        
        var buttonTpl = new Ext.Template(
            '<table id="{4}" cellspacing="0" class="x-btn {3}"><tbody class="{1}">',
            '<tr><td class="x-btn-ml"><i>&#160;</i></td><td class="x-btn-mc"><em class="{2}" unselectable="on"><button type="{0}"></button></em></td><td class="x-btn-mr"><i>&#160;</i></td></tr>',
            '</tbody></table>'
        ).compile();
        
        Ext.each(this.items, function(item) {
            item.template = buttonTpl;
        }, this);
        
        this.supr().initComponent.call(this);
    },
    
    getItems: function() {
        return [{
            text: Tine.title,
            hidden: !this.showMainMenu,
            menu: {
                id: 'Tinebase_System_Menu', 
                items: this.getMainActions()
        }},
        '->', {
            text: String.format(_('User: {0}'), Tine.Tinebase.registry.get('currentAccount').accountDisplayName),
            menu: this.getUserActions(),
            menuAlign: 'tr-br'
        },
        this.onlineStatus,
        this.action_logout];
    },
    
    /**
     * returns all main actions
     * 
     * @return {Array}
     */
    getMainActions: function() {
        if (! this.mainActions) {
            this.mainActions = [
                this.action_aboutTine,
                '-',
                this.getUserActions(),
                '-',
                this.action_logout
            ];
            
            if (Tine.Tinebase.registry.get("version").buildType.match(/(DEVELOPMENT|DEBUG)/)) {
                this.mainActions.splice(1, 0, '-', this.action_showDebugConsole);
            }
        }
        
        return this.mainActions;
    },
    
    getUserActions: function() {
        if (! this.userActions) {
            this.userActions = [
                this.action_editProfile,
                this.action_showPreferencesDialog,
                this.action_changePassword,
                this.action_notificationPermissions,
                this.action_installChromeWebApp
            ];
        }
        return this.userActions;
    },
    
    /**
     * initialize actions
     * @private
     */
    initActions: function() {
        this.action_aboutTine = new Ext.Action({
            text: String.format(_('About {0}'), Tine.title),
            handler: this.onAboutTine20,
            iconCls: 'action_about'
        });
        
        this.action_showDebugConsole = new Ext.Action({
            text: _('Debug Console (Ctrl + F11)'),
            handler: Tine.Tinebase.common.showDebugConsole,
            iconCls: 'tinebase-action-debug-console'
        });
        
        this.action_showPreferencesDialog = new Ext.Action({
            text: _('Preferences'),
            disabled: false,
            handler: this.onEditPreferences,
            iconCls: 'action_adminMode'
        });

        this.action_editProfile = new Ext.Action({
            text: _('Edit Profile'),
            disabled: false,
            handler: this.onEditProfile,
            iconCls: 'tinebase-accounttype-user'
        });
        
        this.action_changePassword = new Ext.Action({
            text: _('Change password'),
            handler: this.onChangePassword,
            disabled: (Tine.Tinebase.registry.get('changepw') == '0'),
            iconCls: 'action_password'
        });
        
        this.action_logout = new Ext.Action({
            text: _('Logout'),
            tooltip:  String.format(_('Logout from {0}'), Tine.title),
            iconCls: 'action_logOut',
            handler: this.onLogout,
            scope: this
        });
        
        this.action_notificationPermissions = new Ext.Action({
            text: _('Allow desktop notifications'),
            tooltip:  _('Request permissions for webkit desktop notifications.'),
            iconCls: 'action_edit',
            disabled: ! (window.webkitNotifications && window.webkitNotifications.checkPermission() != 0),
            handler: function() {
                window.webkitNotifications.requestPermission(Ext.emptyFn);
            },
            scope: this
        });
        
        this.action_installChromeWebApp = new Ext.Action({
            text: _('Install web app'),
            tooltip:  _('Install Tine 2.0 as web app in your browser.'),
            iconCls: 'action_edit',
            disabled: ! (Ext.isChrome && chrome.app && !chrome.app.isInstalled),
            handler: function() {
                chrome.app.install();
            },
            scope: this
        });
    },
    
    /**
     * @private
     */
    onAboutTine20: function() {
        var aboutDialog = new Tine.Tinebase.AboutDialog();
        aboutDialog.show();
    },
    
    /**
     * @private
     */
    onChangePassword: function() {
        var passwordDialog = new Tine.Tinebase.PasswordChangeDialog();
        passwordDialog.show();
    },
    
    /**
     * @private
     */
    onEditPreferences: function() {
        Tine.widgets.dialog.Preferences.openWindow({});
    },

    /**
     * @private
     */
    onEditProfile: function() {
        Tine.widgets.dialog.Preferences.openWindow({
            initialCardName: 'Tinebase.UserProfile'
        });
    },
    
    /**
     * the logout button handler function
     * @private
     */
    onLogout: function() {
        if (Tine.Tinebase.registry.get('confirmLogout') != '0') {
            Ext.MessageBox.confirm(_('Confirm'), _('Are you sure you want to logout?'), function(btn, text) {
                if (btn == 'yes') {
                    this._doLogout();
                }
            }, this);
        } else {
            this._doLogout();
        }
    },
    
    /**
     * logout user & redirect
     */
    _doLogout: function() {
        Ext.MessageBox.wait(_('Logging you out...'), _('Please wait!'));
        Ext.Ajax.request( {
            params : {
                method : 'Tinebase.logout'
            },
            callback : function(options, Success, response) {
                // remove the event handler
                // the reload() trigers the unload event
                var redirect = (Tine.Tinebase.registry.get('redirectUrl'));
                if (redirect && redirect != '') {
                    window.location = Tine.Tinebase.registry.get('redirectUrl');
                } else {
                    window.location.reload();
                }
            }
        });
    }
});

//Tine.Tinebase.MainMenu = function(config) {
//    this.isPrism = 'platform' in window;
//    Ext.apply(this, config);
//    // NOTE: Prism has no top menu yet ;-(
//    //       Only the 'icon menu' is implemented (right mouse button in dock(mac)/tray(other)
//    /*if (this.isPrism) {
//        window.platform.showNotification('title', 'text', 'images/clear-left.png');
//        this.menu = window.platform.icon().title = 'supertine';
//        //window.platform.menuBar.addMenu(“myMenu”);
//        
//        this.menu = window.platform.icon().menu;
//        window.platform.icon().menu.addMenuItem("myItem", "My Item", function(e) { window.alert("My Item!"); });
//        
//        var sub = this.menu.addSubmenu('mytest', 'mytest');
//        sub.addMenuItem('test', 'test', function() {alert('test');});
//        
//    } else { */
//        this.menu = new Ext.Toolbar({
//            id: this.id, 
//            height: this.height,
//            items: this.items
//        });
//        
//        return this.menu;
//    //}
//};


/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
Ext.ns('Tine.Tinebase');

/**
 * Tine 2.0 jsclient MainScreen.
 * 
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.MainScreen
 * @extends     Ext.Panel
 * @singleton   
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Tinebase.MainScreen = Ext.extend(Ext.Panel, {
    
    border: false,
    layout: {
        type:'vbox',
        align:'stretch',
        padding:'0'
    },
    
    /**
     * @cfg {String} appPickerStyle "tabs" or "pile" defaults to "tabs"
     */
    appPickerStyle: 'tabs',
    
    /**
     * @private
     */
    initComponent: function() {
        // NOTE: this is a cruid method to create some kind of singleton...
        Tine.Tinebase.MainScreen = this;
        
        this.initLayout();
        Tine.Tinebase.appMgr.on('activate', this.onAppActivate, this);
        
        this.supr().initComponent.call(this);
    },
    
    /**
     * @private
     */
    initLayout: function() {
        
        this.items = [{
            cls: 'tine-mainscreen-topbox',
            border: false,
            html: '<div class="tine-mainscreen-topbox-left"></div><div class="tine-mainscreen-topbox-middle"></div><div class="tine-mainscreen-topbox-right"></div>'
        }, {
            cls: 'tine-mainscreen-mainmenu',
            height: 20,
            layout: 'fit',
            border: false,
            items: this.getMainMenu()
        }, {
            cls: 'tine-mainscreen-apptabs',
            hidden: this.appPickerStyle != 'tabs',
            border: false,
            height: Ext.isGecko ? 22 : 20,
            items: new Tine.Tinebase.AppTabsPanel({
                plain: true
            })
        }, {
            cls: 'tine-mainscreen-centerpanel',
            flex: 1,
            layout: 'border',
            border: false,
            items: [{
                cls: 'tine-mainscreen-centerpanel-north',
                region: 'north',
                layout: 'card',
                activeItem: 0,
                //height: 26,
                height: 59,
                border: false,
                id:     'north-panel-2',
                items: []
            }, {
                cls: 'tine-mainscreen-centerpanel-center',
                region: 'center',
                id: 'center-panel',
                animate: true,
                border: false,
                layout: 'card',
                activeItem: 0,
                items: []
            }, {
                cls: 'tine-mainscreen-centerpanel-west',
                region: 'west',
                id: 'west',
                stateful: false,
                layout: 'border',
                split: true,
                width: 200,
                minSize: 100,
                maxSize: 300,
                border: false,
                collapsible:true,
                collapseMode: 'mini',
                header: false,
                items: [{
                    cls: 'tine-mainscreen-centerpanel-west-apptitle',
                    hidden: this.appPickerStyle != 'pile',
                    region: 'north',
                    layout: 'fit',
                    border: false,
                    height: 40,
                    baseCls: 'x-panel-header',
                    html: '<div class ="app-panel-title"></div>'
                }, {
                    cls: 'tine-mainscreen-centerpanel-west-treecards',
                    border: false,
                    id: 'treecards',
                    region: 'center',
                    layout: 'card',
                    activeItem: 0,
                    items: []
                }, new Tine.Tinebase.AppPile({
                    cls: 'tine-mainscreen-centerpanel-west-apppile',
                    hidden: this.appPickerStyle != 'pile',
                    region: 'south',
                    layout: 'fit',
                    border: false,
                    split: true,
                    collapsible:true,
                    collapseMode: 'mini',
                    header: false
                })]
            }]
        }];
    },
    
    /**
     * returns main menu
     * 
     * @return {Ext.Menu}
     */
    getMainMenu: function() {
        if (! this.mainMenu) {
            this.mainMenu = new Tine.Tinebase.MainMenu({
                showMainMenu: this.appPickerStyle != 'tabs'
            });
        }
        
        return this.mainMenu;
    },
    
    /**
     * appMgr app activation listener
     * 
     * @param {Tine.Application} app
     */
    onAppActivate: function(app) {
        Tine.log.info('Activating app ' + app.appName);
        
        // set document / browser title
        var postfix = (Tine.Tinebase.registry.get('titlePostfix')) ? Tine.Tinebase.registry.get('titlePostfix') : '',
            // some apps (Felamimail atm) can add application specific title postfixes
            // TODO generalize this
            appPostfix = (document.title.match(/^\([0-9]+\) /)) ? document.title.match(/^\([0-9]+\) /)[0] : '';
        document.title = appPostfix + Tine.title + postfix  + ' - ' + app.getTitle();
        
        // set left top title
        Ext.DomQuery.selectNode('div[class=app-panel-title]').innerHTML = app.getTitle();
        
    },
    
    /**
     * executed after rendering process
     * 
     * @private
     */
    afterRender: function() {
        this.supr().afterRender.apply(this, arguments);
        
        this.activateDefaultApp();
        
        // check for new version 
        if (Tine.Tinebase.common.hasRight('check_version', 'Tinebase')) {
            Tine.widgets.VersionCheck();
        }
        
        if (Tine.Tinebase.registry.get('mustchangepw')) {
            var passwordDialog = new Tine.Tinebase.PasswordChangeDialog({
                title: _('Your password expired. Please enter a new user password:')
            });
            passwordDialog.show();
        }
    },
    
    /**
     * activate default application
     * 
     * NOTE: this fn waits for treecard panel to be rendered
     * 
     * @private
     */
    activateDefaultApp: function() {
        if (Ext.getCmp('treecards').rendered) {
            Tine.Tinebase.appMgr.activate();
        } else {
            this.activateDefaultApp.defer(10, this);
        }
    },
    
    /**
     * sets the active content panel
     * 
     * @param {Ext.Panel} item Panel to activate
     * @param {Bool} keep keep panel
     */
    setActiveContentPanel: function(panel, keep) {
        var cardPanel = Ext.getCmp('center-panel');
        panel.keep = keep;
        
        this.cleanupCardPanelItems(cardPanel);
        this.setActiveCardPanelItem(cardPanel, panel);
    },
    
    /**
     * sets the active tree panel
     * 
     * @param {Ext.Panel} panel Panel to activate
     * @param {Bool} keep keep panel
     */
    setActiveTreePanel: function(panel, keep) {
        var cardPanel = Ext.getCmp('treecards');
        panel.keep = keep;
        
        this.cleanupCardPanelItems(cardPanel);
        this.setActiveCardPanelItem(cardPanel, panel);
    },
    
    /**
     * sets item
     * 
     * @param {Ext.Toolbar} panel toolbar to activate
     * @param {Bool} keep keep panel
     */
    setActiveToolbar: function(panel, keep) {
        var cardPanel = Ext.getCmp('north-panel-2');
        panel.keep = keep;
        
        this.cleanupCardPanelItems(cardPanel);
        this.setActiveCardPanelItem(cardPanel, panel);
    },
    
    /**
     * gets the currently displayed toolbar
     * 
     * @return {Ext.Toolbar}
     */
    getActiveToolbar: function() {
        var northPanel = Ext.getCmp('north-panel-2');

        if(northPanel.layout.activeItem && northPanel.layout.activeItem.el) {
            return northPanel.layout.activeItem.el;
        } else {
            return false;
        }
    },
    
    /**
     * remove all items which should not be keeped -> don't have a keep flag
     * 
     * @param {Ext.Panel} cardPanel
     */
    cleanupCardPanelItems: function(cardPanel) {
        if(cardPanel.items) {
            for (var i=0,p; i<cardPanel.items.length; i++){
                p =  cardPanel.items.get(i);
                if (! p.keep) {
                    cardPanel.remove(p);
                }
            }  
        }
    },
    
    /**
     * add or set given item
     * 
     * @param {Ext.Panel} cardPanel
     * @param {Ext.Panel} item
     */
    setActiveCardPanelItem: function(cardPanel, item) {
        if (cardPanel.items.indexOf(item) !== -1) {
            cardPanel.layout.setActiveItem(item.id);
        } else {
            cardPanel.add(item);
            cardPanel.layout.setActiveItem(item.id);
            cardPanel.doLayout();
        }
    }
});
/* 
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009-2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */

/*global Ext, Tine*/
 
Ext.ns('Tine.Tinebase');

/**
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.LoginPanel
 * @extends     Ext.Panel
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
    
    /**
     * @cfg {String} defaultUsername prefilled username
     */
    defaultUsername: '',
    
    /**
     * @cfg {String} defaultPassword prefilled password
     */
    defaultPassword: '',
    
    /**
     * @cfg {String} loginMethod server side login method
     */
    loginMethod: 'Tinebase.login',
    
    /**
     * @cfg {String} loginLogo logo to show
     */
    loginLogo: 'images/tine_logo.png',
    
    /**
     * @cfg {String} onLogin callback after successfull login
     */
    onLogin: Ext.emptyFn,
    
    /**
     * @cfg {Boolean} show infobox (survey, links, text)
     */
    showInfoBox: true,
    
    /**
     * @cfg {String} scope scope of login callback
     */
    scope: null,
    
    layout: 'fit',
    border: false,
    
    /**
     * return loginPanel
     * 
     * @return {Ext.FromPanel}
     */
    getLoginPanel: function () {
        if (! this.loginPanel) {
            this.loginPanel = new Ext.FormPanel({
                width: 460,
                height: 250,
                frame: true,
                labelWidth: 90,
                cls: 'tb-login-panel',
                items: [{
                    xtype: 'container',
                    cls: 'tb-login-lobobox',
                    border: false,
                    html: '<a target="_blank" href="' + Tine.weburl + '" border="0"><img src="' + this.loginLogo + '" /></a>'
                }, {
                    xtype: 'label',
                    cls: 'tb-login-big-label',
                    text: _('Login')
                }, {
                    xtype: 'tinelangchooser',
                    name: 'locale',
                    width: 170,
                    tabindex: 1
                }, {
                    xtype: 'textfield',
                    tabindex: 2,
                    width: 170,
                    fieldLabel: _('Username'),
                    id: 'username',
                    name: 'username',
                    allowBlank: false,
                    validateOnBlur: false,
                    selectOnFocus: true,
                    value: this.defaultUsername ? this.defaultUsername : undefined,
                    listeners: {
                        render: function (field) {
                            field.focus(false, 250);
                        }
                    }
                }, {
                    xtype: 'textfield',
                    tabindex: 3,
                    width: 170,
                    inputType: 'password',
                    fieldLabel: _('Password'),
                    id: 'password',
                    name: 'password',
                    //allowBlank: false,
                    selectOnFocus: true,
                    value: this.defaultPassword
                }],
                buttonAlign: 'right',
                buttons: [{
                    xtype: 'button',
                    width: 120,
                    text: _('Login'),
                    scope: this,
                    handler: this.onLoginPress
                }]
            });
        }
        
        return this.loginPanel;
    },
    
    getTinePanel: function () {
        if (! this.tinePanel) {
            this.tinePanel = new Ext.Container({
                layout: 'fit',
                cls: 'tb-login-tinepanel',
                border: false,
                defaults: {xtype: 'label'},
                items: [{
                    cls: 'tb-login-big-label',
                    html: _('Tine 2.0 is made for you')
                }, {
                    html: '<p>' + _('Tine 2.0 wants to make business collaboration easier and more enjoyable - for your needs! So you are warmly welcome to discuss with us, bring in ideas and get help.') + '</p>'
                }, {
                    cls: 'tb-login-big-label-spacer',
                    html: '&nbsp;'
                }, {
                    html: '<ul>' + 
                        '<li><a target="_blank" href="' + Tine.weburl + '" border="0">' + _('Tine 2.0 Homepage') + '</a></li>' +
                        '<li><a target="_blank" href="' + Tine.weburl + 'forum/" border="0">' + _('Tine 2.0 Forum') + '</a></li>' +
                    '</ul><br/>'
                }, {
                    cls: 'tb-login-big-label',
                    html: _('Translations')
                }, {
                    html: '<p>' + _('If you miss a language, or your language is not supported completely, you can help our translation teams at transifex.') + '</p>'
                }, {
                    html: '<br/><ul>' +
                        '<li><a target="_blank" href="https://www.transifex.com/projects/p/tine20/" border="0">' + _('Tine 2.0 Translation Portal') + '</a></li>'
                    + '</ul>'
                }]
            });
        }
        
        return this.tinePanel;
    },
    
    getSurveyData: function (cb) {
        var ds = new Ext.data.Store({
            proxy: new Ext.data.ScriptTagProxy({
                url: 'https://versioncheck.officespot20.com/surveyCheck/surveyCheck.php'
            }),
            reader: new Ext.data.JsonReader({
                root: 'survey'
            }, ['title', 'subtitle', 'duration', 'langs', 'link', 'enddate', 'htmlmessage', 'version'])
        });
        
        ds.on('load', function (store, records) {
            var survey = records[0];
            
            cb.call(this, survey);
        }, this);
        ds.load({params: {lang: Tine.Tinebase.registry.get('locale').locale}});
    },
    
    getSurveyPanel: function () {
        if (! this.surveyPanel) {
            this.surveyPanel = new Ext.Container({
                layout: 'fit',
                cls: 'tb-login-surveypanel',
                border: false,
                defaults: {xtype: 'label'},
                items: []
            });
            
            if (! Tine.Tinebase.registry.get('denySurveys')) {
                this.getSurveyData(function (survey) {
                    if (typeof survey.get === 'function') {
                        var enddate = Date.parseDate(survey.get('enddate'), Date.patterns.ISO8601Long);
                        var version = survey.get('version');
                        
                        if (Ext.isDate(enddate) && enddate.getTime() > new Date().getTime() && 
                            Tine.clientVersion.packageString.indexOf(version) === 0) {
                            survey.data.lang_duration = String.format(_('about {0} minutes'), survey.data.duration);
                            survey.data.link = 'https://versioncheck.officespot20.com/surveyCheck/surveyCheck.php?participate';
                            
                            this.surveyPanel.add([{
                                cls: 'tb-login-big-label',
                                html: _('Tine 2.0 needs your help')
                            }, {
                                html: '<p>' + _('We regularly need your feedback to make the next Tine 2.0 releases fit your needs even better. Help us and yourself by participating:') + '</p>'
                            }, {
                                html: this.getSurveyTemplate().apply(survey.data)
                            }, {
                                xtype: 'button',
                                width: 120,
                                text: _('participate!'),
                                handler: function () {
                                    window.open(survey.data.link);
                                }
                            }]);
                            this.surveyPanel.doLayout();
                        }
                    }
                });
            }
        }
        
        return this.surveyPanel;
    },
    
    getSurveyTemplate: function () {
        if (! this.surveyTemplate) {
            this.surveyTemplate = new Ext.XTemplate(
                '<br/ >',
                '<p><b>{title}</b></p>',
                '<p><a target="_blank" href="{link}" border="0">{subtitle}</a></p>',
                '<br/>',
                '<p>', _('Languages'), ': {langs}</p>',
                '<p>', _('Duration'), ': {lang_duration}</p>',
                '<br/>').compile();
        }
        
        return this.surveyTemplate;
    },
    
    /**
     * checks browser compatibility and show messages if unknown/incompatible
     * 
     * ie6, gecko2 -> bad
     * unknown browser -> may not work
     * 
     * @return {Ext.Container}
     * 
     * TODO find icons with the correct license
     */
    getBrowserIncompatiblePanel: function() {
        if (! this.browserIncompatiblePanel) {
            this.browserIncompatiblePanel = new Ext.Container({
                layout: 'fit',
                cls: 'tb-login-surveypanel',
                border: false,
                defaults: {xtype: 'label'},
                items: []
            });
            
            var browserSupport = 'compatible';
            if (Ext.isIE6 || Ext.isGecko2) {
                browserSupport = 'incompatible';
            } else if (
                ! (Ext.isWebKit || Ext.isGecko || Ext.isIE)
            ) {
                // yepp we also mean -> Ext.isOpera
                browserSupport = 'unknown';
            }
            
            var items = [];
            if (browserSupport == 'incompatible') {
                items = [{
                    cls: 'tb-login-big-label',
                    html: _('Browser incompatible')
                }, {
                    html: '<p>' + _('Your browser is not supported by Tine 2.0.') + '<br/><br/></p>'
                }];
            } else if (browserSupport == 'unknown') {
                items = [{
                    cls: 'tb-login-big-label',
                    html: _('Browser incompatible?')
                }, {
                    html: '<p>' + _('You are using an unrecognized browser. This could result in unexpected behaviour.') + '<br/><br/></p>'
                }];
            }
            
            if (browserSupport != 'compatible') {
                this.browserIncompatiblePanel.add(items.concat([{
                    html: '<p>' + _('You might try one of these browsers:') + '<br/>'
                        + '<a href="http://www.google.com/chrome" target="_blank">Google Chrome</a><br/>'
                        + '<a href="http://www.mozilla.com/firefox/" target="_blank">Mozilla Firefox</a><br/>'
                        + '<a href="http://www.apple.com/safari/download/" target="_blank">Apple Safari</a><br/>'    
                        + '<a href="http://www.microsoft.com/windows/internet-explorer/default.aspx" target="_blank">Microsoft Internet Explorer</a>'
                        + '<br/></p>'
                }]));
                this.browserIncompatiblePanel.doLayout();
            }
        }
        
        return this.browserIncompatiblePanel;
    },
    
    initComponent: function () {
        this.initLayout();
        
        this.supr().initComponent.call(this);
    },
    
    initLayout: function () {
        var infoPanelItems = (this.showInfoBox) ? [
            this.getBrowserIncompatiblePanel(),
            this.getTinePanel(),
            this.getSurveyPanel()
        ] : [];
        
        this.infoPanel = new Ext.Container({
            cls: 'tb-login-infosection',
            border: false,
            width: 300,
            height: 460,
            layout: 'vbox',
            layoutConfig: {
                align: 'stretch'
            },
            items: infoPanelItems
        });
        
        this.items = [{
            xtype: 'container',
            layout: 'absolute',
            border: false,
            items: [
                this.getLoginPanel(),
                this.infoPanel
            ]
        }];
    },
    
    /**
     * do the actual login
     */
    onLoginPress: function () {
        var form = this.getLoginPanel().getForm(),
            values = form.getValues();
            
        if (form.isValid()) {
            Ext.MessageBox.wait(_('Logging you in...'), _('Please wait'));
            
            Ext.Ajax.request({
                scope: this,
                params : {
                    method: this.loginMethod,
                    username: values.username,
                    password: values.password
                },
                timeout: 60000, // 1 minute
                callback: function (request, httpStatus, response) {
                    var responseData = Ext.util.JSON.decode(response.responseText);
                    if (responseData.success === true) {
                        Ext.MessageBox.wait(String.format(_('Login successful. Loading {0}...'), Tine.title), _('Please wait!'));
                        window.document.title = this.originalTitle;
                        this.onLogin.call(this.scope);
                    } else {
                        if (responseData.data && responseData.data.code === 510) {
                            // NOTE: when communication is lost, we can't create a nice ext window.
                            (function() {
                                Ext.MessageBox.hide();
                                alert(_('Connection lost, please check your network!'));
                            }).defer(1000);
                        } else {
                            Ext.MessageBox.show({
                                title: _('Login failure'),
                                msg: _('Your username and/or your password are wrong!!!'),
                                buttons: Ext.MessageBox.OK,
                                icon: Ext.MessageBox.ERROR,
                                fn: function () {
                                    this.getLoginPanel().getForm().findField('password').focus(true);
                                }.createDelegate(this)
                            });
                        }
                    }
                }
            });
        } else {
            Ext.MessageBox.alert(_('Errors'), _('Please fix the errors noted.'));
        }
    },
    
    onRender: function (ct, position) {
        this.supr().onRender.apply(this, arguments);
        
        this.map = new Ext.KeyMap(this.el, [{
            key : [10, 13],
            scope : this,
            fn : this.onLoginPress
        }]);
        
        this.originalTitle = window.document.title;
        var postfix = (Tine.Tinebase.registry.get('titlePostfix')) ? Tine.Tinebase.registry.get('titlePostfix') : '';
        window.document.title = Tine.title + postfix + ' - ' + _('Please enter your login data');
    },
    
    onResize: function () {
        this.supr().onResize.apply(this, arguments);

        var box      = this.getBox(),
            loginBox = this.getLoginPanel().rendered ? this.getLoginPanel().getBox() : {width : this.getLoginPanel().width, height: this.getLoginPanel().height},
            infoBox  = this.infoPanel.rendered ? this.infoPanel.getBox() : {width : this.infoPanel.width, height: this.infoPanel.height};

        var top = (box.height - loginBox.height) / 2;
        if (box.height - top < infoBox.height) {
            top = box.height - infoBox.height;
        }
        
        var loginLeft = (box.width - loginBox.width) / 2;
        if (loginLeft + loginBox.width + infoBox.width > box.width) {
            loginLeft = box.width - loginBox.width - infoBox.width;
        }
                
        this.getLoginPanel().setPosition(loginLeft, top);
        this.infoPanel.setPosition(loginLeft + loginBox.width, top);
    },
    
    renderSurveyPanel: function (survey) {
        var items = [{
            cls: 'tb-login-big-label',
            html: _('Tine 2.0 needs your help')
        }, {
            html: '<p>' + _('We regularly need your feedback to make the next Tine 2.0 releases fit your needs even better. Help us and yourself by participating:') + '</p>'
        }];
    }
});
/*
 * Tine 2.0
 * 
 * @package     Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
 
/*global Ext, Tine, Locale*/
 
Ext.ns('Tine.Tinebase');

/**
 * admin settings panel
 * 
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.AdminPanel
 * @extends     Ext.TabPanel
 * 
 * <p>Tinebase Admin Panel</p>
 * <p><pre>
 * </pre></p>
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 * 
 * @param       {Object} config
 * @constructor
 * Create a new Tine.Tinebase.AdminPanel
 * @TODO        Invent some kind of registry for inner panels
 */
Tine.Tinebase.AdminPanel = Ext.extend(Ext.TabPanel, {
    
    activeItem: 0,
    border: false,
    
    /**
     * @private
     */
    initComponent: function () {
        this.items = new Tine.Tinebase.Admin.UserProfileConfigPanel({});
        
        Tine.Tinebase.AdminPanel.superclass.initComponent.call(this);
    }
});

/**
 * Tinebase Admin Panel Popup
 * 
 * @param   {Object} config
 * @return  {Ext.ux.Window}
 */
Tine.Tinebase.AdminPanel.openWindow = function (config) {
    var window = Tine.WindowFactory.getWindow({
        width: 500,
        height: 470,
        name: 'tinebase-admin-panel',
        contentPanelConstructor: 'Tine.Tinebase.AdminPanel',
        contentPanelConstructorConfig: config
    });
};

Ext.ns('Tine.Tinebase.Admin');
Tine.Tinebase.Admin.UserProfileConfigPanel = Ext.extend(Ext.Panel, { // TODO: extend some kind of AppAdminPanel

    layout: 'fit',
    border: false,
    
    /**
     * @private
     */
    initComponent: function () {
        this.title = _('Profile Information');
        this.items = [];
        
        this.applyAction = new Ext.Action({
            text: _('Apply'),
            disabled: true,
            iconCls: 'action_applyChanges',
            handler: this.applyConfig.createDelegate(this)
        });
        this.buttons = [this.applyAction];
        
        Tine.Tinebase.getUserProfileConfig(this.initProfileTable, this);
        
        this.supr().initComponent.call(this);
    },
    
    afterRender: function () {
        this.supr().afterRender.apply(this, arguments);
        
        this.loadMask = new Ext.LoadMask(this.getEl(), {msg: _('Please Wait')});
        if (! this.store) {
            (function () {
                this.loadMask.show();
            }).defer(50, this);
        }
    },
    
    applyConfig: function () {
        var userProfileConfig = {
            readableFields: [],
            updateableFields: []
        };
        
        this.store.each(function (field) {
            var fieldName = field.get('fieldName');
            
            if (field.get('readGrant')) {
                userProfileConfig.readableFields.push(fieldName);
            }
            if (field.get('editGrant')) {
                userProfileConfig.updateableFields.push(fieldName);
            }
        }, this);
        
        this.loadMask.show();
        Tine.Tinebase.setUserProfileConfig(userProfileConfig, function () {
            this.store.commitChanges();
            this.applyAction.setDisabled(true);
            this.loadMask.hide();
        }, this);
        
    },
    
    initProfileTable: function (userProfileConfig) {
        var adbI18n = new Locale.Gettext();
        adbI18n.textdomain('Addressbook');
        
        var fieldData = [];
        
        Ext.each(userProfileConfig.possibleFields, function (fieldName) {
            var fieldDefinition = Tine.Addressbook.Model.Contact.getField(fieldName);
            fieldData.push([
                fieldName,
                adbI18n._hidden(fieldDefinition.label),
                userProfileConfig.readableFields.indexOf(fieldName) >= 0,
                userProfileConfig.updateableFields.indexOf(fieldName) >= 0
            ]);
        }, this);
        
        this.store = new Ext.data.ArrayStore({
            autoDestroy: true,
            fields: ['fieldName', 'fieldLabel', 'readGrant', 'editGrant'],
            data: fieldData,
            listeners: {
                scope: this,
                update: this.onStoreUpdate
            }
        });
        
        var cbs = [
            new Ext.ux.grid.CheckColumn({
                header: _('Read'),
                tooltip: _('The field is readable part of the profile'),
                dataIndex: 'readGrant',
                width: 55
            }), new Ext.ux.grid.CheckColumn({
                header: _('Edit'),
                tooltip: _('The field is editable part of the profile'),
                dataIndex: 'editGrant',
                width: 55
            })
        ];
        
        this.userProfileConfigGrid = new Ext.grid.EditorGridPanel({
            layout: 'fit',
            store: this.store,
            autoExpandColumn: 'fieldName',
            plugins: cbs,
            columns: [{
                id: 'fieldName',
                header: _('Field Name'),
                dataIndex: 'fieldLabel'
            }].concat(cbs)
        });
        
        this.add(this.userProfileConfigGrid);
        this.doLayout();
        
        if (this.loadMask) {
            this.loadMask.hide();
        }
    },
    
    onStoreUpdate: function () {
        this.applyAction.setDisabled(false);
    }
});
/*
 * Tine 2.0
 * 
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */
Ext.ns('Tine.Tinebase');

/**
 * user profile panel
 * 
 * @namespace   Tine.Tinebase
 * @class       Tine.Tinebase.UserProfilePanel
 * @extends     Ext.form.FormPanel
 * @consturctor
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Tinebase.UserProfilePanel = Ext.extend(Ext.Panel, {
    genericFields: ['account_id', 'created_by', 'creation_time', 'last_modified_by',
                      'last_modified_time', 'is_deleted', 'deleted_time', 'deleted_by'],
    layout: 'form',
    border: true,
    labelAlign: 'top',
    autoScroll: true,
    defaults: {
        anchor: '95%',
        labelSeparator: ''
    },
    bodyStyle: 'padding:5px',
    
    initComponent: function() {
        Tine.Tinebase.getUserProfile(Tine.Tinebase.registry.get('currentAccount').accountId, this.onData.createDelegate(this));
        this.items = [];
        
        this.supr().initComponent.apply(this, arguments);
    },
    
    onData: function(userProfileInfo) {
        var userProfile      = userProfileInfo.userProfile,
            readableFields   = userProfileInfo.readableFields,
            updateableFields = userProfileInfo.updateableFields;
            
        var adbI18n = new Locale.Gettext();
        adbI18n.textdomain('Addressbook');
        
        Ext.each(readableFields, function(fieldName) {
            // don't display generic fields
            var fieldDefinition = Tine.Addressbook.Model.Contact.getField(fieldName);
            
            switch(fieldName) {
                default: 
                    this.add(new Ext.form.TextField({
                        hidden: this.genericFields.indexOf(fieldName) >= 0,
                        name: fieldName, 
                        value: userProfile[fieldName],
                        fieldLabel: adbI18n._hidden(fieldDefinition.label),
                        readOnly: updateableFields.indexOf(fieldName) < 0
                    }));
                    break;
            }
        }, this);
        
        this.doLayout();
    }
});
/*
 * Tine 2.0
 * 
 * translations for js prototypes
 * 
 * @package     Tine
 * @subpackage  Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Philipp Schuele <p.schuele@metaways.de>
 * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
 */

Ext.namespace('Tine.Tinebase');

/**
 * this is called in tineInit::initLocale() to make sure we translate some plugin strings by overwriting prototype values
 */
Tine.Tinebase.prototypeTranslation = function() {
    // html editor plugin translations
    Ext.ux.form.HtmlEditor.IndentOutdent.prototype.midasBtns[1].tooltip.title = _('Outdent Text');
    Ext.ux.form.HtmlEditor.IndentOutdent.prototype.midasBtns[1].overflowText = _('Outdent Text');
    Ext.ux.form.HtmlEditor.IndentOutdent.prototype.midasBtns[2].tooltip.title = _('Indent Text');
    Ext.ux.form.HtmlEditor.IndentOutdent.prototype.midasBtns[2].overflowText = _('Indent Text');
    Ext.ux.form.HtmlEditor.RemoveFormat.prototype.midasBtns[1].tooltip.title = _('Remove Formatting');
    Ext.ux.form.HtmlEditor.RemoveFormat.prototype.midasBtns[1].overflowText = _('Remove Formatting');
}
/*
 * Tine 2.0
 * 
 * @package     Tine
 * @subpackage  Tinebase
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 * TODO         allow to add user defined part to Tine.title
 * TODO         move locale/timezone registry values to preferences MixedCollection?
 */

/*global Ext, Tine, google, OpenLayers, Locale, */

/** ------------------------- Ext.ux Initialisation ------------------------ **/

Ext.ux.Printer.BaseRenderer.prototype.stylesheetPath = 'Tinebase/js/ux/Printer/print.css';


/** ------------------------ Tine 2.0 Initialisation ----------------------- **/

/**
 * @class Tine
 * @singleton
 */
Ext.namespace('Tine', 'Tine.Tinebase', 'Tine.Calendar');

/**
 * version of Tine 2.0 javascript client version, gets set a build / release time <br>
 * <b>Supported Properties:</b>
 * <table>
 *   <tr><td><b>buildType</b></td><td> type of build</td></tr>
 *   <tr><td><b>buildDate</b></td><td> date of build</td></tr>
 *   <tr><td><b>buildRevision</b></td><td> revision of build</td></tr>
 *   <tr><td><b>codeName</b></td><td> codename of release</td></tr>
 *   <tr><td><b>packageString</b></td><td> packageString of release</td></tr>
 *   <tr><td><b>releaseTime</b></td><td> releaseTime of release</td></tr>
 * </table>
 * @type {Object}
 */
Tine.clientVersion = {};
Tine.clientVersion.buildType = 'DEBUG';
Tine.clientVersion.buildDate="2013-02-21 16:37:03";
Tine.clientVersion.codeName=": 0 ()";
Tine.clientVersion.codeName = 'Joey';
Tine.clientVersion.packageString = '2012.10.4';
Tine.clientVersion.releaseTime = '2013-02-21 16:36:24+01:00';

/**
 * title of app (gets set at build time)
 * 
 * @type String
 */
Tine.title = 'Tine 2.0';
Tine.weburl = 'http://www.tine20.com/';

/**
 * quiet logging in release mode
 */
Ext.LOGLEVEL = Tine.clientVersion.buildType = 'DEBUG';
Tine.log = Ext.ux.log;

Ext.namespace('Tine.Tinebase');

/**
 * @class Tine.Tinebase.tineInit
 * @namespace Tine.Tinebase
 * @sigleton
 * static tine init functions
 */
Tine.Tinebase.tineInit = {
    /**
     * @cfg {String} getAllRegistryDataMethod
     */
    getAllRegistryDataMethod: 'Tinebase.getAllRegistryData',

    /**
     * @cfg {Boolean} stateful
     */
    stateful: true,

    /**
     * @cfg {String} requestUrl
     */
    requestUrl: 'index.php',
    
    /**
     * list of initialised items
     */
    initList: {
        initWindow:   false,
        initViewport: false,
        initRegistry: false
    },
    
    initWindow: function () {
        Ext.getBody().on('keydown', function (e) {
            if (e.ctrlKey && e.getKey() === e.A && ! (e.getTarget('form') || e.getTarget('input') || e.getTarget('textarea'))) {
                // disable the native 'select all'
                e.preventDefault();
            } else if (e.getKey() === e.BACKSPACE && ! (e.getTarget('form') || e.getTarget('input') || e.getTarget('textarea'))) {
                // disable the native 'history back'
                e.preventDefault();
            } else if (!window.isMainWindow && e.ctrlKey && e.getKey() === e.T) {
                // disable the native 'new tab' if in popup window
                e.preventDefault();
            }
        });
        
        // disable generic drops
        Ext.getBody().on('dragover', function (e) {
            e.stopPropagation();
            e.preventDefault();
            e.browserEvent.dataTransfer.dropEffect = 'none';
        }, this);
        
        //init window is done in Ext.ux.PopupWindowMgr. yet
        this.initList.initWindow = true;
    },
    
    initDebugConsole: function () {
        var map = new Ext.KeyMap(Ext.getDoc(), [{
            key: [122], // F11
            ctrl: true,
            fn: Tine.Tinebase.common.showDebugConsole
        }]);
    },
    
    
    /**
     * Each window has exactly one viewport containing a card layout in its lifetime
     * The default card is a splash screen.
     * 
     * defautl wait panel (picture only no string!)
     */
    initBootSplash: function () {
        
        this.splash = {
            xtype: 'container',
            id: 'tine-viewport-waitcycle',
            border: false,
            layout: 'fit',
            width: 16,
            height: 16,
            // the content elements come from the initial html so they are displayed fastly
            contentEl: Ext.select('div[class^=tine-viewport-]')
        };
        
        Tine.Tinebase.viewport = new Ext.Viewport({
            layout: 'fit',
            border: false,
            items: {
                xtype: 'container',
                id: 'tine-viewport-maincardpanel',
                ref: 'tineViewportMaincardpanel',
                isWindowMainCardPanel: true,
                layout: 'card',
                border: false,
                activeItem: 0,
                items: this.splash
            },
            listeners: {
                scope: this,
                render: function (p) {
                    this.initList.initViewport = true;
                }
            }
        });
    },
    
    renderWindow: function () {
        var mainCardPanel = Tine.Tinebase.viewport.tineViewportMaincardpanel;
        
        // check if user is already logged in        
        if (! Tine.Tinebase.registry.get('currentAccount')) {
            if (! Tine.loginPanel) {
                Tine.loginPanel = new Tine.Tinebase.LoginPanel({
                    defaultUsername: Tine.Tinebase.registry.get('defaultUsername'),
                    defaultPassword: Tine.Tinebase.registry.get('defaultPassword'),
                    scope: this,
                    onLogin: function (response) {
                        Tine.Tinebase.tineInit.initList.initRegistry = false;
                        Tine.Tinebase.tineInit.initRegistry();
                        var waitForRegistry = function () {
                            if (Tine.Tinebase.tineInit.initList.initRegistry) {
                                Ext.MessageBox.hide();
                                Tine.Tinebase.tineInit.initExtDirect();
                                Tine.Tinebase.tineInit.initState();
                                Tine.Tinebase.tineInit.renderWindow();
                            } else {
                                waitForRegistry.defer(100);
                            }
                        };
                        waitForRegistry();
                    }
                });
                mainCardPanel.add(Tine.loginPanel);
            }
            mainCardPanel.layout.setActiveItem(Tine.loginPanel.id);
            Tine.loginPanel.doLayout();
            
            return;
        }
        
        // todo: find a better place for stuff to do after successfull login
        Tine.Tinebase.tineInit.initAppMgr();
        
        Tine.Tinebase.tineInit.initUploadMgr();
        
        /** temporary Tine.onReady for smooth transition to new window handling **/
        if (typeof(Tine.onReady) === 'function') {
            Tine.Tinebase.viewport.destroy();
            Tine.onReady();
            return;
        }
        
        // fetch window config from WindowMgr
        var c = Ext.ux.PopupWindowMgr.get(window) || {};
        
        // set window title
        window.document.title = c.title ? c.title : window.document.title;
        
        // finally render the window contents in a new card  
        var card = Tine.WindowFactory.getCenterPanel(c);
        mainCardPanel.add(card);
        mainCardPanel.layout.setActiveItem(card.id);
        card.doLayout();
        
        //var ping = new Tine.Tinebase.sync.Ping({});
        
        window.initializationComplete = true;
    },

    initAjax: function () {
        Ext.Ajax.url = Tine.Tinebase.tineInit.requestUrl;
        Ext.Ajax.method = 'POST';
        
        Ext.Ajax.defaultHeaders = {
            'X-Tine20-Request-Type' : 'JSON'
        };
        
        Ext.Ajax.transactions = {};
        
        /**
         * inspect all requests done via the ajax singleton
         * 
         * - send custom headers
         * - send json key 
         * - implicitly transform non jsonrpc requests
         * 
         * NOTE: implicitly transformed reqeusts get their callback fn's proxied 
         *       through generic response inspectors as defined below
         */
        Ext.Ajax.on('beforerequest', function (connection, options) {
            options.headers = options.headers || {};
            options.headers['X-Tine20-JsonKey'] = Tine.Tinebase.registry && Tine.Tinebase.registry.get ? Tine.Tinebase.registry.get('jsonKey') : '';
            options.headers['X-Tine20-TransactionId'] = Tine.Tinebase.data.Record.generateUID();
            
            options.url = Ext.urlAppend((options.url ? options.url : Tine.Tinebase.tineInit.requestUrl),  'transactionid=' + options.headers['X-Tine20-TransactionId']);
            
            // convert non Ext.Direct request to jsonrpc
            // - convert params
            // - convert error handling
            if (options.params && !options.isUpload) {
                var params = {};
                
                var def = Tine.Tinebase.registry.get ? Tine.Tinebase.registry.get('serviceMap').services[options.params.method] : false;
                if (def) {
                    // sort parms according to def
                    for (var i = 0, p; i < def.parameters.length; i += 1) {
                        p = def.parameters[i].name;
                        params[p] = options.params[p];
                    }
                } else {
                    for (var param in options.params) {
                        if (options.params.hasOwnProperty(param) && param !== 'method') {
                            params[param] = options.params[param];
                        }
                    }
                }
                
                options.jsonData = Ext.encode({
                    jsonrpc: '2.0',
                    method: options.params.method,
                    params: params,
                    id: ++Ext.Direct.TID
                });
                
                options.cbs = {};
                options.cbs.success  = options.success  || null;
                options.cbs.failure  = options.failure  || null;
                options.cbs.callback = options.callback || null;
                
                options.isImplicitJsonRpc = true;
                delete options.params;
                delete options.success;
                delete options.failure;
                delete options.callback;
            }
            
            Ext.Ajax.transactions[options.headers['X-Tine20-TransactionId']] = {
                date: new Date(),
                json: options.jsonData
            };
        });
        
        /**
         * inspect completed responses => staus code == 200
         * 
         * - detect resoponse errors (e.g. html from xdebug) and convert to exceptional states
         * - implicitly transform requests from JSONRPC
         * 
         *  NOTE: All programatically catchable exceptions lead to successfull requests
         *        with the jsonprc protocol. For implicitly converted jsonprc requests we 
         *        transform error states here and route them to the error methods defined 
         *        in the request options
         *        
         *  NOTE: Illegal json data responses are mapped to error code 530
         *        Empty resonses (Ext.Decode can't deal with them) are maped to 540
         *        Memory exhausted to 550
         */
        Ext.Ajax.on('requestcomplete', function (connection, response, options) {
            delete Ext.Ajax.transactions[options.headers['X-Tine20-TransactionId']];
            
            // detect resoponse errors (e.g. html from xdebug) and convert into error response
            if (! options.isUpload && ! response.responseText.match(/^([{\[])|(<\?xml)+/)) {
                var exception = {
                    code: response.responseText !== "" ? 530 : 540,
                    message: response.responseText !== "" ? 'illegal json data in response' : 'empty response',
                    traceHTML: response.responseText,
                    request: options.jsonData,
                    response: response.responseText
                };
                
                // Fatal error: Allowed memory size of n bytes exhausted (tried to allocate m bytes) 
                if (response.responseText.match(/^Fatal error: Allowed memory size of /m)) {
                    Ext.apply(exception, {
                        code: 550,
                        message: response.responseText
                    });
                }
                
                // encapsulate as jsonrpc response
                var requestOptions = Ext.decode(options.jsonData);
                response.responseText = Ext.encode({
                    jsonrpc: requestOptions.jsonrpc,
                    id: requestOptions.id,
                    error: {
                        code: -32000,
                        message: exception.message,
                        data: exception
                    }
                });
            }
            
            // strip jsonrpc fragments for non Ext.Direct requests
            if (options.isImplicitJsonRpc) {
                var jsonrpc = Ext.decode(response.responseText);
                if (jsonrpc.result) {
                    response.responseText = Ext.encode(jsonrpc.result);
                    
                    if (options.cbs.success) {
                        options.cbs.success.call(options.scope, response, options);
                    }
                    if (options.cbs.callback) {
                        options.cbs.callback.call(options.scope, options, true, response);
                    }
                } else {
                    
                    response.responseText = Ext.encode(jsonrpc.error);
                    
                    if (options.cbs.failure) {
                        options.cbs.failure.call(options.scope, response, options);
                    } else if (options.cbs.callback) {
                        options.cbs.callback.call(options.scope, options, false, response);
                    } else {
                        var responseData = Ext.decode(response.responseText);
                            
                        exception = responseData.data ? responseData.data : responseData;
                        exception.request = options.jsonData;
                        exception.response = response.responseText;
                        
                        Tine.Tinebase.ExceptionHandler.handleRequestException(exception);
                    }
                }
            }
        });
        
        /**
         * inspect request exceptions
         *  - convert to jsonrpc compatiple exceptional states
         *  - call generic exception handler if no handler is defined in request options
         *  
         * NOTE: Request exceptions are exceptional state from web-server:
         *       -> status codes != 200 : This kind of exceptions are not part of the jsonrpc protocol
         *       -> timeouts: status code 520
         */
        Ext.Ajax.on('requestexception', function (connection, response, options) {
            delete Ext.Ajax.transactions[options.headers['X-Tine20-TransactionId']];
            // map connection errors to errorcode 510 and timeouts to 520
            var errorCode = response.status > 0 ? response.status :
                            (response.status === 0 ? 510 : 520);
                            
            // convert into error response
            if (! options.isUpload) {
                var exception = {
                    code: errorCode,
                    message: 'request exception: ' + response.statusText,
                    traceHTML: response.responseText,
                    request: options.jsonData,
                    requestHeaders: options.headers,
                    openTransactions: Ext.Ajax.transactions,
                    response: response.responseText
                };
                
                // encapsulate as jsonrpc response
                var requestOptions = Ext.decode(options.jsonData);
                response.responseText = Ext.encode({
                    jsonrpc: requestOptions.jsonrpc,
                    id: requestOptions.id,
                    error: {
                        code: -32000,
                        message: exception.message,
                        data: exception
                    }
                });
            }
            
            // NOTE: Tine.data.RecordProxy is implicitRPC atm.
            if (options.isImplicitJsonRpc) {
                var jsonrpc = Ext.decode(response.responseText);
                
                response.responseText = Ext.encode(jsonrpc.error);
                    
                if (options.cbs.failure) {
                    options.cbs.failure.call(options.scope, response, options);
                } else if (options.cbs.callback) {
                    options.cbs.callback.call(options.scope, options, false, response);
                } else {
                    var responseData = Ext.decode(response.responseText);
                    
                    exception = responseData.data ? responseData.data : responseData;
                    
                    Tine.Tinebase.ExceptionHandler.handleRequestException(exception);
                }
                
            } else if (! options.failure && ! options.callback) {
                Tine.Tinebase.ExceptionHandler.handleRequestException(exception);
            }
        });
    },
    
    /**
     * init registry
     */
    initRegistry: function () {
        Ext.namespace('Tine.Tinebase.registry');
        if (window.isMainWindow) {
            Ext.Ajax.request({
                timeout: 120000, // 2 minutes
                params: {
                    method: Tine.Tinebase.tineInit.getAllRegistryDataMethod
                },
                failure: function () {
                    // if registry could not be loaded, this is mostly due to missconfiguaration
                    // don't send error reports for that!
                    Tine.Tinebase.ExceptionHandler.handleRequestException({
                        code: 503
                    });
                },
                success: function (response, request) {
                    var registryData = Ext.util.JSON.decode(response.responseText);
                    for (var app in registryData) {
                        if (registryData.hasOwnProperty(app)) {
                            var appData = registryData[app];
                            if (Tine[app]) {
                                Tine[app].registry = new Ext.util.MixedCollection();

                                for (var key in appData) {
                                    if (appData.hasOwnProperty(key)) {
                                        if (key === 'preferences') {
                                            var prefs = new Ext.util.MixedCollection();
                                            for (var pref in appData[key]) {
                                                if (appData[key].hasOwnProperty(pref)) {
                                                    prefs.add(pref, appData[key][pref]);
                                                }
                                            }
                                            prefs.on('replace', Tine.Tinebase.tineInit.onPreferenceChange);
                                            Tine[app].registry.add(key, prefs);
                                        } else {
                                            Tine[app].registry.add(key, appData[key]);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    // update window factory window type (required after login)
                    if (Tine.Tinebase.registry && Tine.Tinebase.registry.get('preferences')) {
                        var windowType = Tine.Tinebase.registry.get('preferences').get('windowtype');
                        // init ApplicationStarter on Ext window once
                        if(windowType == 'Ext') {
                            Tine.Tinebase.ApplicationStarter.init();
                        }
                        if (Tine.WindowFactory && Tine.WindowFactory.windowType !== windowType) {
                            Tine.WindowFactory.windowType = windowType;
                        }
                    }
                    
                    Tine.Tinebase.tineInit.overrideFields();
                    
                    Tine.Tinebase.tineInit.initList.initRegistry = true;
                }
            });
        } else {
            var mainWindow = Ext.ux.PopupWindowMgr.getMainWindow();
            
            for (var p in mainWindow.Tine) {
                if (Object.prototype.hasOwnProperty.call(mainWindow.Tine[p], 'registry') && Tine.hasOwnProperty(p)) {
                    Tine[p].registry = mainWindow.Tine[p].registry;
                }
            }
            
            Tine.Tinebase.tineInit.overrideFields();
            
            Tine.Tinebase.tineInit.initList.initRegistry = true;
        }
    },
    
    /**
     * Applying registry entries to Tine classes
     */
    overrideFields: function () {
        
        Ext.override(Ext.ux.file.Upload, {
            maxFileUploadSize: Tine.Tinebase.registry.get('maxFileUploadSize'),
            maxPostSize: Tine.Tinebase.registry.get('maxPostSize')
        });
                
    },
    
    /**
     * executed when a value in Tinebase registry/preferences changed
     * @param {string} key
     * @param {value} oldValue
     * @param {value} newValue
     */
    onPreferenceChange: function (key, oldValue, newValue) {
        switch (key) {
            case 'windowtype':
            case 'confirmLogout':
            case 'timezone':
            case 'locale':
                // TODO do we still need the gears stuff here?
                if (window.google && google.gears && google.gears.localServer) {
                    var pkgStore = google.gears.localServer.openStore('tine20-package-store');
                    if (pkgStore) {
                        google.gears.localServer.removeStore('tine20-package-store');
                    }
                }
                // reload mainscreen
                window.location.reload(key == 'locale');
                break;
        }
    },
    
    /**
     * check if selfupdate is needed
     */
    checkSelfUpdate: function () {
        if (! Tine.Tinebase.registry.get('version')) {
            return false;
        }        
        
        var needSelfUpdate, serverVersion = Tine.Tinebase.registry.get('version'), clientVersion = Tine.clientVersion;
        if (clientVersion.codeName.match(/^\$HeadURL/)) {
            return;
        }
        
        var cp = new Ext.state.CookieProvider({});
        
        if (serverVersion.packageString !== 'none') {
            needSelfUpdate = (serverVersion.packageString !== clientVersion.packageString);
        } else {
            needSelfUpdate = (serverVersion.codeName !== clientVersion.codeName);
        }
        
        if (needSelfUpdate) {
            if (window.google && google.gears && google.gears.localServer) {
                google.gears.localServer.removeManagedStore('tine20-store');
                google.gears.localServer.removeStore('tine20-package-store');
            }
            if (cp.get('clientreload', '0') === '0') {
                
                cp.set('clientreload', '1');
                window.location.reload(true);
                return;
                
            } else {
                cp.clear('clientreload');
                var loadMask = new Ext.LoadMask(Ext.getBody(), {
                    msg: _('Fatal Error: Client self-update failed, please contact your administrator and/or restart/reload your browser.'),
                    msgCls: ''
                }).show();
            }
        } else {
            cp.clear('clientreload');
            
            // if no selfupdate is needed we store langfile and index.php in manifest
            if (window.google && google.gears && google.gears.localServer) {
                if (serverVersion.buildType === 'RELEASE') {
                    var pkgStore = google.gears.localServer.createStore('tine20-package-store');
                    var resources = [
                        '',
                        'index.php',
                        'Tinebase/js/Locale/build/' + Tine.Tinebase.registry.get('locale').locale + '-all.js'
                    ];
                    
                    Ext.each(resources, function (resource) {
                        if (! pkgStore.isCaptured(resource)) {
                            pkgStore.capture(resources, function () {/*console.log(arguments)*/});
                        }
                    }, this);
                } else {
                    google.gears.localServer.removeStore('tine20-package-store');
                }
            }
        }
    },
    
    /**
     * initialise window and windowMgr (only popup atm.)
     */
    initWindowMgr: function () {
        /**
         * init the window handling
         */
        Ext.ux.PopupWindow.prototype.url = 'index.php';
        
        /**
         * initialise window types
         */
        var windowType = (Tine.Tinebase.registry.get('preferences') && Tine.Tinebase.registry.get('preferences').get('windowtype')) ? Tine.Tinebase.registry.get('preferences').get('windowtype') : 'Browser';
            
        Tine.WindowFactory = new Ext.ux.WindowFactory({
            windowType: windowType
        });
        
        /**
         * register MainWindow
         */
        if (window.isMainWindow) {
            Ext.ux.PopupWindowMgr.register({
                name: window.name,
                popup: window,
                contentPanelConstructor: 'Tine.Tinebase.MainScreen'
            });
        }
    },
    /**
     * initialise state provider
     */
    initState: function () {
        if (Tine.Tinebase.tineInit.stateful === true) {
            if (window.isMainWindow || Ext.isIE) {
                // NOTE: IE is as always pain in the ass! cross window issues prohibit serialisation of state objects
                Ext.state.Manager.setProvider(new Tine.Tinebase.StateProvider());
            } else {
                var mainWindow = Ext.ux.PopupWindowMgr.getMainWindow();
                Ext.state.Manager = mainWindow.Ext.state.Manager;
            }
        }
    },
    
    /**
     * add provider to Ext.Direct based on Tine servicemap
     */
    initExtDirect: function () {
        var sam = Tine.Tinebase.registry.get('serviceMap');
        
        Ext.Direct.addProvider(Ext.apply(sam, {
            'type'     : 'jsonrpcprovider',
            'namespace': 'Tine',
            'url'      : sam.target
        }));
    },
    
    /**
     * init external libraries
     */
    initLibs: function () {
        if (OpenLayers) {
            // fix OpenLayers script location to find images/themes/...
            OpenLayers._getScriptLocation = function () {
                return 'library/OpenLayers/';
            };
        }
    },
    
    /**
     * initialise application manager
     */
    initAppMgr: function () {
        if (! Ext.isIE && ! window.isMainWindow) {
            // return app from main window for non-IE browsers
            Tine.Tinebase.appMgr = Ext.ux.PopupWindowMgr.getMainWindow().Tine.Tinebase.appMgr;
        } else {
            Tine.Tinebase.appMgr = new Tine.Tinebase.AppManager();
        }
    },
    
    /**
     * initialise upload manager
     */
    initUploadMgr: function () {
        Tine.Tinebase.uploadManager = new Ext.ux.file.UploadManager();
    },
    
    /**
     * config locales
     */
    initLocale: function () {
        //Locale.setlocale(Locale.LC_ALL, '');
        Tine.Tinebase.translation = new Locale.Gettext();
        Tine.Tinebase.translation.textdomain('Tinebase');
        window._ = function (msgid) {
            return Tine.Tinebase.translation.dgettext('Tinebase', msgid);
        };
        Tine.Tinebase.prototypeTranslation();
    },
    
    /**
     * Last stage of initialisation, to be done after Tine.onReady!
     */
    onLangFilesLoad: function () {
        //Ext.ux.form.DateTimeField.prototype.format = Locale.getTranslationData('Date', 'medium') + ' ' + Locale.getTranslationData('Time', 'medium');
    }    
    
};

Ext.onReady(function () {
    Tine.Tinebase.tineInit.initWindow();
    Tine.Tinebase.tineInit.initDebugConsole();
    Tine.Tinebase.tineInit.initBootSplash();
    Tine.Tinebase.tineInit.initLocale();
    Tine.Tinebase.tineInit.initAjax();
    Tine.Tinebase.tineInit.initRegistry();
    Tine.Tinebase.tineInit.initLibs();
    var waitForInits = function () {
        if (! Tine.Tinebase.tineInit.initList.initRegistry) {
            waitForInits.defer(100);
        } else {
            Tine.Tinebase.tineInit.initExtDirect();
            Tine.Tinebase.tineInit.initState();
            Tine.Tinebase.tineInit.initWindowMgr();
            //Tine.Tinebase.tineInit.onLangFilesLoad();
            //Tine.Tinebase.tineInit.checkSelfUpdate();
            Tine.Tinebase.tineInit.renderWindow();
        }
    };
    waitForInits();
});
