/**
 * @fileoverview This code can help smb to zoom in/out and scroll big images
 * Tested in IE6, Mozila FireFox 2.0.0.2, Opera 7.23
 *
 * This code is free software, so you can redistribute it and/or
 * modify it (even eat) under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 * See licence on http://www.gnu.org/copyleft/lesser.html (or in the package)
 *
 * This code is distributed 'cause it could be useful, but WITHOUT ANY GUARANTEE!!!
 *
 * @author KSNK {@link http://forum.dklab.ru/users/ksnk/}
 * @special_thanks SiNaC0m {@link http://forum.dklab.ru/users/SiNaC0m/}
 * @special_thanks AKS {@link http://forum.dklab.ru/users/aks/}
 * @version donow :(
 * @charcode  windows-1251
 * @detail_description

   основной смысл -- пробежаться по коду и все картинки с
       классом "panorama" сделать скроллируемыми и зуммируемыми

      примерный алгоритм
      по window.onLoad для каждой картинке обрамляющий ее div устанавливаем
      - style.overflow='hidden', дабы неповадно было
      - ожидаем подгрузки картинки, после чего вешаем обработчик onmouseover

      по приходу в картинку
      - рисуем колесо прокрутки (roller) верхнем-правом углу
      - устанавливаем обработчики мыши, чтобы не потерять выход за границу
      - ставим обработчик клавиатуры.

      по выходу из картинки - вертаем все в зад

   устройство такой картинки -

       <div> <img class='panorama' src=XXX/></div>
   после "подготовки" у DIV'а появится style.overflow=hidden,
   класс у картинки - пропадет, сама она "впишется" в рамку DIV'а

   примерный вид Роллера :

<div id='ROLLER' style="visibility:hidden;position:absolute" >
	<img src="roller0.gif" usemap="#Map" border=0>
	<map name="Map">
	  <area shape="rect" coords="13,0,47,13"  href="#up">
	  <area shape="rect" coords="13,47,47,63" href="#dn">
	  <area shape="rect" coords="0,15,12,47"  href="#lt">
	  <area shape="rect" coords="48,15,61,47" href="#rt">
	  <area shape="rect" coords="11,15,28,47" href="#zu">
	  <area shape="rect" coords="29,15,47,47" href="#zd">
	</map>
</div>
 или
<div id='ROLLER2' style="visibility:hidden;position:absolute;">
	<table>
	<tr>
	  <td>&nbsp;</td>
	  <td><a href=#up>up</a></td>
	  <td>&nbsp;</td>
	</tr><tr>
	  <td><a href=#lt>lt</a></td>
	  <td><a href=#zu>+</a><a href=#zd>-</a></td>
	  <td><a href=#rt>rt</a></td>
	</tr><tr>
	  <td>&nbsp;</td>
	  <td><a href="#dn">dn</a></td>
	  <td>&nbsp;</td>
	</tr>
	</table>
</div>
 или даже так
<div id='ROLLER' style="visibility:hidden;position:absolute;">
<table><tr>
  <td>&nbsp;</td>
  <td><input type='button' value='up' onMouseDown="__SCROLLZOOM__.doit('up')"
            onmouseup="__SCROLLZOOM__.clr()"></td>
  <td>&nbsp;</td>
...
</tr><tr>
  <td>&nbsp;</td>
  <td><input type='button' value='dn' onMouseDown="__SCROLLZOOM__.doit('dn')"
            onmouseup="__SCROLLZOOM__.clr()"></td>
  <td>&nbsp;</td>
</tr></table>
</div>

   стиль роллера так или иначе должен содержать атрибут position:absolute;
   все внутренние элементы, содержащие атрибут href вида #ACTION , где ACTION -
     поля структуры hstore далее по тексту, будет снабжен методами
     onmousedown="doit(ACTION)",
     onmouseup="clr()",
     onclick="return false"

   для настройки скрипта "по месту" можно сразу за тегом скрипта написать еще такое

// настройка объекта по месту...
<script type="text/javascript" >

    __SCROLLZOOM__.expand({

		zoomable:       'panorama',
		    //def: zoomable  - имя класса IMG, который будем зуммировать

	    picture_dragged:'panorama_dragged',
	        //def: не использ  - стиль drag'ируемого DIV

	    use_roller:     'ROLLER'
	        //def: ROLLER имя роллера

    })
</script>
    параметры и комментарии к ним - см. далее по тексту

    ********************************************************
 */

(__SCROLLZOOM__={/************************************************************
 *  настройка параметров
 ************************************************************
 *   имя класса IMG, который будем зуммировать
 */
	zoomable:'zoomable',
/**
 *   стиль drag'ируемого окна - по умолчанию - отключен
 */
//    picture_dragged:'panorama_dragged',
/**
 *   имя роллера
 */
    use_roller:'ROLLER',
/**
 *   использовать ли клавиатуру для зума-скролла
 */
    use_keyboard:true,
/**
 *   использовать ли колесо мыши для зума
 */
    use_mouseweel:true,

/**
 *  список и расшифровка всех действий, которые можно сделать
 */
    hstore:{
    // тут храняться все действия
    // элементы - якоря перехода, которые вставляются в href соответствующего МAP-сегмента
        lt:['apply_scroll',-4,0],
        rt:['apply_scroll', 4,0],
        up:['apply_scroll', 0,-4],
        dn:['apply_scroll', 0,4],
        zu:['apply_Zoom', 1.04],
        zd:['apply_Zoom', 0.96]
    },
    expand: function(prop) {    	for(var p in prop)
    	   this[p]=prop[p]  // имеем дело только с простым объектами
 //   	return this
    },

	init:function(){	var
/************************************************************
 *  грустная магия Javascript
 */
    apply_Func=function (aObj, aMethod, par,par2 ) {
           return function () { aObj[aMethod](par,par2) };
    },
/************************************************************
 *  и еще
 */
    apply_EH =function (o,m,p) {
        return function (e){return o[m](e,p) }
    },
/*************************************************************
 *  если клавиатура используется - соответствие клавишей действиям
 */                                                             //    38
    keymap={38:'up',37:'lt',40:'dn',39:'rt',109:'zd',107:'zu'}, // 37 40 39
                                                                //    +-
/************************************************************
 *  поиск элемента по имени - сокращенная запись
 */
    _i=function (e){return(   document.getElementById(e)
                            ||document.getElementsByName(e)[0])},

/************************************************************
 *  установка обработчиков событий
 */
    add_Handler= function(a,e,o){
        if (a.addEventListener)
           a.addEventListener(e,o,false)
        else if (a.attachEvent) {
            try {
                a.attachEvent('on' + e, o);
            } catch (aEx) {}
        }
        return{a:a,e:e,o:o} // для автоматической чистки хендлов!!!
    },
    clear_handlers=function(_h) {    	var h;        while(h=_h.pop()){
            if (h.a.removeEventListener) {
                h.a.removeEventListener(h.e, h.o, false);
            } else if (h.a.detachEvent) {
                try {
                    h.a.detachEvent('on' + h.e, h.o);
                } catch (aEx) {}
            }
        }
    },
/************************************************************
 *  вычисление координат элемента в "абсолютных" числах
 */
    getBounds=function (e)
    {
      for (var parent = e.offsetParent,left=0,top=e.offsetTop; parent;
          parent = parent.offsetParent)
      {
        left += parent.offsetLeft;
        top += parent.offsetTop;
      }
      return {left: left, top: top, width: e.offsetWidth, height: e.offsetHeight};
    },
    clear=function (e) {    	if (e.preventDefault) e.preventDefault()
    	else e.returnValue = false;
        return false;
    },
/************************************************************
 *  устанавливаем
 */
    stopdrag=function(img) {
     // грязно избавляемся от width и height в классе!!!
     // После этой операции возможен уезд крыши у дизайна
       	img.img.className=''
       	img.dragged=false;
    },

/************************************************************
 *   разМЫШления о browser-undependense
 */
    mouse_target = window,
    mouse_event = (window.execScript || window.opera)
                    ? (mouse_target = document, 'mousewheel')
                    : 'DOMMouseScroll',
    mouse_events=['mousemove','mousedown','mouseup'],
    th=this,preloads=[],
    Timer      // + нечаянная проверка на "неглобальность"

    if (this.use_mouseweel)  mouse_events.push(mouse_event)

/************************************************************
 *  местный onLoad.
 *   утверждается, что эта функция будет запущена при готовом body
 */
    this.onload= function() {

    // бегаем по всему, что шевелится и корежим ссылки!!!
    // работаем с роллером. все ссылки с #XXX дополняем doit(XXX)
        this.roller = _i(this.use_roller);
        var o= this.roller && this.roller.firstChild,
            stack=[null]
        do {
            if(!o) {            	o=stack.pop(); continue  // go up
            }        	if (o.href) {        		var t=o.href.replace(/.*#/,'').toLowerCase()
        		if (this.hstore[t]) {                    add_Handler(o,'mousedown',apply_Func(this,'doit',t));
                    add_Handler(o,'click',function(e){return clear(e)});
        		}
        	}        	if (o.firstChild) {        		stack.push(o.nextSibling);
        		o=o.firstChild; // go deep
        	} else
        	    o=o.nextSibling
        } while(stack.length)
    // var cs = document.defaultView.getComputedStyle(img.img,'');
    // ставим на ожидание загрузку картинок
        var x=document.getElementsByTagName('img'),
            aimages=[];

        for(var i=x.length-1;i>=0;i--) {
        // надо бы и про композитные классы продумать
            if (typeof x[i].className =="string")
            if (x[i].className==this.zoomable) {
                x[i].galleryimg='no' // кусочек магии от IE
                //var cs = document.defaultView.getComputedStyle(x[i].parentNode,'');
                // коррекция
                x[i].parentNode.style.overflow='hidden';
                var img={ img:x[i],
                     //     preload:null,
                     //     realWidth:100,
                     //     realHeight:100,
                     //     overflow:cs.overflow,
                          marginLeft:0,
                          marginTop:0 };
                (img.preload=preloads[x[i].src]?preloads[x[i].src]:(preloads[x[i].src]=new Image())).src=x[i].src;
                aimages.push(img);
            }
        }
        // поставить ожидание загрузки картинок
        var timercheck=
        function() {
            cont=0 ; // чекаем до полного просветления...
            for (var i=0;i<aimages.length;i++) {
                var img=aimages[i]
                if (img.preload) {
                    cont++;
                    if(img.preload.complete){
                        img.realWidth=img.preload.width;
                        img.realHeight=img.preload.height;
                        stopdrag(img)
                        if (img.realWidth>0 && img.realHeight>0) {
              // "раздуть" картинку на весь доступный регион
                            img.img.height=img.img.parentNode.clientHeight;
                            img.img.widht=img.img.parentNode.clientWidth;
              // перерасчет ZoomFactor
              // так - разьезжаемся вширь!
                            img.ZoomFactor=img.img.widht/img.realWidth;
              // а вот так - вписываемся
              //    img.ZoomFactor=Math.min(img.img.height/img.realHeight,img.img.widht/img.realWidth);
                            add_Handler(img.img.parentNode,'mouseover',
                                    apply_Func(th,'mouseover',img));
                            th.apply_Zoom(1,img);
                        }
                        img.preload=null; // чистим хвосты...
                    }
                }
            }
            if (cont) setTimeout(timercheck,100); // просветлились ?
        }
        setTimeout(timercheck,50)
    }

/************************************************************
 *  устанавливаем обработчик onLoad
 */
    add_Handler(window, 'load',apply_Func(this, 'onload'));

//
    var
        hide_roller_timer,
        show_roller=function() {        	th.roller.style.visibility='visible';
            hide_roller_timer=null
        },
        hide_roller=function(noshow) {
            if (th.roller.style.visibility!='hidden')                th.roller.style.visibility='hidden';
            if (hide_roller_timer) {            	clearTimeout(hide_roller_timer);
            	hide_roller_timer = null;
            }
            if (noshow) return;
            hide_roller_timer=setTimeout(show_roller,500)
        }
/************************************************************
 *  общий обработчик мыша
 */
    this.getClick=function(e,img){
        e= (window.event) ? window.event : e;
   // мы можем болтаться по parent текущей картинки и roller'у
        for (var o= e.target || e.srcElement;
            o && o!=img.img.parentNode && o!=this.roller;
            o = o.parentNode ){}

        switch (e.type) {

        case 'mousemove':
/**
 *  для того, чтобы не потерять mouseout, мы будем его смотреть так.
 */
            if (!o) { // вылезли за границы ...
                this.mouseout(img);
            } else if (img.dragged) {
                hide_roller()
            	var x=img.startx,y=img.starty;
                this.apply_scroll(  x-(img.startx=e.clientX),
                                    y-(img.starty=e.clientY))
	        } else if (Timer && o!=this.roller)
	            this.clr()
            break
        case mouse_event: // weel!!!!!!!
/**
 *  обработчик колеса мышки - Зумм+/-
 */
            var delta =(e.detail
                        ?e.detail/3
                        :e.wheelDelta
                          ?e.wheelDelta/60
                          :0)
            if (delta) {
                hide_roller()
                this.apply_Zoom(1-0.04 * delta);
            }
            break
        case "mousedown":
            if (o==img.img.parentNode){
                hide_roller()
            	img.dragged=true
                img.startx=e.clientX
                img.starty=e.clientY // начинаем драг
                if(this.picture_dragged) img.img.className=this.picture_dragged
            }
            break
        case "mouseup":
            if(img.dragged) stopdrag(img);
            if (Timer)this.clr()
            break
    	}
        return clear(e);
    }
    // обработчик клавиатуры
    this.keyb_func=function(e,img) {
        var x=keymap[(e||window.event).keyCode]||'';
        if (this.hstore[x]) {
            hide_roller()
            this.doit(x,true);
        }
        //else alert((e||window.event).keyCode)
        return clear(e);
    }
    var handles=[]
    this.mouseover= function(img) {
        // ставим обработчик mousemove
        // показываем в правом-верхнем углу роллер
        if(this.currentimg && this.currentimg!=img)
            this.mouseout(this.currentimg);

        if (!this.currentimg) {
            var kb_owner= window.HTMLElement? window : document.body,
                mousemove_func= apply_EH(this,'getClick',img )

            stopdrag(img);
            if(this.use_keyboard)
                handles.push(add_Handler(kb_owner,'keydown',apply_EH(this,'keyb_func')))
           	for(var e in mouse_events)
                handles.push(add_Handler(mouse_target, mouse_events[e], mousemove_func))

            this.currentimg=img
            var r=getBounds(img.img.parentNode);
            if (this.roller){
                this.roller.style.left=(r.left+(r.width-this.roller.offsetWidth)+335)+'px';
                this.roller.style.top=(r.top)+'px';
                show_roller()
            }
        }
    }
/************************************************************
 *  не обработчик, на самом-то деле. Чистим все, что под руку попадет...
 */
    this.mouseout= function(img) {
        if (this.currentimg==img) // сравнение ссылок?
        {
            clear_handlers(handles)
            if (this.roller) hide_roller(true)
            this.currentimg=null;
            this.clr();
        }
    }
/************************************************************
 *  функция для развешивания на HTML элементах
 * можно писать onmousedown="__SCROLLZOOM__.doit('lt')"
 */
    this.doit=function (e,single)
    {
        if (this.currentimg && !Timer && (e= this.hstore[e]))
            if (single) {  // делаем дважды...
                this[e[0]](e[1]||0,e[2]||0);
                this[e[0]](e[1]||0,e[2]||0);
            }else
                Timer = setInterval(apply_Func(this,e[0],e[1]||0,e[2]||0), 50);
        return false;
    }
/************************************************************
 *  завершение текущей операции
 */
    this.clr=function ()
    {
        clearInterval(Timer); Timer=null;
    }

    },

/************************************************************
 *  делаем скролл на dl,dt
 */
    apply_scroll:function (dl,dt) {
        // скроллируем на определенный процент картинки ??? а именно оно надо ???
        var img=this.currentimg
        if(img){
            img.img.style.marginLeft = (img.marginLeft-=dl)+'px';
            img.img.style.marginTop =  (img.marginTop-=dt)+'px';
        }
    },

/************************************************************
 *  Зумм. Центр остается в центре
 */
    apply_Zoom : function (dx,img) {
        if (!img) img=this.currentimg;
        if (img) {
            if(dx) img.ZoomFactor*=dx;
            img.img.width=img.realWidth*img.ZoomFactor;
            img.img.height=img.realHeight*img.ZoomFactor;
            this.apply_scroll((img.img.parentNode.clientWidth/2-img.marginLeft)*(dx-1),
                              (img.img.parentNode.clientHeight/2-img.marginTop)*(dx-1)) ;
        }
    }

}).init()



