function WindowControl(config) {
  var self=this, winCtrl=this;



  // *** check prerequisites, prepare data ***
  var evalXPath=HelperFunctions.getEvalXPathFunction({tmb:'http://reiseland-brandenburg.de/XML', xhtml:'http://www.w3.org/1999/xhtml'});
  if( !document.getElementById || !HelperFunctions.getXMLHttpRequest() || !evalXPath ) {
    window.location.href='notsupported.php';
    return;
  }
  var htmlElems={
    container:document.getElementById('jsMap-container')
    , interface:document.getElementById('jsMap-interface')
    , map:document.getElementById('jsMap-map')
    , minimap:document.getElementById('jsMap-minimap')
    , minimapContainer:document.getElementById('jsMap-minimap-container')
    , loader:document.getElementById('jsMap-loader')
    , search:document.getElementById('jsMap-search')
    , classifications:document.getElementById('jsMap-classifications')
    , vcardContainer:document.getElementById('jsMap-vcard-container')
    , vcardClose:document.getElementById('jsMap-vcard-close')
    , categories:document.getElementById('jsMap-categories')
    , menu:document.getElementById('jsMap-menu')
    , events:document.getElementById('jsMap-events')
    , logoBB:document.getElementById('jsMap-logo-bb')
    , logoATKIS:document.getElementById('jsMap-logo-atkis')
  };
  var frames={
    map:window.frames['jsMap-map']
    , minimap:window.frames['jsMap-minimap']
    , vcard:window.frames['jsMap-vcard']
  };
  var fragID=new FragID();
  var hierarchyXML=null;



  // *** process configurations ***
  self.config=config; (function() {
    if(config.vcardBaseURI) self.vcardBaseURI=config.vcardBaseURI;
    if(config.initXML) self.initXML=HelperFunctions.parseFromString(config.initXML);
    if(config.hideMinimap && htmlElems.minimapContainer) htmlElems.minimapContainer.className+=" closed";
    config.layout=config.layout.split(' ');
    for(var i=0; i<config.layout.length; i++) config.layout[ config.layout[i].replace(/^\s*(.*)\s*$/, '$1') ]=true;
    if(config.hideCategory==undefined) config.hideCategory='';
    if(config.showCategory==undefined) config.showCategory='';
  })();
  var getExtraQuery=self.getExtraQuery=function() { return '&lang='+config.langKey+'&idVer='+config.idVer; };



  // *** watch window size ***
  (function() {
    var windowOnresize=window.onresize
        , minSize=[800, 600]
        , mapOff=[250, 60]
    ;
    if( config.layout.minimal ) {
      minSize=[570, 480];
      mapOff=[0, 0];
    }
    function onResize(e) {
      var styleContainer=htmlElems.container.style
          , styleMap=htmlElems.map.style
          , styleLoader=htmlElems.loader.style
          , styleInterface=htmlElems.interface.style
          , winSize=HelperFunctions.getWindowSize()
          , w=Math.max(minSize[0], winSize[0])
          , h=Math.max(minSize[1], winSize[1])
      ;
      styleContainer.width=(w)+'px';
      styleContainer.height=(h)+'px';
      styleMap.width=(w-mapOff[0])+'px';
      styleMap.height=(h-mapOff[1])+'px';
      styleLoader.width=(w)+'px';
      styleLoader.height=(h)+'px';
      if( config.user.match(/ic|tomas/) ) {
      } else if( config.layout.minimal ) {
        if(htmlElems.interface.onmouseout) htmlElems.interface.onmouseout();
        styleInterface.top='auto';
        styleInterface.bottom=(h-32)+'px';
      }
      if(windowOnresize) return windowOnresize.call(window, e);
    };
    window.onresize=onResize;
    var resizeInterval=window.setInterval(
        function() {
          if( !HelperFunctions.getWindowSize() ) return;
          window.clearInterval( resizeInterval );
          onResize();
        }
        , 100
      );
  })();



  // *** notifications from iframes ***
  (function() {
    var notReady= frames.minimap ? 2 : 1 ;
    var xmlReq=HelperFunctions.getXMLHttpRequest();
    self.eventFromIframe=function(e) {
      switch(e.type) {
        case null:
        default:
          break;
        case 'ready':
          if(e.window==frames.map) {
            frames.map.winCtrl.onResize();
            xmlReq.open('GET', 'xml.php?hierarchy'+getExtraQuery(), false);
            try { xmlReq.send(null); } catch(e) {}
            if( xmlReq.readyState==4 && xmlReq.status==200 ) {
              hierarchyXML=xmlReq.responseXML;
              modXML.modify(hierarchyXML.documentElement);
            }
            classifications=new Classifications();
            categories=new Categories();
            events=new Events();
            search=new Search();
            var m;
            if( config.initBBox && (m=/^(\d+),(\d+),(\d+),(\d+)$/.exec(config.initBBox)) ) {
              frames.map.winCtrl.map.moveToBBox([parseInt(m[1]), parseInt(m[2])], [parseInt(m[3]), parseInt(m[4])]);
            }
            if( config.initTour ) {
              for(m=config.initTour.split(','); m[0]; m.shift()) if( m[0]=parseInt(m[0]) ) frames.map.winCtrl.map.tours.add(m[0]);
            }
            loader.hide();
          }
          notReady=Math.max(0, notReady-1);
          if( !notReady ) initFragID();
          break;
        case 'setIntendedGeoPos':
          if( notReady || !frames.minimap ) break;
          try {
            var m= e.window==frames.map ? frames.minimap.winCtrl.map : frames.map.winCtrl.map ;
            m.state.set('dontNotify');
            m.setIntendedGeoPosition(e.pos);
            m.state.clear('dontNotify');
          } catch(e) {}
          break;
        case 'setPosition':
          if( notReady || !frames.minimap ) break;
          try {
            var m= e.window==frames.map ? frames.minimap.winCtrl.map : frames.map.winCtrl.map ;
            m.state.set('dontNotify');
            m.setGeoPosition(e.pos);
            m.state.clear('dontNotify');
          } catch(e) {}
          try {
            frames.minimap.winCtrl.map.bboxMarker.set( frames.map.winCtrl.map.getBBox() );
          } catch(e) {}
          break;
        case 'moveToPosition':
          if( notReady || !frames.minimap ) break;
          if(e.window==frames.minimap) frames.map.winCtrl.map.moveToGeoPosition(e.pos); 
          break;
      }
    }
  })();



  // *** loading message ***
  var loader=new (function() {
    var self=this, cnt=1, div=htmlElems.loader;
    self.hide=function() { if(--cnt==0) div.style.display='none'; };
    self.show=function() { if(cnt++==0) div.style.display='block'; };
    self.isVisible=function() { return cnt>0; };
  })(); self.loader=loader;



  // *** modifications of loaded XMLs ***
  var modXML=new (function() {
    var self=this
        , regexp={nsPrefix:/^[^:]+:/}
        , overrideIcons={}
        , locationsId=-1
    ;
    switch(config.idVer) {
    default:
    case '1.0':
      overrideIcons={
        'instance':{
          6:'hotels.png'
          , 10:'mqm.png'
          , 30:'30pic_rad_lgo_bett-bike_24.png'
          , 32:'32QMJ_Siegel_24.png'
          , 33:'33barrierefrei_Icons_rolli.gif'
          , 34:'34barrierefrei_Icons_augeOh.gif'
          , 35:'35barrierefrei_Icons_kopf.gif'
          , 36:'36barrierefrei_Icons_allerg.gif'
          , 38:'38hat_stern.png'
          , 55:'q_logo.png'
          , 56:'gelbe-welle.png'
          , 57:'mqm.png'
        }
      };
      locationsId=66;
      break;
    case '2.0':
      break;
    }
    self.modify=function(xml) {
      // remove rubrics and categories, which are to be hidden, and their objects
      // !!! objects, whose rubric is hidden but category not, are kept
      var ids={rubric:',', category:','}
        , xpathExpr=[
              "//tmb:category[contains(',"+config.hideCategory+",', concat(',', @id, ','))]"
            , "//tmb:category[contains(@opt, 'JSMAP_HIDE') and not(contains(',"+config.showCategory+",', concat(',', @id, ',')))]"
            , "//tmb:rubric[contains(@opt, 'JSMAP_HIDE')]"
          ]
      ;
      for(var e, elems=evalXPath(xml, xpathExpr.join("|")); e=elems.shift(); ) {
        ids[e.nodeName]+=e.getAttribute('id')+',';
        e.parentNode.removeChild(e);
      }
      for(var i=0, elems=evalXPath(xml, "//tmb:object[contains('"+ids.category+"', concat(',', @cat, ','))]"); i<elems.length; i++) {
        elems[i].parentNode.removeChild( elems[i] );
      }
      // override icons
      for(var i=0, elems=evalXPath(xml, '//tmb:*[@icon]'); i<elems.length; i++) {
        try {
          var newIcon=overrideIcons[ elems[i].nodeName.replace(regexp.nsPrefix) ][ elems[i].getAttribute('id') ];
          if(newIcon) elems[i].setAttribute('icon', 'icons/'+newIcon);
        } catch(e) {}
      }
      // remove objects of category ,,Orte`` (cities from table "places")
      // if objects of any other category  exists
      // with the same attribute locationofficial of address tag
      for(var i=0, elems=evalXPath(xml, '//tmb:object[@cat='+locationsId+' and tmb:address/@locationofficial]'); i<elems.length; i++) {
        var locoff=evalXPath(elems[i], 'tmb:address/@locationofficial')[0].value;
        if( evalXPath(xml, '//tmb:object[@cat!='+locationsId+' and tmb:coords and tmb:address/@locationofficial=\''+locoff+'\']').length ) {
          elems[i].parentNode.removeChild(elems[i]);
        }
      }
    }
  })();



  // *** switch top most elem ***
  var topElem=new (function() {
    var self=this
        , lastTop=null
        , regexp={top:/(^|\s)top(\s+|$)/}
    ;
    self.meNow=function(newTop) {
      if( lastTop==newTop ) return;
      if( lastTop ) lastTop.className=lastTop.className.replace(regexp.top, '$2');
      lastTop=newTop;
      newTop.className+=' top';
    };
  })();



  // *** menu of classification filter ***
  // depends on loaded categories
  var classifications=null; // is instantiated when iframe is ready
  function Classifications() {
    var self=this
        , htmlElem=htmlElems.classifications
        , ul=document.createElement('ul')
        , shownClas={}, shownClis={}
        , activeClas={}, activeClis={}
        , regexp={ss2cs:/^|\s+|$/g, classInactive:/(^|\s)inactive(\s|$)/i}
    ;
    if(config.user.match(/ic|tomas/)) (function() {
      htmlElem.parentNode.removeChild(htmlElem);
      htmlElems.categories.parentNode.insertBefore(htmlElem, htmlElems.menu);
      var dl=htmlElem.appendChild( document.createElement('dl') )
          , dt=dl.appendChild( document.createElement('dt') )
          , dd=dl.appendChild( document.createElement('dd') )
      ;
      dl.style.display='block';
      dt.appendChild( document.createTextNode( config.lang['classifications.select'] ) );
      dt.title=config.lang['classifications.info.line0']+' '+config.lang['classifications.info.line1'];
      dd.appendChild(ul);
      htmlElem=dd;
    })(); else {
      htmlElem.style.display='none';
      htmlElem.appendChild(ul);
    }
    if( !config.layout.minimal ) {
      var li=document.createElement('li');
      li.appendChild( document.createTextNode(config.lang['classifications.info.line0']) );
      li.appendChild( document.createElement('br') );
      li.appendChild( document.createTextNode(config.lang['classifications.info.line1']) );
      ul.appendChild(li);
    }
    self.initClis=function() {
      var clis=evalXPath(hierarchyXML.documentElement, "//tmb:instance[contains(',"+config.initCli+",', concat(',', @id, ','))]");
      for(var i=0, cli; cli=clis[i]; i++) {
        var claId=cli.getAttribute('cla');
        activeClas[claId]==undefined && (activeClas[claId]=0);
        activeClas[claId]++;
        activeClis[ cli.getAttribute('id') ]=cli;
      }
      frames.map.winCtrl.map.icons.setActiveClis(activeClas, activeClis);
    }
    function liOnclick(e) {
      var cliId=this.cliXML.getAttribute('id')
          , claId=this.cliXML.getAttribute('cla')
      ;
      if( regexp.classInactive.exec(this.className) ) {
        this.className=this.className.replace(regexp.classInactive, '$2');
        activeClas[claId]++;
        activeClis[cliId]=this.cliXML;
      } else {
        this.className+=' inactive';
        delete activeClis[cliId];
        if( --activeClas[claId]<=0 ) delete activeClas[claId];
      }
      frames.map.winCtrl.map.icons.setActiveClis(activeClas, activeClis);
      frames.map.winCtrl.map.infoBox.redraw();
    }
    function newLI(cliXML) {
      var li=document.createElement('li')
          , img=document.createElement('img')
      ;
      li.cliXML=cliXML;
      li.onclick=liOnclick;
      img.setAttribute('src', cliXML.getAttribute('icon'));
      img.setAttribute('title', cliXML.getAttribute('alt'));
      img.setAttribute('alt', cliXML.getAttribute('alt'));
      li.className='cliId'+cliXML.getAttribute('id')+' claId'+cliXML.getAttribute('cla');
      if( !activeClis[cliXML.getAttribute('id')] ) li.className+=' inactive';
      li.appendChild(img);
      return li;
    }
    var redraw=self.redraw=function() {
      var clis2draw=[];
      for(var cliId in shownClis) clis2draw.push( shownClis[cliId].xml );
      if( clis2draw.length ) {
        var keepFirstN= config.layout.minimal ? 0 : 1 ;
        while( ul.childNodes.length>keepFirstN ) ul.removeChild( ul.childNodes[keepFirstN] );
        clis2draw.sort(sortCLIs);
        for(var i=0; i<clis2draw.length; i++) ul.appendChild( newLI(clis2draw[i]) );
        if(config.user.match(/ic|tomas/)) htmlElem.parentNode.style.display='block';
        else htmlElem.style.display='block';
      } else {
        if(config.user.match(/ic|tomas/)) htmlElem.parentNode.style.display='block';
        else htmlElem.style.display='none';
      }
      frames.map.winCtrl.map.icons.setActiveClis(activeClas, activeClis);
      frames.map.winCtrl.map.infoBox.redraw();
    }
    function sortCLIs(cli0, cli1) { return cli0.getAttribute('sorting')>cli1.getAttribute('sorting'); }
    function getClasByCatXML(catXML) {
      var cla=catXML.getAttribute('cla');
      if(!cla) return [];
      return evalXPath(catXML, "//tmb:class[contains('"+cla.replace(regexp.ss2cs, ',')+"', concat(',', @id, ',')) and contains(concat(',', @opt, ','), ',JSMAP_FILTER,')]");
    }
    function getClisByClaXMLs(claXMLs) {
      if( !claXMLs.length ) return [];
      var claIds=[];
      for(var i=0; i<claXMLs.length; i++) claIds.push( claXMLs[i].getAttribute('id') );
      return evalXPath(claXMLs[0], "//tmb:instance[contains(',"+claIds.join(',')+",', concat(',', @cla, ','))]");
    }
    function addToShown(shown, xmlList) {
      for(var i=0; i<xmlList.length; i++) {
        var id=xmlList[i].getAttribute('id');
        if( !shown[id] ) shown[id]={xml:xmlList[i], cnt:1};
        else shown[id].cnt++;
      }
    }
    function removeFromShown(shown, xmlList) {
      for(var i=0; i<xmlList.length; i++) {
        var id=xmlList[i].getAttribute('id');
        if( shown[id] && 0>=--shown[id].cnt ) delete shown[id];
      }
    }
    self.newCategory=function(catXML, suppressRedraw) {
      var clas=getClasByCatXML(catXML)
          , clis=getClisByClaXMLs(clas)
      ;
      addToShown(shownClas, clas);
      addToShown(shownClis, clis);
      if(!suppressRedraw) redraw();
    }
    self.removeCategory=function(catXML) {
      var clas=getClasByCatXML(catXML)
          , clis=getClisByClaXMLs(clas)
      ;
      removeFromShown(shownClas, clas);
      removeFromShown(shownClis, clis);
      redraw();
    }
  }



  // *** minimap controls ***
  (function() {
    var elem=htmlElems.minimapContainer;
    if( !elem ) return;
    var regexp={closed:/(^|\s+)closed(\s+|$)/};
    elem.onclick=function(e) {
      if( regexp.closed.exec(this.className) ) this.className=this.className.replace(regexp.closed, '$2');
      else this.className+=' closed';
    };
    elem.onmouseover=function(e) { topElem.meNow(this); };
  })();



  // *** interface menu ***
  if( config.user.match(/ic|tomas/)) (function() {
  })();
  else if( config.layout.minimal ) (function() {
    var interface=htmlElems.interface
        , style=interface.style
        , bottom=null
        , regexp={hover:/(^|\s)hover(\s|$)/}
        , timeout=null
    ;
    function unfold() {
      if( regexp.hover.exec(interface.className) ) return;
      interface.className+=' hover';
      bottom=style.bottom;
      style.bottom='auto';
      style.top=(-12)+'px';
    }
    function foldIn() {
      if( !regexp.hover.exec(interface.className) ) return;
      interface.className=interface.className.replace(regexp.hover, '$2');
      style.top='auto';
      style.bottom=bottom;
    }
    interface.onmouseover=function(e) {
      if( timeout ) {
        window.clearTimeout(timeout);
        timeout=null;
      }
      unfold();
    }
    interface.onmouseout=function(e) {
      if( config.layout.minimal && !timeout ) timeout=window.setTimeout(function() { timeout=null; foldIn(); }, 500);
    }
  })();
  else {
    htmlElems.interface.onmouseover=function(e) { topElem.meNow(this); }
  }
  



  // *** rubric and category menu ***
  var categories=null; // is instantiated when iframe is ready
  var Categories=function() {
    var self=this;
    var div=htmlElems.categories;
    // creates common XHTML structure for rubrics and categories
    function newDIV(parent, xml, onclick) {
      var div=document.createElement('div')
          , h1=document.createElement('h1')
          , opt=xml.getAttribute('opt')
          , alt=xml.getAttribute('alt')
      ;
      parent.appendChild(div);
      div.xml=xml;
      div.className=xml.nodeName;
      if(opt && -1!=opt.indexOf('JSMAP_TRAINSTATION')) div.className+=' trainstation';
      if(onclick) {
        div.onclick=onclick;
        div.onmousedown=onMouseDown;
      }
      var icon_src=xml.getAttribute('icon');
      if(xml.nodeName=='rubric') {
        var categories=evalXPath(hierarchyXML.documentElement, 'tmb:category[@rub=\''+xml.getAttribute('id')+'\']');
        if(categories.length==1) icon_src=categories[0].getAttribute('icon');
      }
      if(icon_src) {
        var icon=document.createElement('img')
        icon.alt=icon.title=xml.getAttribute('name')+(alt?': '+alt:'');
        icon.className='icon';
        icon.src=icon_src;
        div.appendChild(icon);
      }
      h1.appendChild( document.createTextNode( xml.getAttribute('name') ) );
      if(alt) h1.title=alt;
      div.appendChild( h1 );
      return div;
    }
    // handling of rubrics 
    var regexpRubCatElem=/^div$/i;
    if( config.user.match(/ic|tomas/) ) regexpRubCatElem=/^dl$|^p$/i;
    var regexpRubric=/(^|\s+)rubric(\s+|$)/
        , regexpRubricOpen=/(^|\s+)open(\s+|$)/
        , regexpRubricActive=/(^|\s+)active(\s+|$)/
    ;
    // load/unload POIs of rubric, if it has only one category, use category event handler for this
    // or open rubric an show categories
    function onRubricClick(e) {
      e||(e=window.event);
      if( config.user.match(/ic|tomas/) ) {
        if(this.categoryXMLs.length==1) this.nextSibling.firstChild.onclick(e);
      } else {
        for(var rubric=div.firstChild; rubric; rubric=rubric.nextSibling)
          if( rubric.nodeType==1 && regexpRubCatElem.exec(rubric.nodeName) && this!=rubric && regexpRubric.exec(rubric.className) )
            rubric.className=rubric.className.replace(regexpRubricOpen, '$2');
        var categories=evalXPath(hierarchyXML.documentElement, 'tmb:category[@rub=\''+this.xml.getAttribute('id')+'\']');
        if(categories.length>1) {
          if( !regexpRubricOpen.exec(this.className) ) this.className+=' open';
        } else {
          this.childNodes[2].onclick(e);
        }
        events.close();
      }
      return HelperFunctions.stopPropagation(e);
    }
    self.closeAllRubrics=function() {
      for(var rubric=div.firstChild; rubric; rubric=rubric.nextSibling)
        if( rubric.nodeType==1 && regexpRubCatElem.exec(rubric.nodeName) )
          rubric.className=rubric.className.replace(regexpRubricOpen, '$2');
    };
    // mark rubrics as active, if any of its categories is active or deactivate rubric if none of them is active 
    function checkRubricActivation() {
      for(var rubric=div.firstChild; rubric; rubric=rubric.nextSibling) {
        if( rubric.nodeType==1 && regexpRubCatElem.exec(rubric.nodeName) && regexpRubric.exec(rubric.className) ) {
          var category=rubric.firstChild;
          if( config.user.match(/ic|tomas/) ) category=category.nextSibling.firstChild;
          for(; category; category=category.nextSibling) {
            if( category.nodeType==1 && regexpRubCatElem.exec(category.nodeName) && regexpCategoryActive.exec(category.className) ) {
              if( !regexpRubricActive.exec(rubric.className) ) rubric.className+=' active';
              break;
            }
          }
          if(!category) rubric.className=rubric.className.replace(regexpRubricActive, '$2');
        }
      }
    }
    // handling of categories
    var regexpCategoryActive=/(^|\s+)active(\s|$)/;
    // load/unload category and refresh classifications
    function onCategoryClick(e) {
      e||(e=window.event);
      var catid=this.xml.getAttribute('id');
      if( regexpCategoryActive.exec(this.className) ) {
        this.className=this.className.replace(regexpCategoryActive, '$2');
        frames.map.winCtrl.map.pois.removeGroup(catid);
        classifications.removeCategory(this.xml);
      } else {
        loader.show();
        window.setTimeout( function() { frames.map.winCtrl.map.pois.addGroup(catid, loader.hide); }, 0);
        this.className+=' active';
        classifications.newCategory(this.xml);
      }
      checkRubricActivation();
      return HelperFunctions.stopPropagation(e);
    }
    // prevent mousedown event from being propagated, so that underlying map can't be moved
    function onMouseDown(e) { e||(e=window.event); return HelperFunctions.stopPropagation(e); }
    // mark rubrics as rolled over
    var over=[];
    function onRubricOver(e) { this.className+=' hover'; }
    function onRubricOut(e) { this.className=this.className.replace(/(^|\s)hover(\s|$)/, '$2'); }
    // prepare categories, that need to be shown initially
    var initCatIds={};
    if( config.initCategory && 'string'==typeof(config.initCategory) ) {
      loader.show();
      window.setTimeout( function() { frames.map.winCtrl.map.pois.addGroup(config.initCategory, loader.hide); }, 0);
      for(var i=0, catIds=config.initCategory.split(','); i<catIds.length; i++)
        if( catIds[i]=parseInt(catIds[i]) )
          initCatIds[ catIds[i] ]=true;
    }
    // build menu of rubrics and categories
    // special styles and forms for some users or layouts possible
    for(var i=0, rubrics=evalXPath(hierarchyXML.documentElement, 'tmb:rubric'); i<rubrics.length; i++) {
      var categories=evalXPath(hierarchyXML.documentElement, 'tmb:category[@rub=\''+rubrics[i].getAttribute('id')+'\']');
      var alt;
      if( !categories.length ) continue;
      if( config.user.match(/ic|tomas/) ) {
        var h1; if(!h1) {
          div.appendChild( h1=document.createElement('h1') );
          h1.title=config.lang['categories.alt'];
          if(config.layout.closecat) div.className+=' closed';
          h1.onclick=function(evt) {
            var cats=htmlElems.categories, regexp=/(^|\s)closed(\s|$)/;
            if( cats.className.match(regexp) ) {
              cats.className=cats.className.replace(regexp, '$2');
            } else cats.className+=' closed';
          }
          h1.appendChild(   document.createTextNode( config.lang['categories.select'] )   );
        }
        var dl=document.createElement('dl');
        dl.onmouseover=onRubricOver;
        dl.onmouseout=onRubricOut;
        dl.xml=rubrics[i];
        dl.className= 'rubric' + (categories.length>1?' multi':' single') ;
        var dt=document.createElement('dt');
        dt.xml=rubrics[i];
        if( alt=dt.xml.getAttribute('alt') ) dt.title=alt;
        dt.onclick=onRubricClick;
        dt.categoryXMLs=categories;
        dt.appendChild(   document.createTextNode( dt.xml.getAttribute('name') )   );
        if( categories.length==1 ) dt.style.backgroundImage='url("'+categories[0].getAttribute('icon')+'")';
        dl.appendChild(dt);
        var dd=null;
        for(var j=0, maxRowsPerColumn=7; j<categories.length; j++) {
          if(j%maxRowsPerColumn==0) {
            dd && dl.appendChild(dd);
            dd=document.createElement('dd');
            dd.className='col'+Math.floor(j/maxRowsPerColumn);
          }
          var p=document.createElement('p');
          p.xml=categories[j];
          if( alt=p.xml.getAttribute('alt') ) p.title=alt;
          p.appendChild( document.createTextNode(p.xml.getAttribute('name')) );
          p.style.backgroundImage='url("'+p.xml.getAttribute('icon')+'")';
          if(categories.length==1) p.style.display='none';
          p.onclick=onCategoryClick;
          if( initCatIds[ categories[j].getAttribute('id') ] ) {
            classifications.newCategory(categories[j], true);
            p.className+=' active';
          }
          dd.appendChild(p);
        }
        dd && dl.appendChild(dd);
        div.appendChild(dl);
      } else {
        var rubDIV=newDIV(div, rubrics[i], onRubricClick);
        for(var j=0; j<categories.length; j++) {
          var catDIV=newDIV(rubDIV, categories[j], onCategoryClick);
          if( initCatIds[ categories[j].getAttribute('id') ] ) {
            classifications.newCategory(categories[j], true);
            catDIV.className+=' active';
          }
        }
      }
    }
    classifications.initClis();
    classifications.redraw();
    checkRubricActivation();
  }



  // *** events ***
  var events=null;
  function Events() {
    var self=this
        , firstDayOfWeek=1 //monday
        , daysOfWeek=[]
        , monthsOfYear=[]
    ;
    for(var i=0; i<7; i++) daysOfWeek[i]=config.lang['events.weekdays.'+i];
    for(var i=0; i<12; i++) monthsOfYear[i]=config.lang['events.months.'+i];
    var begin, end; (function() {
      var d=new Date();
      d.setMilliseconds(0);
      d.setSeconds(0);
      d.setMinutes(0);
      d.setHours(0);
      begin=new Calender(document.getElementById('jsMap-events-begin'), new Date(d.getTime()));
      d.setTime( d.getTime() + (8*24*60*60-1)*1000 );
      end=new Calender(document.getElementById('jsMap-events-end'),   new Date(d.getTime()));
    })();
    var makeClickable; (function() {
      var hover=/(^|\s)hover(\s|$)/;
      function onmouseover(e) { if( !hover.exec(this.className) ) this.className+=' hover'; }
      function onmouseout(e) { this.className=this.className.replace(hover, '$2'); }
      makeClickable=function(cell, onclick, cfg) {
        cell.className+=' clickable';
        cell.onmouseover=onmouseover;
        cell.onmouseout=onmouseout;
        if(onclick) cell.onclick=onclick;
        if(cfg) cell.cfg=cfg;
      };
    })();
    var pageOnclick; (function() {
      var back=/(^|\s)pageBack(\s|$)/, forth=/(^|\s)pageForth(\s|$)/;
      pageOnclick=function(e) {
        if( back.exec(this.className) ) this.cfg.cal.pageMonths(-1);
        else if( forth.exec(this.className) ) this.cfg.cal.pageMonths(1);
      };
    })();
    var dayOnclick; (function() {
      dayOnclick=function(e) {
        this.cfg.cal.setDate( this.cfg.d );
      }
    })();
    var update; (function() {
      var span=htmlElems.events.getElementsByTagName('h1')[0].getElementsByTagName('span')[0];
      update=function() {
        var b=begin.getDate(), e=end.getDate()
            , msg=''+config.lang['events.invalidDateOrder']
        ;
        if(b<=e) msg=
          '('
          +b.getDate()+'.'+(1+b.getMonth())+'.'+( b.getFullYear()==e.getFullYear() ? '' : b.getFullYear() )
          +' - '
          +e.getDate()+'.'+(1+e.getMonth())+'.'+e.getFullYear()
          +')'
        ;
        span.replaceChild(document.createTextNode(msg), span.firstChild);
      }
    })();
    function Calender(elem, date) {
      var self=this
          , displayedDate=new Date( date.getTime() )
      ;
      displayedDate.setDate(1);
      self.write=function() {
        var table=document.createElement('table')
            , tbody=document.createElement('tbody')
            , tr=null, cell=null, cellCnt=0
            , d=new Date( displayedDate.getTime() )
        ;
        function writeCell(content, cols, type) {
          if(0==cellCnt%7) tbody.appendChild( tr=document.createElement('tr') );
          tr.appendChild( cell=document.createElement(type ? type : 'td') );
          if(cols) {
            cell.colSpan=cols;
            cellCnt+=cols;
          } else cellCnt++;
          cell.appendChild( document.createTextNode(content) );
        }
        with(table) border=cellSpacing=cellPadding=0;
        table.appendChild(tbody);
        writeCell('<', 1, 'th');
        cell.className='pageBack';
        makeClickable(cell, pageOnclick, {cal:self});
        writeCell(monthsOfYear[d.getMonth()]+'. '+d.getFullYear(), 5, 'th');
        cell.className='month';
        writeCell('>', 1, 'th');
        cell.className='pageForth';
        makeClickable(cell, pageOnclick, {cal:self});
        if(d.getDay()!=firstDayOfWeek) writeCell('', (d.getDay()-1+7)%7);
        while(d.getMonth()==displayedDate.getMonth()) {
          writeCell( d.getDate() );
          cell.className=daysOfWeek[(cellCnt-1)%7];
          if( date.getTime()==d.getTime() ) cell.className+=' active';
          else if( begin.getDate()<=d && d<=end.getDate() ) cell.className+=' between';
          makeClickable(cell, dayOnclick, {cal:self, d:d.getDate()});
          d.setDate( 1+d.getDate() );
        }
        for(var i=0, tables=elem.getElementsByTagName('table'); i<tables.length; i++) {
          if(elem==tables[i].parentNode) elem.removeChild(tables[i]);
        }
        elem.appendChild(table);
      };
      self.pageMonths=function(i) {
        displayedDate.setMonth( displayedDate.getMonth()+i );
        self.write();
      }
      self.setDate=function(d) {
        date=new Date( displayedDate.getTime() );
        date.setDate(d);
        begin.write();
        end.write();
        update();
      }
      self.getDate=function() { return date; };
    }
    begin.write();
    end.write();
    update();
    window.setTimeout(function() {
      var h=htmlElems.events.getElementsByTagName('h1')[0]
          , links=htmlElems.events.getElementsByTagName('a')
          , regexp={active:/(^|\s)active(\s|$)/, open:/(^|\s)open(\s|$)/, showclear:/(^|\s)(show|clear)(\s|$)/}
          , pois=frames.map.winCtrl.map.pois
          , eventsXML=null
      ;
      h.onclick=function(e) {
        if( !regexp.open.exec(htmlElems.events.className) ) {
          htmlElems.events.className+=' open';
          categories.closeAllRubrics();
        }
      };
      self.close=function() {
        htmlElems.events.className=htmlElems.events.className.replace(regexp.open, '$2');
      };
      function removePOIs() {
        if(eventsXML) {
          pois.removeGroup(eventsXML, 'event');
          eventsXML=null;
        }
      }
      for(var a, i=0; a=links[i]; i++) switch( regexp.showclear.exec(a.className)[2] ) {
      case 'show':
        a.onclick=function(e) {
          e||(e=window.event);
          loader.show();
          removePOIs();
          htmlElems.events.className+=' active';
          var period=[Math.round(begin.getDate().getTime()/1000), Math.round(end.getDate().getTime()/1000)];
          var xmlReq=HelperFunctions.getXMLHttpRequest();
          xmlReq.open('GET', 'event.php?start='+period[0]+'&end='+period[1]+getExtraQuery(), false);
          try{ xmlReq.send(null); } catch(e) {}
          if( xmlReq.readyState==4 && xmlReq.status==200 ) {
            eventsXML=xmlReq.responseXML;
            eventsXML.documentElement.setAttribute('start', period[0]);
            eventsXML.documentElement.setAttribute('end', period[1]);
            for(var i=0, events=evalXPath(eventsXML.documentElement, '//tmb:object'); i<events.length; i++) {
              events[i].setAttribute('icon', 'icons/event.gif');
              //events[i].setAttribute( 'id' , 'e'+events[i].getAttribute('id') );
            }
            window.setTimeout(function() { pois.addGroup(eventsXML, loader.hide, 'event') }, 0);
          }
          return HelperFunctions.stopPropagation(e);
        };
        break;
      case 'clear':
        a.onclick=function(e) {
          e||(e=window.event);
          htmlElems.events.className=htmlElems.events.className.replace(regexp.active, '$2');
          removePOIs();
          return HelperFunctions.stopPropagation(e);
        };
        break;
      }
    }, 0);
  }



  // *** search ***
  var search=null; // is created when iframe is ready
  function Search() {
    var form=htmlElems.search.getElementsByTagName('form')[0]
        , result
        , poiXML, addressXML
        , type='search'
        , pois=frames.map.winCtrl.map.pois
        , regexp={
            centroidId:/^a\d+$/
            , swordParts:/(^|\s)(\S+)/g
            , searchHover:/(^|\s)hover(\s|$)/
            , searchResults:/(^|\s)results(\s|$)/
          }
        , timeout=null
    ;
    htmlElems.search.onmouseover=function(e) {
      if(timeout) {
        window.clearTimeout(timeout);
        timeout=null;
      }
      if( regexp.searchHover.exec(this.className) ) return;
      this.className+=' hover';
      topElem.meNow(this);
    };
    htmlElems.search.onmouseout=function(e) {
      if( !regexp.searchHover.exec(this.className) ) return;
      if( !timeout ) {
        var elem=this;
        timeout=window.setTimeout(function() { elem.className=elem.className.replace(regexp.searchHover, '$2'); timeout=null; }, 500); 
      }
    };
    result=htmlElems.search.getElementsByTagName('div');
    for(var i=0; i<result.length; i++) if(result[i].className=='result') { result=result[i]; break; }
    form.onsubmit=function(e) {
      e||(e=window.event);
      var sword=this.elements['sword'].value.replace(regexp.swordParts, '$1+$2*');
      if(sword.length!='') {
        loader.show();
        try {
          pageTracker._trackPageview('/search='+sword);
        } catch(ex) {}
        var xmlReq=HelperFunctions.getXMLHttpRequest();
        xmlReq.open('GET', 'address.php?address='+encodeURIComponent(sword)+getExtraQuery(), false);
        try { xmlReq.send(null); } catch(e) {}
        if( xmlReq.readyState==4 && xmlReq.status==200 ) addressXML=xmlReq.responseXML;
        xmlReq.open('GET', 'xml.php?orderby=name&sword='+encodeURIComponent(sword)+getExtraQuery(), false);
        try { xmlReq.send(null); } catch(e) {}
        if( xmlReq.readyState==4 && xmlReq.status==200 ) {
          modXML.modify( xmlReq.responseXML.documentElement );
          updateResult(xmlReq.responseXML);
        }
      }
      return HelperFunctions.stopPropagation(e);
    }
    form.onreset=function(e) {
      if(poiXML) {
        frames.map.winCtrl.map.pois.removeGroup(poiXML, type);
        doClassifications(true);
        poiXML=null;
        clearResultList();
      }
    }
    form.elements['sword'].onclick=function(e) { this.select(); };
    function updateResult(_poiXML, dontChangeView) {
      if( poiXML || !_poiXML ) {
        pois.removeGroup(poiXML, type);
        clearResultList();
        doClassifications(true);
      }
      poiXML=_poiXML;
      if(poiXML && poiXML.documentElement) {
        var objXMLs=evalXPath(poiXML.documentElement, '//tmb:object[tmb:coords]')
            , listElem=document.createElement('dl') 
        ;
        listElem.appendChild( document.createElement('dt') );
        if(objXMLs.length) {
          listElem.firstChild.appendChild( document.createTextNode(config.lang['search.results.pois.hits']+' ('+objXMLs.length+')') );
          for(var i=0; i<objXMLs.length; i++) listElem.appendChild( createResultItem(objXMLs[i]) );
        } else listElem.firstChild.appendChild( document.createTextNode(config.lang['search.results.pois.empty']) );
        result.appendChild(listElem);
        doClassifications();
        window.setTimeout( function() { pois.addGroup(poiXML, loader.hide, type, dontChangeView); }, 0);
      } else loader.hide();
      if( addressXML && addressXML.documentElement ) {
        var objXMLs=evalXPath(addressXML.documentElement, '//tmb:object[tmb:address and tmb:coords]')
            , listElem=document.createElement('dl')
        ;
        listElem.appendChild( document.createElement('dt') );
        if(objXMLs.length) {
          listElem.firstChild.appendChild( document.createTextNode(config.lang['search.results.adresses.hits']+' ('+objXMLs.length+')') );
          listElem.className+=' addresses';
          for(var i=0; i<objXMLs.length; i++) {
            objXMLs[i].setAttribute('id', 'a'+i);
            var addr=evalXPath(objXMLs[i], 'tmb:address')[0];
            objXMLs[i].setAttribute('name', addr.getAttribute('street')+', '+addr.getAttribute('zip')+' '+addr.getAttribute('location'));
            listElem.appendChild( createResultItem(objXMLs[i]) );
          }
        }
        result.appendChild(listElem);
      }
      if( !regexp.searchResults.exec(htmlElems.search.className) ) htmlElems.search.className+=' results';
    }
    function clearResultList() { 
      with(result) while(firstChild) removeChild(firstChild);
      htmlElems.search.className=htmlElems.search.className.replace(regexp.searchResults, '$2');
    }
    function createResultItem(objXML) {
      var itemElem=document.createElement('dd');
      itemElem.objXML=objXML;
      itemElem.onclick=resultItemOnclick;
      itemElem.appendChild( document.createTextNode(objXML.getAttribute('name')) );
      return itemElem;
    }
    function resultItemOnclick(e) {
      e||(e=window.event);
      var id=this.objXML.getAttribute('id')
          , map=frames.map.winCtrl.map
      ;
      if( regexp.centroidId.exec(id) ) {
        var coords=evalXPath(this.objXML, 'tmb:coords')[0];
        if(coords) {
          var pos=[ parseInt(coords.getAttribute('x')) , parseInt(coords.getAttribute('y')) ];
          map.markGeoPos(pos);
          map.switchScale(null, Number.MAX_VALUE);
          map.moveToGeoPosition(pos);
        }
      } else {
        frames.map.winCtrl.activateIconByObjectXML(this.objXML);
      }
      return HelperFunctions.stopPropagation(e);
    }
    function doClassifications(unregister) {
      var op= unregister ? classifications.removeCategory : classifications.newCategory ;
      for(var i=0, catXMLs=evalXPath(poiXML.documentElement, '//tmb:category'); i<catXMLs.length; i++)
        op(catXMLs[i], true);
      classifications.redraw();
    }
    if(winCtrl.initXML) {
      loader.show();
      modXML.modify(winCtrl.initXML.documentElement);
      updateResult(winCtrl.initXML, undefined!=winCtrl.config.initBBox);
    }
  }



  // *** vcard handling ***
  self.openVCard=function(uri) {
    if(!uri) uri=self.vcardBaseURI+'visitenkarte.php?mode=notizblock'+getExtraQuery();
    var f=frames.vcard
        , vcardContStyle=htmlElems.vcardContainer.style
    ;
    if(config.parentURI) {
      window.parent.location.href=config.parentURI+fragID.encode({op:'openVCard', uri:uri});
    } else if(config.layout.minimal) {
      window.open(uri, 'tmbvcard', 'width=780, height=580, location=no, menubar=no, resizable=no, status=no, toolbar=no').focus();
    } else {
      f.location.href='about:blank';
      window.setTimeout(function() {
        f.document.open();
        f.document.write(
            '<html>'
              +'<head>'
                +'<title>'+config.lang['misc.loadingData']+'</title>'
              +'</head>'
              +'<body style="background:url(css/loading'+(config.langKey=='de'?'':'_'+config.langKey)+'.gif) no-repeat center; min-width:600px; min-height:400px;" />'
            +'</html>'
        );
        f.document.close();
        if(vcardContStyle.display!='block') {
          loader.show();
          vcardContStyle.display='block';
        }
        f.location.href=uri;
      }, 0);
    }
  }
  htmlElems.vcardClose.onclick=function(e) {
    e||(e=window.event);
    htmlElems.vcardContainer.style.display='none';
    frames.vcard.location.href='about:blank';
    loader.hide();
    return HelperFunctions.stopPropagation(e);
  };



  // *** mouse handling ***
  document.ondragstart=document.onselectstart=function(e) { return false; };



  // *** display logo links in iframe ***
  (function() {
    function onLogoClick(e) {
      e||(e=window.event);
      if(config.user.match(/ic|tomas/)) {
      } else self.openVCard( this.href );
      return HelperFunctions.stopPropagation(e);
    }
    for(var i=0, links=htmlElems.logoBB.getElementsByTagName('a'); i<links.length; i++) links[i].onclick=onLogoClick;
    for(var i=0, links=htmlElems.logoATKIS.getElementsByTagName('a'); i<links.length; i++) links[i].onclick=onLogoClick;
  })();



  // *** iframe crosssite communication ***
  // started only when everything is ready
  function initFragID() {
    var href=null;
    window.setInterval(function() {
      if( href==window.location.href || loader.isVisible() ) return;
      href=window.location.href;
      var data=fragID.decode(href);
      if(!data) return;
      if(data.op) switch(data.op) {
      case 'activateIcon':
        if(data.id) frames.map.winCtrl.activateIconById(data.id);
        break;
      case 'setParentURI':
        if( window.parent && data.uri ) config.parentURI=data.uri;
        break;
      }
    }, 100);
  }



  // *** printing ***
  // open new window, to render same map area with img elements, that are (should be) always printable
  // window is printed and closed automatically
  self.printMap=function() {
    var printMap
      , map=frames.map.winCtrl.map
      , tiles=map.tiles.tiles
      , tours=frames.map.document.getElementById('jsMap-tours')
      , regExp={url:/^url\(["']?([^"']*)["']?\)$/, multi:/(^|\s)multi(\s|$)/}
    ;
    function setStyles(elem, styles) { for(var s in styles) elem.style[s]=styles[s]; }
    self.printMapOnload=function() {
      var printDoc=printMap.document
        , z=0
      ;
      // set map size according to current visible size
      var div=printDoc.getElementById('map');
      setStyles(div, {position:'absolute', width:map.size[0]+'px', height:map.size[1]+'px', overflow:'hidden'});
      // create map graphic images
      for(var pos in tiles) {
        var gfx=tiles[pos].gfx, src;
        if( !(src=regExp.url.exec(gfx.style.backgroundImage)) ) continue;
        src=src[1];
        var img=printDoc.createElement('img');
        setStyles(img, {position:'absolute', zIndex:++z, left:gfx.style.left, top:gfx.style.top});
        img.src=src;
        div.appendChild(img);
      }
      // put tour layer on map
      var t=null;
      if(printDoc.importNode) {
        t=printDoc.importNode(tours, true);
      } else if(tours.innerHTML) {
        t=printDoc.createElement('div');
        t.innerHTML=tours.innerHTML;
        t.setAttribute('id', 'jsMap-tours');
        setStyles(t, {position:'absolute', width:map.size[0]+'px', height:map.size[1]+'px', overflow:'hidden'});
      }
      if(t) {
        setStyles(t, {zIndex:++z});
        div.appendChild(t);
      }
      // put icons on map
      for(var pos in tiles) {
        var icons=tiles[pos].icons
          , x=parseFloat(icons.style.left)
          , y=parseFloat(icons.style.top)
        ;
        for(var icon=icons.firstChild; icon; icon=icon.nextSibling) {
          var src=regExp.url.exec(icon.style.backgroundImage) 
            , img=printDoc.createElement('img')
            , divIcon=printDoc.createElement('div')
          ;
          src= src ? src[1] : 'css/multiicon'+(config.user.match(/ic|tomas/)?'_ic.png':'.gif') ;
          setStyles(divIcon, {position:'absolute', zIndex:++z, left:(x+parseFloat(icon.style.left))+'px', top:(y+parseFloat(icon.style.top))+'px', width:28+'px', height:28+'px'});
          div.appendChild(divIcon);
          setStyles(img, {position:'absolute', top:'0', left:'0'});//width:28+'px', height:28+'px'});
          img.src='css/icon_shadow.png';
          divIcon.appendChild(img);
          img=printDoc.createElement('img');
          setStyles(img, {position:'absolute', left:4+'px', top:4+'px'});//, width:20+'px', height:20+'px'});
          img.src=src;
          divIcon.appendChild(img);
        }
      }
      // put logos on top of map
      if( config.user.match(/ic|tomas/) ) {
      } else {
        img=printDoc.createElement('img');
        setStyles(img, {position:'absolute', right:8+'px', bottom:8+'px', zIndex:++z});
        img.src='css/bb-logo.png';
        div.appendChild(img);
      }
      img=printDoc.createElement('img');
      setStyles(img, {position:'absolute', left:8+'px', bottom:8+'px', zIndex:++z});
      img.src= config.user.match(/ic|tomas/) ? 'css/atkis-logo_ic.png' : 'css/atkis-logo.png' ;
      div.appendChild(img);
      printMap.print();
      printMap.close();
    };
    try {
      printMap=window.open('printMap.php?'+'&user='+config.user+'&layout='+config.layout.join('%20')+getExtraQuery(), 'printMap');
      if(!printMap) throw('not opened');
    } catch(exc) {
      alert('Please enable popups to print the map!');
    }
  };



}