/* $Id: module-img-viewer-class-viewer.js 16503 2009-10-01 08:48:43Z jcwiklinski $ */
/**
Copyright (C) 2003-2009 AJLSM, Anaphore
Voir le fichier LICENCE
**/
/* Ce fichier de configuration fait partie de la distribution standard
de Pleade. Vous pouvez le modifier à votre guise. */
/**
 @fileoverview

 Implémentation de la classe PivViewer. Un objet PivViewer contrôle la présentation
 des images et leur manipulation dans la visionneuse.

*/

// Création de la classe
var PivViewer = Class.create();		// Utilitaire de la librairie Prototype

PivViewer.prototype = {

	IMAGETOOL_PAN: 0,
	IMAGETOOL_ZOOMIN: 1,
	IMAGETOOL_ZOOMOUT: 2,
	IMAGETOOL_ZOOMSELECT: 3,

	zoomInFactor: 1.2,			// TODO: préférences
	zoomOutFactor: 0.8333333,			// TODO: préférences
	bgImgW: null,
	cursorMoveHFactor: null,

	// TODO: à quoi sert saveDisplayMetrics, et en particulier le zoom qu'on y enregistre?

	/**
	*	Le facteur de zoom appliqué à l'image.
	*/
	currentZoom: 1.0,

	/**
	* Crée un objet PivViewer.
	* @constructor
	*/
	initialize: function(config) {
		// Redimensionnement de la visionneuse lorsque la fenêtre du
		// navigateur est redimensionnée
		YAHOO.util.Event.addListener(window, "resize", this.resize);
		if( config ){
			if( config.zoomInFactor ) this.zoomInFactor = config.zommInFactor;
			if( config.zoomOutFactor ) this.zoomoutFactor = config.zoomOutFactor;
			if( config.bgImgW ) this.bgImgW = config.bgImgW;
			if( config.cursorMoveHFactor ) this.cursorMoveHFactor = config.cursorMoveHFactor;
		}
	},

	/**
	* Initialisation des contrôles de visionnement.
	*/
	init: function() {
		// Initialisation des widgets
		this.initWidgets();
		// Initialisation du panneau d'aperçu
		this.initPnlOverview();
		// On redimensionne
		this.resize();
	},

	/**
	* Modifications à la visionneuse pour une nouvelle séries à afficher
	* @param s {PivSeries} La série d'images à afficher.
	*/
	initSeries: function(s) {
		// On conserve la série
		this.series = s;
		// Les informations et tailles liées au slider de sélection d'images
		this.slider.init(this.series);
		$("span-imageselect-last").update(this.series.getSize());
		pivCreateTooltip("txt-imageselect" + "_tt", "txt-imageselect", pivGetMessage("tt_txt_imageselect", {max: this.series.getSize()}));
		// Les informations sur le téléchargement d'images
		if ( $("txt-download-to")) {
			$("txt-download-to").setAttribute("value", this.series.getSize());	// TODO: plutôt des préférences
		}
	},

	/**
	* Redimensionne la visionneuse afin qu'elle prenne tout l'écran disponible.
	*/
	resize: function() {
		// Les parties fixes de l'écran, dont la hauteur ne peut être utilisée pour la partie image
		/* var above = getVPadding($("body")) + getHeight($("pl-pg-header"), true) + getHeight($$(".pl-pg-brdr-t")[0], true) + getHeight($("piv-body-top"), true) + getHeight($("piv-body-hmenu"), true) + Math.max(getHeight($$(".piv-body-hmenu-navigation")[0], true), getHeight($$(".piv-body-hmenu-slider")[0], true), getHeight($$(".piv-body-hmenu-download")[0], true)); */

		// La position Y de la partie à redimensionner
		var yMcolFrame = Position.page($("piv-mcol-frame"))[1];
		// La hauteur
		var hBelow = $("piv-body-bottom").getHeight();
		if ($$(".pl-pg-brdr-b")[0]!=null){
			hBelow += $$(".pl-pg-brdr-b")[0].getHeight();
		}
		if ($("pl-pg-footer")){
			hBelow += $("pl-pg-footer").getHeight();
		}
		// On fixe la hauteur du panneau de visualisation
		$("piv-mcol-frame").setStyle({height: (YAHOO.util.Dom.getClientHeight() - yMcolFrame - hBelow) + "px"});

		/* var above = getVPadding($("body"));
		above += getHeight($("pl-pg-header"), true);
		above += getHeight($$(".pl-pg-brdr-t")[0], true);
		above += getHeight($("piv-body-top"), true);
		above += getHeight($("piv-body-hmenu"), true);
		above += Math.max(getHeight($$(".piv-body-hmenu-navigation")[0], true), getHeight($$(".piv-body-hmenu-slider")[0], true), getHeight($$(".piv-body-hmenu-download")[0], true));
		var below = getHeight($("piv-body-bottom"), true) + getHeight($$(".pl-pg-brdr-b")[0], true) + getHeight($("pl-pg-footer"), true);
		setTotalHeight($("piv-mcol-frame"), YAHOO.util.Dom.getClientHeight() - above - below); */
		// On conserve les informations sur le viewport
		var vp = $("piv-mcol-frame");
		var dim = vp.getDimensions();
		var pos = Position.cumulativeOffset(vp);
		_pivViewer.viewport = {
			x: pos[0],
			y: pos[1],
			width: dim.width,
			height: dim.height,
			ratio: dim.width / dim.height
		}
	},

	/**
	* Initialise le panneau d'aperçu.
	* @private
	*/
	initPnlOverview: function() {
		// La fenêtre d'aperçu
		this.pnlOverview = new YAHOO.widget.Panel("pnl-overview", { underlay: "none", width:"200px", visible:false, constraintoviewport:true, context:["piv-mcol-frame", "br", "br"] } );
		var btn = this.btnOverview;
		this.pnlOverview.hideEvent.subscribe(function() {btn.set("checked", false);});
		var pnl = this.pnlOverview;
		this.btnOverview.subscribe("click", function() {
			if (btn.get("checked")) {
				pnl.render(); pnl.show();
			} else pnl.hide();
		});
		//this.pnlOverview.render();	// On le fait au moment de le montrer...
		// Le masque dans la fenêtre d'aperçu
		this.dndOverviewMask = new YAHOO.util.DD("pnl-overview-mask", "pnl-overview-mask", {scroll: false});
		// Pour l'instant on le bouge uniquement à la fin du drag, car sinon il semble y avoir un bogue
		this.dndOverviewMask.endDrag = this.dndOverviewMaskOnDrag;
		// La méthode à appeler pour initialiser le drag
		this.dndOverviewMask.startDrag = this.dndOverviewMaskStartDrag;
	},

	/**
	* Initialise les boutons et autres widgets similaires dans l'interface.
	* @private
	*/
	initWidgets: function() {

		// On passe tous les boutons normaux, de haut en bas
		this.btnHelp = $("btn-help");
		this.btnHelp.observe("click", _pivViewer.showHelp);
		this.btnClose = $("btn-close-window");
		if ( window.opener ) {
			$('btn-home').hide();
			this.btnOpener = $("btn-goto-opener");
		} else {
			$('btn-goto-opener').hide();
			this.btnHome = $("btn-home");
			this.btnHome.observe("click", _pivViewer.gotoHome);
		}

		this.btnMvFirst = this.createButton("btn-mv-first-span", "push", "btn-mv-first");
		this.btnMvStepBackward = this.createButton("btn-mv-stepbackward-span", "push", "btn-mv-stepbackward");
		this.btnMvPrevious = this.createButton("btn-mv-previous-span", "push", "btn-mv-previous");
		this.btnMvNext = this.createButton("btn-mv-next-span", "push", "btn-mv-next");
		this.btnMvStepForward = this.createButton("btn-mv-stepforward-span", "push", "btn-mv-stepforward");
		this.btnMvLast = this.createButton("btn-mv-last-span", "push", "btn-mv-last");
		var btnImageSelect = this.createButton("btn-imageselect-span", "push", "btn-imageselect");
		this.createButton("btn-download-span", "push", "btn-download");
		this.createButton("btn-lockzoom-span", "checkbox", "btn-lockzoom");
		this.createButton("btn-realsize-span", "push", "btn-realsize");
		this.createButton("btn-fitscreen-span", "push", "btn-fitscreen");
		this.createButton("btn-contrast-span", "push", "btn-contrast");
		this.createButton("btn-rotate-span", "push", "btn-rotate");
		this.btnOverview = this.createButton("btn-overview-span", "checkbox", "btn-overview");
		if( _viewer_actives.mosaic ) {
		this.btnMosaic = this.createButton("btn-mosaic-span", "checkbox", "btn-mosaic");
			this.btnMosaic.set("disabled", true);
			this.btnMosaic.subscribe("click", this.handleBtnMosaicClick, this.btnMosaic);
		}

		// Groupe de boutons exclusifs pour les outils de manipulation d'images (inutile de déclarer chacun)
		this.btngrpImageTools = new YAHOO.widget.ButtonGroup("btngrp-imagetools");
		// On associe un gestionnaire d'événement à chaque bouton du groupe
		var btns = this.btngrpImageTools.getButtons();
		for (i=0; i<btns.length; i++) {
			btns[i].on("checkedChange", this.imageToolsChanged, btns[i], this);
			if( btns[i]._button.id == 'btn-pan' ){
				var _title = btns[i]._button.title;
				btns[i].set("checked", true);
				btns[i].set("title", _title);
			}
		}
		// On doit aussi définir leurs tooltips
		pivCreateTooltip("btn-pan" + "_tt", "btn-pan");
		pivCreateTooltip("btn-zoomin" + "_tt", "btn-zoomin");
		pivCreateTooltip("btn-zoomout" + "_tt", "btn-zoomout");
		pivCreateTooltip("btn-zoomselect" + "_tt", "btn-zoomselect");

		// Les boutons de type menu
		this.mnuVersions = new YAHOO.widget.Menu("mnu-versions", {lazyLoad: true});
		this.btnVersions = this.createButton("btn-versions-span", "menu", "btn-versions", null, this.mnuVersions);
		this.mnuPrint = new YAHOO.widget.Menu("mnu-print", {lazyLoad: true});
		this.createButton("btn-print-span", "menu", "btn-print", null, this.mnuPrint);

		// Les bulles d'aide pour les widgets qui ne sont pas des boutons
		pivCreateTooltip("txt-download-from" + "_tt", "txt-download-from");
		pivCreateTooltip("txt-download-to" + "_tt", "txt-download-to");
		pivCreateTooltip("rad-download-type-pdf" + "_tt", "rad-download-type-pdf");
		pivCreateTooltip("rad-download-type-zip" + "_tt", "rad-download-type-zip");
		pivCreateTooltip("link-pleade" + "_tt", "link-pleade");

		// Le slider pour les images et son fonctionnement général
		this.slider = new PivImageSelectSlider( { bgImgW: this.bgImgW, cursorMoveHFactor: this.cursorMoveHFactor } );
		pivCreateTooltip("btn-imageselect" + "_tt", "btn-imageselect", pivGetMessage("tt_btn_imageselect", {no: $F("txt-imageselect")}));
		// TODO: le change a seulement lieu après la perte de focus, donc très souvent le tooltip n'est pas bon
		YAHOO.util.Event.addListener("txt-imageselect", "change", function() {pivCreateTooltip("btn-imageselect" + "_tt", "btn-imageselect", pivGetMessage("tt_btn_imageselect", {no: $F("txt-imageselect")}));});

		// Le panneau d'indication de chargement des images
		this.pnlLoading = $("pnl-loading");
		pivCenterOnScreen(this.pnlLoading);

    /* TODO (MP) : En faire un panneau pour les messages d'erreur ou autres.
     * Donc un panneau dont le texte doit etre changeable et regulierement mis a
     * jour.*/
		this.pnlNoImg = $("pnl-no-img");
		pivCenterOnScreen(this.pnlNoImg);

		// Le panneau qui entoure l'image
		this.pnlImage = $("pnl-picture");

		// Le panneau de zoom avec sélection
		this.pnlZoomSelect = $("pnl-zoomselect");

		// Si une seule image, alors on désactive la barre de navigation (on la cache)
		if ( _pivSeries!=null && _pivSeries.getSize() < 2 ) {
			$$('div.piv-body-hmenu-navigation').invoke('hide');
			$$('div.piv-body-hmenu-slider').invoke('hide');
		}
	},

	/**
	*	Fonction appelée lorsque le masque de l'aperçu commence à être déplacé.
	*/
	dndOverviewMaskStartDrag: function(x, y) {
		// La position du masque
		var maskPos = Position.positionedOffset($("pnl-overview-mask"));
		// On conserve les coordonnées d'origine du masque
		//_pivViewer.overviewMaskPosition = {originalX: maskPos[0], originalY: maskPos[1], f: _pivViewer.currentZoom / ($("pnl-overview-img").width / $("img-main").width )};
		_pivViewer.overviewMaskPosition = {originalX: maskPos[0], originalY: maskPos[1], f: 1.0 / ($("pnl-overview-img").width / $("img-main").width )};
	},

	/**
	*	Fonction appelée lorsque le masque de l'aperçu est déplacé. On doit ajuster
	*	la position de l'image principale en fonction du rectangle correspondant
	*	au masque.
	*	Pour l'instant, on l'appelle lors du endDrag car il semble que l'appel
	*	lors du onDrag produit des effets indésirables.
	*/
	dndOverviewMaskOnDrag: function(e) {
		// La position du masque
		var maskPos = Position.positionedOffset($("pnl-overview-mask"));
		// L'image affichée
		var img = $("img-main");
		// Les déplacements effectués, en coordonnées de l'image affichée
		var diffX = _pivViewer.overviewMaskPosition.f * (maskPos[0] - _pivViewer.overviewMaskPosition.originalX);
		var diffY = _pivViewer.overviewMaskPosition.f * (maskPos[1] - _pivViewer.overviewMaskPosition.originalY);
		// La position actuelle de l'image
		var imgPos = Position.positionedOffset(img);
		// On déplace l'image
		img.setStyle({left: (imgPos[0] - diffX) + "px", top: (imgPos[1] - diffY) + "px"});
		// On lance les traitements de fin de déplacement
		_pivViewer.visibleImageChanged(false);
	},

	dndImageZoomSelectStartDrag: function(x, y) {
		_pivViewer.zoomSelect = {originalX: x, originalY: y};
		_pivViewer.pnlZoomSelect.setStyle({left: x + "px", top: y + "px", width: "1px", height: "1px"});
		_pivViewer.pnlZoomSelect.show();
	},
	dndImageZoomSelectOnDrag: function(e) {
		var diffX = Event.pointerX(e) - _pivViewer.zoomSelect.originalX;
		var x = diffX > 0 ? _pivViewer.zoomSelect.originalX : Event.pointerX(e);
		var diffY = Event.pointerY(e) - _pivViewer.zoomSelect.originalY;
		var y = diffY > 0 ? _pivViewer.zoomSelect.originalY : Event.pointerY(e);
		var w = Math.abs(diffX);
		var h = Math.abs(diffY);
		var r = w / y;
		// TODO: ajuster le ratio?
		/* if (r > _pivViewer.viewport.ratio) h = _pivViewer.viewport.ratio * h;
		else w = _pivViewer.viewport.ratio * w; */
		_pivViewer.pnlZoomSelect.setStyle({left: x + "px", top: y + "px", width: w + "px", height: h + "px"});
	},

	/**
	*	Fin du drag du zoom select : on doit faire un zoom sur la sélection.
	*	@param e {Event} L'événement lié à la fin de ce drag.
	*/
	dndImageZoomSelectEndDrag: function(e) {
		var pnlDim = _pivViewer.pnlZoomSelect.getDimensions();
		var pnlX = parseInt(_pivViewer.pnlZoomSelect.getStyle("left"));
		var pnlY = parseInt(_pivViewer.pnlZoomSelect.getStyle("top"));
		var img = $("img-main");
		var imgX = parseInt(img.getStyle("left"));
		var imgY = parseInt(img.getStyle("top"));
		var vp = _pivViewer.viewport;
		var rect = pivScreenToImage({x: pnlX - vp.x, y: pnlY - vp.y, width: pnlDim.width, height: pnlDim.height}, {x: imgX, y: imgY}, _pivViewer.imageDimensions, _pivViewer.currentZoom);
		_pivViewer.displayRect(rect);
		//_pivViewer.zoomSelect(parseInt(_pivViewer.pnlZoomSelect.getStyle("left")), parseInt(_pivViewer.pnlZoomSelect.getStyle("top")), dim.width, dim.height);
		_pivViewer.pnlZoomSelect.hide();
	},

	/**
	*	Méthode appelée pendant le drag de l'image principale (pan).
	*/
	dndImagePanOnDrag: function(e) {
		_pivViewer.visibleImageChanged();
	},

	/**
	*	Méthode appelée à la fin d'un drag de l'image principale (pan).
	*/
	dndImagePanEndDrag: function(e) {
		_pivViewer.visibleImageChanged();
	},

	updateSlider: function(no) {
		this.slider.update(no);
	},

	/**
	*	Affiche une partie de l'image, soit un rectangle r, de manière à ce
	*	qu'il entre complètement à l'écran.
	*/
	displayRect: function(r) {
		// r est en coordonnées images
		var vp = _pivViewer.viewport;
		var id = _pivViewer.imageDimensions;
		var zoom = Math.min(vp.width / r.width, vp.height / r.height);
		$("img-main").setStyle({left: -r.x*zoom + "px", top: -r.y*zoom + "px", width: id.width*zoom + "px", height: id.height*zoom+ "px"});
		_pivViewer.currentZoom = zoom;
		_pivViewer.visibleImageChanged();
	},

	/**
	*	Ajuste la taille de l'image pour qu'elle entre entièrement à l'écran, en position
	*	0,0.
	*/
	fitScreen: function() {
		var id = _pivViewer.imageDimensions;
		_pivViewer.displayRect({x: 0, y: 0, width: id.width, height: id.height});
		_pivViewer.visibleImageChanged();
	},

	realSize: function() {
		$("img-main").setStyle({left: "0px", top: "0px", width: _pivViewer.imageDimensions.width + "px", height: _pivViewer.imageDimensions.height + "px"});
		_pivViewer.currentZoom = 1.0;
		_pivViewer.visibleImageChanged();
	},

	onButtonClick: function(evt, viewer) {
		if(viewer == undefined) viewer = _pivViewer;
		// Appelée sur chaque click d'un bouton
		// Le this est le bouton, le viewer est le PivViewer
		var id = this.get("id");
		switch (this.get("id")) {
			case "btn-realsize-span":
				viewer.realSize();
				break;
			case "btn-fitscreen-span":
				viewer.fitScreen();
				break;
			case "btn-lockzoom-span":
				viewer.lockZoom = this.get("checked");
				break;
			case "btn-imageselect-span":
				viewer.doImgSelectSpan();
				break;
			case "btn-mv-first-span":
				viewer.gotoFirst();
				break;
			case "btn-mv-stepbackward-span":
				viewer.gotoStepBackward();
				break;
			case "btn-mv-previous-span":
				viewer.gotoPrevious();
				break;
			case "btn-mv-next-span":
				viewer.gotoNext();
				break;
			case "btn-mv-stepforward-span":
				viewer.gotoStepForward();
				break;
			case "btn-mv-last-span":
				viewer.gotoLast();
				break;
			case "btn-close-window-span":
				window.close();
				break;
			case "btn-goto-opener-span":
				if ( window.opener ) window.opener.focus();
				break;
			case "btn-rotate-span":
				viewer.rotate(90); // TODO (MP) : rotation horaire 90°
				break;
			case "btn-download-span":
				var format = "zip";
				if( $('rad-download-type-pdf')!=null && $('rad-download-type-pdf').checked==true ) { format = "pdf"; }
				// TODO (MP) : controle sur le nombre d'images a exporter
				pivDoDownload( format, /*first*/$F("txt-download-from"), /*last*/$F("txt-download-to") );
				break;
			case "btn-help-span":
					var newWin = window.open(_pivViewer.baselink + "pages/help-viewer.html", "help");
					newWin.focus();
				break;
		}
	},

	/**
	*	Fait les ajustements nécessaires en fonction du bouton sélectionné.
	*
	*	@param btn {YAHOO.widget.Button} Le bouton sélectionné.
	*/
	updateImageTools: function(btn) {
		var cursor = "auto";
		var id = "btn-pan-span";
		if (btn) id = btn.get("id");
		else {
			var b = _pivViewer.btngrpImageTools.get("checkedButton");
			if (b) id = b.get("id");
		}
		_pivViewer.currentImageTool = _pivViewer.IMAGETOOL_PAN;		// Default
		switch (id) {
			case "btn-pan-span":
				cursor = "move";
				_pivViewer.currentImageTool = _pivViewer.IMAGETOOL_PAN;
				if ( _pivViewer.dndImageZoomSelect ) _pivViewer.dndImageZoomSelect.lock();
				if ( _pivViewer.dndImagePan ) _pivViewer.dndImagePan.unlock();
				break;
			case "btn-zoomin-span":
				_pivViewer.currentImageTool = _pivViewer.IMAGETOOL_ZOOMIN;
				//cursor = "-moz-zoom-in";// FIXME (MP): mauvaise idee de selectionner une image mozilla que ie ne sera pas capable de servir !
				if ( _pivViewer.dndImagePan ) _pivViewer.dndImagePan.lock();
				if ( _pivViewer.dndImageZoomSelect ) _pivViewer.dndImageZoomSelect.lock();
				break;
			case "btn-zoomout-span":
				_pivViewer.currentImageTool = _pivViewer.IMAGETOOL_ZOOMOUT;
				// cursor = "-moz-zoom-out";// FIXME (MP): mauvaise idee de selectionner une image mozilla que ie ne sera pas capable de servir !
				if ( _pivViewer.dndImagePan ) _pivViewer.dndImagePan.lock();
				if ( _pivViewer.dndImageZoomSelect ) _pivViewer.dndImageZoomSelect.lock();
				break;
			case "btn-zoomselect-span":
				_pivViewer.currentImageTool = _pivViewer.IMAGETOOL_ZOOMSELECT;
				cursor = "crosshair";
				var tmp = _pivViewer.dndImageZoomSelect;
				if ( _pivViewer.dndImagePan ) _pivViewer.dndImagePan.lock();
				if ( _pivViewer.dndImageZoomSelect ) _pivViewer.dndImageZoomSelect.unlock();
				break;
		}
		if ($("img-main")) $("img-main").setStyle({cursor: cursor});
	},

	/**
	* Méthode qui gère le changement d'outil de manipulation d'image sélectionné.
	*/
	imageToolsChanged: function(evt, btn) {
		_pivViewer.updateImageTools(btn);
	},

	/**
	* Création d'un bouton YUI, avec bulle d'aide.
	* @private
	* @param id {String} Identifiant de l'élément HTML qui définit l'ensemble du bouton (normalement un span)
	* @param type {String} Le type de bouton YUI (optionnel, par défaut "push")
	* @param ttId {String} Identifiant de l'objet qui recevra la bulle d'aide (optionnel, si absent aucune bulle d'aide ne sera ajoutée)
	* @param ttText {String} Texte de la bulle d'aide (optionnel, si absent ce sera l'attribut title de l'élément ttId qui sera utilisé)
	* @param mnu {YAHOO.widget.Menu} Si le type de bouton est "menu", ce menu sera passé en configuration du bouton (optionnel)
	* @return Le bouton YUI créé
	* @type YAHOO.widget.Button
	*/
	createButton: function(id, type, ttId, ttText, mnu) {
		// On le crée uniquement si l'élément existe
		if ($(id)) {
			// Gestion du type par défaut
			if ( !type ) type = "push";
			// Le bouton lui-même
			var b;
			if ( type != "menu" ) b = new YAHOO.widget.Button(id, { type: type, onclick: {fn: this.onButtonClick, obj: this} });
			else b = new YAHOO.widget.Button(id, { type: type, menu: mnu, onclick: {fn: this.onButtonClick, obj: this} });
			// La bulle d'aide si nécessaire
			if ( ttId && $(ttId) ) {
				pivCreateTooltip(ttId + "_tt", ttId, ttText);
			}
			// On retourne le bouton créé
			return b;
		}
		return null;
	},

	/*
	*	Se déplace à une certaine image.
	*	@param n {int} Le numéro de l'image dans la série
	*	@param updateSlider {boolean} Indique si on doit ajuster le slider (par défaut vrai)
	*/
	gotoImage: function(n, updateSlider) {
		this.uSlider = true;
		if (updateSlider == false) this.uSlider = false;
		// On affiche le message de chargement de l'image
		this.startLoading();
		// On stocke ou initialise les caractéristiques d'affichage de l'image
		this.saveDisplayMetrics();
		// On appelle le chargement des informations sur l'image
		_pivSeries.fetchImageSet(n, this.displayImage);
	},

	/*
	*	Se déplace à une certaine image.
	*	@param n {string} Le nom de l'image
	*	@param updateSlider {boolean} Indique si on doit ajuster le slider (par défaut vrai)
	*/
	gotoNamedImage: function(n, updateSlider) {
		this.uSlider = true;
		if (updateSlider == false) this.uSlider = false;
		// On affiche le message de chargement de l'image
		this.startLoading();
		// On stocke ou initialise les caractéristiques d'affichage de l'image
		this.saveDisplayMetrics();
		// On appelle le chargement des informations sur l'image
		_pivSeries.fetchNamedImageSet(n, this.displayImage);
	},

	_updateAfterChange: function(){
		_pivViewer.currentImageNo = _pivSeries.getCurrentImage();	// TODO: est-ce trop tôt?
		if ( _pivViewer.uSlider ) _pivViewer.slider.update(_pivSeries.getCurrentImage());
		_pivViewer.adjustMoveButtons();
	},

	/**
	*	Affiche une version de l'image.
	*	@param set {PivImageSet} Le jeu d'images concerné
	*	@param {PivImageVersion} La version de l'image à afficher
	*/
	gotoVersion: function(set, version) {
		// On affiche le message de chargement de l'image
		_pivViewer.startLoading();
		// On stocke ou initialise les caractéristiques d'affichage de l'image
		_pivViewer.saveDisplayMetrics();
		// On appelle le chargement de l'image
		_pivViewer.displayImage(set, version);
	},

	/**
	*	Rotation de l'image
	*	@param angle {float} L'angle de rotation : +90, -90, +180
	*/
	rotate: function( angle ) {
		// Informe l'image qu'elle subira une rotation
		if ( ( angle > 0 ) && ( angle <= 360 ) ) {
			if ( _pivSeries!=null ) {
				var set = _pivSeries.getCurrentImageSet();
				var image = ( (set) ? set.getCurrentVersion() : null );
				if ( image!=null ) {
					var _v = null;
					if ( angle < 0 ) { _v = 360 + angle; }
					else { _v = angle; }
					image.setRotation( _v );
					// On affiche le message de chargement de l'image
					_pivViewer.startLoading();
					// On stocke ou initialise les caractéristiques d'affichage de l'image
					_pivViewer.saveDisplayMetrics();
					// L'élément img pour l'image
					var img = $(document.createElement("img"));
					img.setAttribute("id", "img-main");
					// On enregistre le onClick pour le zoom, pan, etc.
					img.observe("click", _pivViewer.imageClick);
					// On enregistre le handler pour identifier la fin du chargement de l'image
					var handler = _pivViewer.imageLoaded(img, image, set);
					img.observe("load", handler);
					// Maintenant l'URL de l'image, ce qui déclenche son chargement
					var imgUrl = image.getUrl();
					img.setAttribute( "src", imgUrl );
				}
			}
		}
	},

	/**
	*	Sauvegarde ou réinitialise les valeurs actuelles de consultation
	*	de l'image : position, zoom, etc.
	*/
	saveDisplayMetrics: function() {
		var img = $("img-main");
		if ( img ) {
			var dimensions = img.getDimensions();
			if (_pivViewer.lockZoom) {
				_pivViewer.displayMetrics = {
					screenX: parseInt(img.getStyle("left")),		// Le décalage de l'image en horizontal (pan)
					screenY: parseInt(img.getStyle("top"))			// Le décalage de l'image en vertical (pan)
				}
				if ( _pivViewer.imageDimensions && _pivViewer.imageDimensions.width ) _pivViewer.displayMetrics.zoom = dimensions.width / _pivViewer.imageDimensions.width;
				else _pivViewer.displayMetrics.zoom = 1.0;
			}
			else _pivViewer.displayMetrics = _pivViewer.resetDisplayMetrics();
			_pivViewer.displayMetrics.screenWidth = dimensions.width;
			_pivViewer.displayMetrics.screenHeight = dimensions.height;
		}
		else _pivViewer.displayMetrics = _pivViewer.resetDisplayMetrics();
	},

	/**
	*	Réinitialise les valeurs d'affichage en utilisant les valeurs par défaut.
	*	@return {Object} Un objet qui contient les valeurs par défaut.
	*/
	resetDisplayMetrics: function() {
		return {
			screenX: 0,		// On débute en haut à gauche par défaut
			screenY: 0,
			screenWidth: 0,	// TODO: bonnes valeurs par défaut?
			screenHeight: 0,
			zoom: 1.0		// On débute avec sans zoom
		}
	},

	/**
	 *	Affiche l'image demandée par l'utilisateur via la zone de texte
	 */
	doImgSelectSpan: function() {
		var n = $F("txt-imageselect"); // la valeur entree
		var max = _pivViewer.series.getSize(); // la valeur maxi (ie, le nombre maxi d'image dans la serie)
		if ( n<0 ) n = 0; // pas numero negatif
		else if( n>max ) n = max; // pas de numero superieur au nombre maxi d'images dans la serie
		_pivViewer.gotoImage(n, true); // active l'affichage de l'image
		// mise a jour de la bulle d'aide
		pivCreateTooltip( "btn-imageselect" + "_tt", "btn-imageselect" , pivGetMessage("tt_btn_imageselect", {no: $F("txt-imageselect")}) );
	},

	/**
	 *	Méthode marquant la fin de l'action du slider (mise à jour logicielle ou
	 *	manuelle).
	 * a) Source = 1 :
	 *		Mise à jour manuelle, l'utilisateur à déplacer le curseur.
	 *		Déclenche l'affichage de l'image.
	 *		Mise à jour de la zone de saisie du numéro de l'image suivant le numéro
	 *		donné par le Slider.
	 * b) Source = 2 :
	 *		Mise à jour logicielle, une méthode a appelé le setValue() du Slider
	 *		Mise à jour de la zone de saisie du numéro de l'image suivant le numéro
	 *		donné par la Série d'images.
	 */
	sliderEnd: function(e) {
		var source = _pivViewer.slider.xslider.valueChangeSource;
		var n = null;
		n = ( ( source == 1 ) ?  _pivViewer.slider.getImageNo() : _pivSeries.getCurrentImage() );
		// mise a jour de la zone de saisie et sa bulle d'aide
		$("txt-imageselect").value = n;
		pivCreateTooltip("btn-imageselect" + "_tt", "btn-imageselect", pivGetMessage("tt_btn_imageselect", {no: $F("txt-imageselect")}));
		if ( source == 1 ) {	// Si source = 1, alors on sait que ça vient du déplacement du curseur, et non d'un setValue
			//alert(_pivViewer.slider.pixelsPerImage + " ; " + _pivViewer.slider.xslider.getValue() + " ; " + _pivViewer.slider.getImageNo());
			_pivViewer.gotoImage(n, false);
		}
	},

	/**
	*	Gère le click sur le bouton de mosaïque.
	*	@param evt L'événement (pas très intéressant)
	*	@param btn Le bouton mosaic
	*/
	handleBtnMosaicClick: function(evt, btn) {
		// On active la mosaïque

		// Un booléen pour dire d'afficher les grilles
		// On se base sur l'etat "checked" du bouton pour le savoir
		_pivViewer.showMosaicGrid = btn.get("checked");

		// On (dés)active les boutons zoom, déplacement et redimensionnement
		var btns = _pivViewer.btngrpImageTools.getButtons();
		for (i=0; i<btns.length; i++) {
			btns[i].set("disabled", btn.get("checked"));
		}

		// Le jeu d'images
		var set = _pivSeries.getCurrentImageSet();

		// On affiche la version par défaut
		_pivViewer.gotoVersion(set, set.getDefaultVersion());
	},

	/**
	*	Ajuste le bouton de mosaïque pour l'activer uniquement si une mosaïque est disponible.
	*/
	adjustMosaicButton: function(set) {
		_pivViewer.btnMosaic.set("disabled", !set.hasMosaic());
	},

	/**
	*	Ajuste les boutons de déplacement, en les (dés)activant selon l'image affichée.
	*/
	adjustMoveButtons: function() {
		///FIXME: un hover apparaît parfois, set("hover", false) ne semble rien faire. Visible dans IE6 et FF.
		if ( this.currentImageNo == _pivSeries.getSize() ) {
			this.btnMvFirst.set("disabled", false);
			this.btnMvStepBackward.set("disabled", false);
			this.btnMvPrevious.set("disabled", false);
			this.btnMvNext.set("disabled", true);
			this.btnMvStepForward.set("disabled", true);
			this.btnMvLast.set("disabled", true);
		}
		else if (this.currentImageNo == 1) {
			this.btnMvFirst.set("disabled", true);
			this.btnMvStepBackward.set("disabled", true);
			this.btnMvPrevious.set("disabled", true);
			this.btnMvNext.set("disabled", false);
			this.btnMvStepForward.set("disabled", false);
			this.btnMvLast.set("disabled", false);
		}
		else {
			this.btnMvFirst.set("disabled", false);
			this.btnMvStepBackward.set("disabled", false);
			this.btnMvPrevious.set("disabled", false);
			this.btnMvNext.set("disabled", false);
			this.btnMvStepForward.set("disabled", false);
			this.btnMvLast.set("disabled", false);
		}
	},

	gotoFirst: function() {
		this.gotoImage(1);
	},
	gotoStepBackward: function() {
		this.gotoImage( (this.currentImageNo - _pivSeries.getStepSize()) >= 1 ? (this.currentImageNo - _pivSeries.getStepSize()) : 1);
	},
	gotoPrevious: function() {
		this.gotoImage( (this.currentImageNo - 1) >= 1 ? (this.currentImageNo - 1) : 1);
	},
	gotoNext: function() {
		this.gotoImage( (this.currentImageNo + 1) <= _pivSeries.getSize() ? (this.currentImageNo + 1) : _pivSeries.getSize());
	},
	gotoStepForward: function() {
		this.gotoImage( (this.currentImageNo + _pivSeries.getStepSize()) <= _pivSeries.getSize() ? (this.currentImageNo + _pivSeries.getStepSize()) : _pivSeries.getSize());
	},
	gotoLast: function() {
		this.gotoImage(_pivSeries.getSize());
	},

	/**
	*	Gère le clic sur l'image.
	*	@param e {Event} L'événement.
	*/
	imageClick: function(e) {
		switch (_pivViewer.currentImageTool) {
			case _pivViewer.IMAGETOOL_PAN:
				break;
			case _pivViewer.IMAGETOOL_ZOOMIN:
				_pivViewer.zoomIn();
				break;
			case _pivViewer.IMAGETOOL_ZOOMOUT:
				_pivViewer.zoomOut();
				break;
		}
	},

	/**
	*	Gère le mousedown sur l'image, pour le zoom select.
	*/
	imageMouseDown: function(e) {
		switch (_pivViewer.currentImageTool) {
			case _pivViewer.IMAGETOOL_ZOOMSELECT:
				_pivViewer.pnlZoomSelect.startSelect({x: Event.pointerX(e), y: Event.pointerY(e)});
				break;
		}
	},

	/**
	*	Effectue un zoom avant dans l'image.
	*/
	zoomIn: function() {
		_pivViewer.saveDisplayMetrics();
		// TODO: centrer sur le point ?
		$("img-main").setStyle({width: (_pivViewer.zoomInFactor * _pivViewer.displayMetrics.screenWidth) + "px", height: (_pivViewer.zoomInFactor * _pivViewer.displayMetrics.screenHeight) + "px"});
		_pivViewer.currentZoom = _pivViewer.currentZoom * _pivViewer.zoomInFactor;
		_pivViewer.visibleImageChanged();
	},

	zoomOut: function() {
		_pivViewer.saveDisplayMetrics();
		// TODO: centrer sur le point ?
		$("img-main").setStyle({width: (_pivViewer.zoomOutFactor * _pivViewer.displayMetrics.screenWidth) + "px", height: (_pivViewer.zoomOutFactor * _pivViewer.displayMetrics.screenHeight) + "px"});
		_pivViewer.currentZoom = _pivViewer.currentZoom * _pivViewer.zoomOutFactor;
		_pivViewer.visibleImageChanged();
	},

	/**
	*	Effectue un zoom sur une région spécifique de l'image.
	*/
	zoomSelect: function(x, y, width, height) {
		//var size = pivCrop({width, height}, _pivViewer.viewport);
	},

	/**
	* Va chercher le titre du document auquel est rattachée l'image
	* @param img_p {string} Le chemin vers l'image
	*/
	getImageInfos: function(img_p) {
		// On supprime le chemin vers le serveur d'images
		img_p = img_p.substring( _pivSeries.serverUrl.length+1 );
		var reg1 = new RegExp("^_ead/functions/ead/relative");
		if (img_p.match(reg1)) {
				//dans le cas d'une image relative, on ne récupère que la fin de l'url
				img_p = img_p.substring( img_p.lastIndexOf("/")+1 );
		}
		var reg2 = new RegExp("[\?]");
		if (img_p.match(reg2)) {
			//dans le cas d'une image retaillée ou autre, on ne veut que le nom de l'image
			img_p = img_p.substring(0, img_p.indexOf("?"));
		}

		var s_path = this.baselink + 'functions/ead/infosimage.ajax';
			var handler = this._fetchImageInfos();
			new Ajax.Request(s_path, {
				method: "GET",
				parameters: {path: img_p, baselink: this.baselink},
				onSuccess: handler
				//TODO: erreur?
			});
	},

	_fetchImageInfos: function() {
		return function(r) {
			var result = r.responseText;
			if($('piv-localisation')!=null) {
				$('piv-localisation').update(result);
			}
		}
	},

	/*
	*	Affiche une image à l'écran.
	*	@param set {PivImageSet} Le jeu d'images concerné
	*	@param {PivImageVersion} La version de l'image à afficher
	*/
	displayImage: function(set, image) {
		// On conserve les informations sur le set et la version affichée
		set.setCurrentVersion(image);
		// L'élément img pour l'image
		var img = $(document.createElement("img"));
		img.setAttribute("id", "img-main");
		// On enregistre le onClick pour le zoom, pan, etc.
		img.observe("click", _pivViewer.imageClick);
		// On enregistre le handler pour identifier la fin du chargement de l'image
		var handler = _pivViewer.imageLoaded(img, image, set);
		img.observe("load", handler);
		// Maintenant l'URL de l'image, ce qui déclenche son chargement
		var imgUrl = image.getUrl();
		img.setAttribute( "src", imgUrl );
		//récupère puis affiche des informations sur le document dont dépend l'image
		if($('piv-localisation')!=null){
			//on veut l'url de l'image originale ici !
			_pivViewer.getImageInfos( set.originalVersion.getUrl() );
		}
	},

	/**
	*	Initialise le menu d'impression.
	*	@param image {PivImageVersion} La version de l'image affichée
	*/
	resetMnuPrint: function(image) {
		// Le menu
		var m = _pivViewer.mnuPrint;
		// On le vide pour le reconstruire
		m.clearContent();
		// Un lien vers l'impression de l'image complète
		var _pdf_url =  this.baselink + "img-server/" + _pivSeries.getSummary() + "/" + image.getNameWithoutQueryString() + ".pdf";
		var item = new YAHOO.widget.MenuItem("dummy", {text: pivGetMessage("mnu_print_complete_text"), url: _pdf_url, target: "_blank"});
		m.addItem(item);
		// Un lien vers l'impression de l'image telle qu'affichée (le lien sera complété dans une autre méthode)
		item = new YAHOO.widget.MenuItem("dummy", {text: pivGetMessage("mnu_print_asviewed_text"), url: _pdf_url, target: "_blank"});
		m.addItem(item);
		// On reconstruit le menu visuellement
		m.render("btn-print");
		// On met à jour le second item pour l'image telle qu'affichée
		_pivViewer.updateMnuPrintAsViewed();
	},

	/**
	*	Met à jour le menu d'impression de l'image telle qu'affichée à l'écran.
	*	@param image {PivImageVersion} La version de l'image à afficher
	*/
	updateMnuPrintAsViewed: function() {
		var item = _pivViewer.mnuPrint.getItem(1);	// Second item toujours
		// L'URL sans les paramètres
		var url = item.cfg.getProperty("url");
		if ( url.indexOf("?") > -1 ) url = url.substring(0, url.indexOf("?"));
		// La partie visible
		var rect = _pivSeries.getCurrentImageSet().getOriginalRegion(_pivViewer.getVisibleRegion());
		// On fixe la nouvelle URL
		var image = _pivSeries.getCurrentImageSet().getCurrentVersion();
		item.cfg.setProperty("url", url + "?s=" + _pivSeries.getSummary() + "/" + image.getNameWithoutQueryString() + "&x=" + Math.round(rect.x1) + "&y=" + Math.round(rect.y1) + "&w=" + Math.round(rect.x2 - rect.x1) + "&h=" + Math.round(rect.y2 - rect.y1) + ( (image.getRotation() > 0 && image.getRotation() < 360) ? '&r='+image.getRotation():'' ) );
	},


	/**
	*	Initialise le menu des versions.
	*	@param set {PivImageSet} Le jeu d'images concerné
	*	@param {PivImageVersion} La version de l'image à afficher
	*/
	resetMnuVersions: function(set, image) {
		// On fait quelque chose uniquement si le nombre de versions
		// est plus grand que 1
		if ( set.getVersions().size() > 1 ) {
			// On s'assure que le bouton des versions est actif
			_pivViewer.btnVersions.set("disabled", false);
			// Un raccourci
			var m = _pivViewer.mnuVersions;
			// On vide le menu
			m.clearContent();
			// On boucle sur les versions pour construire le menu
			var versions = set.getVersions();
			for (i=0; i<versions.size(); i++) {
				var v = versions[i];
				//var checked = (v.getUrl() == image.getUrl());
				var checked = (v.getUrl() == image.getUrl());
				// FIXME: il serait intéressant d'avoir helptext et url, mais helptext cause des problèmes d'affichage
				// et url fait en sorte que cette URL est vraiment appelée (comment stopper l'événement?)
				var item = new YAHOO.widget.MenuItem(v.getSummary(), {checked: checked, text: v.getSummary(), onclick: {fn: _pivViewer.handleMnuVersionsClick, obj: v, scope: set}});
				m.addItem(item);
			}
			// On doit s'assurer que le menu est bien reconstruit visuellement
			m.render("btn-versions");
		}
		else {
			// Si pas plus d'une version, alors on désactive le bouton
			_pivViewer.btnVersions.set("disabled", true);
		}
	},

	/**
	*	Méthode appelée lorsqu'il y a un clic sur un item du menu des versions.
	*	Le scope (this) est le PivImageSet.
	*	@param eventType {String} Le type d'événement qui a déclenché
	*	@param event {Event} L'événement
	*	@param version {PivImageVersion} La version de l'image à afficher
	*/
	handleMnuVersionsClick: function(eventType, event, version) {
		// On appele tout simplement l'affichage de l'image
		_pivViewer.gotoVersion(this, version);
	},

	/**
	*	Modifie les informations affichées dans la barre de statut.
	*/
	updateStatus: function() {
		var r = _pivViewer.getVisiblePart();
		// TODO: i18n
		var vRegion = _pivViewer.getVisibleRegion();
		///pivSetStatus(_pivViewer.imageStatus + " \u2022 Zoom : " + Math.floor(100 * _pivViewer.currentZoom) + "%" + " \u2022 Partie visible : (" + Math.round(vRegion.x1) + "," + Math.round(vRegion.y1) + "," + Math.round(vRegion.x2) + "," + Math.round(vRegion.y2) + ")");
		pivSetStatus(_pivViewer.imageStatus + " \u2022 Zoom : " + Math.floor(100 * _pivViewer.currentZoom) + "%");
	},

	/**
	*	Fonction appelée lorsque la partie visible de l'image a été modifiée.
	*/
	visibleImageChanged: function(updateOverview) {
		// On vérifie si on doit mettre à jour l'aperçu, par défaut oui
		var uOverview = true;
		if ( arguments.length > 0 ) uOverview = updateOverview;
		// On s'assure que le masque est toujours correctement positionné
		// TODO: uniquement s'il est visible?
		if ( uOverview) _pivViewer.setOverviewMaskSize();
		// On doit ajuster la barre de statut
		_pivViewer.updateStatus();
		// On doit ajuster le menu d'impression de l'image telle qu'affichée à l'écran
		_pivViewer.updateMnuPrintAsViewed();
	},


	/**
	*	Retourne les coordonnées du rectangle visible de l'image.
	*/
	getVisiblePart: function() {
		var vp = this.viewport;
		var img = $("img-main");
		var imgX = parseInt(img.getStyle("left"));
		var imgY = parseInt(img.getStyle("top"));
		var rect = pivScreenToImage({x: vp.x, y: vp.y, width: vp.width, height: vp.height}, {x: imgX, y: imgY}, this.imageDimensions, this.currentZoom);
		return rect;
	},

	/**
	*	Fonction qui effectue les derniers réglages une fois l'image chargée.
	*/
	imageLoaded: function(img, imageVersion, set) {
		return function() {
			var panel = $("pnl-picture");
			panel.immediateDescendants().invoke("remove");
			_pivViewer.imageDimensions = {
				width: img.width,
				height: img.height,
				ratio: img.width / img.height
			};
			img.setStyle({position: "relative", left: _pivViewer.displayMetrics.screenX + "px", top: _pivViewer.displayMetrics.screenY + "px"});
			if ( _pivViewer.displayMetrics.zoom != 1.0 ) img.setStyle({width: parseInt(_pivViewer.imageDimensions.width * _pivViewer.displayMetrics.zoom) + "px", height: parseInt(_pivViewer.imageDimensions.height * _pivViewer.displayMetrics.zoom) + "px"});
			_pivViewer.currentZoom = _pivViewer.displayMetrics.zoom;
			panel.appendChild(img);
			// L'objet de drag pour le pan
			_pivViewer.dndImagePan = new YAHOO.util.DD("img-main", "img-main", {});
			_pivViewer.dndImagePan.endDrag = _pivViewer.dndImagePanEndDrag;
			_pivViewer.dndImagePan.onDrag = _pivViewer.dndImagePanOnDrag;
			// L'objet de drag pour le zoomselect
			_pivViewer.dndImageZoomSelect = new YAHOO.util.DragDrop("img-main", "img-main");
			_pivViewer.dndImageZoomSelect.startDrag = _pivViewer.dndImageZoomSelectStartDrag;
			_pivViewer.dndImageZoomSelect.onDrag = _pivViewer.dndImageZoomSelectOnDrag;
			_pivViewer.dndImageZoomSelect.endDrag = _pivViewer.dndImageZoomSelectEndDrag;
			// La fenêtre d'aperçu
			_pivViewer.initOverview();
			// On ajuste les outils pour que le curseur soit correct
			_pivViewer.updateImageTools();
			_pivViewer.stopLoading();
			// On conserve la taille (marche sous IE seulement)
			imageVersion.setFileSize(img.fileSize);
			// On ajuste le menu des versions
			_pivViewer.resetMnuVersions(set, imageVersion);
			// On ajuste le menu d'impression
			_pivViewer.resetMnuPrint(imageVersion);
			if( _viewer_actives.mosaic ) {
			// On ajuste le bouton de mosaïque TODO: pas nécessaire sur chaque image...
			_pivViewer.adjustMosaicButton(set);
			}
			// On vérifie si on a besoin d'afficher les grilles de mosaïque
			if ( _pivViewer.showMosaicGrid && _viewer_actives.mosaic ) {
				// On s'assure que l'on voit la totalité de l'image
				_pivViewer.fitScreen();
				// Le div où la grille s'insère
				var p = $("pnl-picture");
				// Un div pour contenir la grille
				var d = window.document.createElement("div");
				d.setAttribute("id", "piv-mosaic-grid");
				d = $(d);
				d.makePositioned();
				d.setStyle({zIndex: 10, position: "absolute", left: 0 + "px", top: 0 + "px"});
				// On insère le nouveau div dans le panel
				//p.insertBefore(d, $("img-main"));
				p.appendChild(d);
				// Le ratio entre l'image affichée et l'originale
				var r = (_pivViewer.currentZoom  * imageVersion.getWidth() ) / set.getOriginalVersion().getWidth()
				// On appelle la création de la grille
				set.getMosaic().drawGrid(d, r);
				// On indique que le boulot est fait
				_pivViewer.showMosaicGrid = false;
			}
			else { if (!_pivViewer.lockZoom) _pivViewer.fitScreen(); }
			// Si c'est une tuile, on doit afficher les contrôles supplémentaires
			if ( imageVersion.getTile() ) {
				var mosaic = set.getMosaic();
				var t = imageVersion.getTile();
				var p = $("pnl-picture");
				// Dimensions des boutons de navigation
				var controlS = 25;
				var controlL = 75;
				// Dimensions du viewport
				var vpW = _pivViewer.viewport.width;
				var vpH = _pivViewer.viewport.height;
				var next = mosaic.above(t);
				if ( next ) {
					var d = _pivViewer.getTileNavigationControl("top");
					d.setStyle({top: 0 + "px", left: (vpW / 2) - (controlL/2) + "px"});
					d.observe("click", _pivViewer.handleBtnMosaicNavigationClick(next));
					p.appendChild(d);
				}
				next = mosaic.right(t);
				if ( next ) {
					var d = _pivViewer.getTileNavigationControl("right");
					d.setStyle({top: (vpH/2) - (controlL/2) + "px", left: (vpW - controlS) + "px"});
					d.observe("click", _pivViewer.handleBtnMosaicNavigationClick(next));
					p.appendChild(d);
				}
				next = mosaic.below(t);
				if ( next ) {
					var d = _pivViewer.getTileNavigationControl("bottom");
					d.setStyle({top: (vpH-controlS) + "px", left: (vpW / 2) - (controlL/2) + "px"});
					d.observe("click", _pivViewer.handleBtnMosaicNavigationClick(next));
					p.appendChild(d);
				}
				next = mosaic.left(t);
				if ( next ) {
					var d = _pivViewer.getTileNavigationControl("left");
					d.setStyle({top: (vpH/2) - (controlL/2) + "px", left: 0 + "px"});
					d.observe("click", _pivViewer.handleBtnMosaicNavigationClick(next));
					p.appendChild(d);
				}
			}
			// On met à jour son statut
			_pivViewer.imageStatus = "Image n° " + _pivViewer.currentImageNo + " : " + imageVersion.getName() + " \u2022 " + imageVersion.getSummary();
			_pivViewer.updateStatus();
		}
	},

	handleBtnMosaicNavigationClick: function(nextTile) {
		return function() {
			_pivViewer.gotoVersion(nextTile.set, nextTile.getVersion());
			//alert(nextTile.i + "," + nextTile.j);
		};
	},

	getTileNavigationControl: function(pos) {
		var d = $(window.document.createElement("div"));
		d.setAttribute("id", "piv-mosaic-navigation-" + pos);
		d.addClassName("piv-mosaic-navigation-" + pos);
		return d;
	},

	/**
	*	Initialise la fenêtre d'aperçu, en particulier l'image qu'on y trouve.
	*/
	initOverview: function() {
		// Le div qui contient l'aperçu
		var div = $("pnl-overview-vp");
		// L'image elle-même
		var img = $(document.createElement("img"));
		img.setAttribute("id", "pnl-overview-img");
		// L'image a la même URL que l'image principale
		img.setAttribute("src", $("img-main").getAttribute("src"));
		// On fixe la largeur à 180, et on ajuste la haute pour que l'image soit
		// dans les mêmes prooportions
		// TODO: attention à la rotation!
		var w = 180;
		var h = w / _pivViewer.imageDimensions.ratio;
		img.setStyle({left: "0px", top: "0px", width: w + "px", height: h + "px"});
		// Maintenant on enlève l'ancienne image dans l'aperçu
		$A(div.getElementsByTagName("img")).each(function(e) { $(e).remove(); });
		// On ajoute tout de suite la nouvelle car sinon on ne peut calculer
		// sa position
		div.appendChild(img);

		// On ajuste le masque
		_pivViewer.setOverviewMaskSize(w / _pivViewer.imageDimensions.width, {width: w, height: h});
		// Le masque doit pouvoir être déplacé, mais sans sortir de la fenêtre

		// La taille de l'image est w et g
		var dnd = _pivViewer.dndOverviewMask;
		var mask = $("pnl-overview-mask");
		var maskPos = Position.cumulativeOffset(mask);
		dnd.setXConstraint(maskPos[0]);	// TODO rendu ici... il faut soustraire quelque chose...
	},

	/**
	*	Règle la taille du masque de l'aperçu.
	*	@param r {float} Le ration entre l'aperçu et l'image (< 1) (peut être null)
	*	@param imgOverview {object} Taille width; height de l'image d'aperçu (peut être null)
	*/
	setOverviewMaskSize: function(r, imgOverview) {

		// Si les paramètres ne sont pas passés on les calcule
		if ( !imgOverview ) {
			var img = $("pnl-overview-img");
			imgOverview = {width: img.width, height: img.height};
		}
		if ( !r ) r = imgOverview.width / _pivViewer.imageDimensions.width;

		// La partie visible de l'image (x1, x2, y1, y2)
		var region = _pivViewer.getVisibleRegion();

		// La position de l'image d'aperçu
		var pOverviewImage = Position.positionedOffset($("pnl-overview-img"));

		// La position X,Y du masque doit être calculés de la même manière
		// en fonction de la position visible de l'image. On doit aussi tenir
		// compte du fait que l'image d'aperçu n'est pas positionnée en 0,0
		var xMask = r * region.x1 + pOverviewImage[0];
		var yMask = r * region.y1 + pOverviewImage[1];
		// La taille du masque doit refléter la taille de l'image visible,
		// en fonction du ratio de l'aperçu vs l'image réelle, mais sans dépasser
		// la taille de l'image d'aperçu
		var wMask = Math.min(r * (region.x2 - region.x1), imgOverview.width + pOverviewImage[0] - xMask);
		var hMask = Math.min(r * (region.y2 - region.y1), imgOverview.height + pOverviewImage[1] - yMask);
		// Le masque
		var mask = $("pnl-overview-mask");
		// Sa position
		mask.setStyle({left: Math.round(xMask) + "px", top: Math.round(yMask) + "px", width: Math.round(wMask) + "px", height: Math.round(hMask) + "px"});
	},

	/**
	*	Retourne la partie visible de l'image, en coordonnées image.
	*	@return {Object} Les coordonnées, sous la forme d'un objet aux propriétés x1, y1, x2, y2.
	*/
	getVisibleRegion: function() {
		// Le facteur de zoom
		var z = _pivViewer.currentZoom;
		// L'image
		var img = $("img-main");
		// La position de l'image par rapport au viewport
		var screenX = parseInt(img.getStyle("left"));
		var screenY = parseInt(img.getStyle("top"));

		// Le coin supérieur gauche de la partie visible de l'image
		var x1 = Math.max(-screenX / z, 0);
		var y1 = Math.max(-screenY / z, 0);

		// Le coin inférieur droit de la partie visible de l'image
		var x2 = Math.min(_pivViewer.imageDimensions.width, (_pivViewer.viewport.width - screenX) / z);
		var y2 = Math.min(_pivViewer.imageDimensions.height, (_pivViewer.viewport.height - screenY) / z);

		// On retourne l'objet
		return {x1: x1, y1: y1, x2: x2, y2: y2};
	},

	/*
	*	Affiche les informations sur l'image en cours de chargement.
	*/
	startLoading: function() {
		// FIXME: ne marche pas
		//var anim = new YAHOO.util.Anim(this.pnlLoading, {opacity: {from: 0, to: 1}}, 1.5, YAHOO.util.Easing.easeInStrong);
		//anim.animate();
		this.pnlLoading.show();
    	if(this.pnlNoImg){
      		this.pnlNoImg.hide();
    	}
	},

	/*
	*	Arrête l'affichage des informations sur l'image en cours de chargement.
	*/
	stopLoading: function() {
		//$("pnl-loading").hide();
		_pivViewer.pnlLoading.hide();
		//setTimeout(function() {$("pnl-loading").hide()}, 5000);
	},

	// Fonction de test
	identify: function() {
		return "Classe PivViewer";
	},

	gotoHome: function(ev) {
		self.location.href = _pivSeries.serverUrl + '/../';
		ev.stop();
	},

	showHelp: function(ev) {
		var _url = _pivSeries.serverUrl + '/../pages/help-viewer.html';
		winFocus(_url, 'viewerhelp', null, null, 'true');
		ev.stop();
	}
};	// Fin de PivViewer.prototype

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

	//les valeurs sont-elles réellement utile ? normalement, on les obtient
	//systématiquement ces valeurs depuis la classe PyViewer
	// La largeur en pixels de l'image de fond du slider
	bgImgW: 160,
	cursorMoveHFactor: 10,
	nbImages: 0,

	/**
	* Création d'un slider pour la sélection d'une image.
	* @constructor
	*/
	initialize: function(config) {
		if( config ){
			if( config.bgImgW ) this.bgImgW = config.bgImgW;
			if( config.cursorMoveHFactor ) this.cursorMoveHFactor = config.cursorMoveHFactor;
		}
	},

	/**
	* Initialisation du slider.
	* Cette méthode doit être appelée à chaque fois qu'on a une nouvelle série
	* à afficher.
	* @param series {PivSeries} La série d'images en cours de visualisation.
	*/
	init: function(series) {
		// Les objets importants
		var bg="sld-imageselect-bg";			// Le div qui contient le background
		var thumb="sld-imageselect-hdl";		// Le curseur lui-même
		var valFld="txt-imageselect";			// Le champ où l'on peut saisir une valeur

		// On construit le slider

		// La largeur en pixels de l'image de fond du slider
		var cursorMoveH = this.bgImgW - this.cursorMoveHFactor;
		// La largeur de variation du curseur
		this.nbImages = series.getSize();
		// Le nombre d'images dans la série
		this.pixelsPerImage = parseFloat(cursorMoveH) / parseFloat(this.nbImages - 1);

		// Le slider, non animé pour les grosses séries
		this.xslider = YAHOO.widget.Slider.getHorizSlider(bg, thumb, 0, cursorMoveH);
		this.xslider.animate = false;

		// Un setSyle pour ne pas a avoir a s'embeter avec une valeur dans le
		// javascript *et* une valeur dans la css
		if($(bg)){
			$(bg).setStyle({
				height:'20px',
				width:this.bgImgW
			});
		}

		// La fonction utilisée lorsqu'on bouge le curseur
		this.addSubscription();

		pivCreateTooltip("sld-imageselect-bg_tt", "sld-imageselect-bg");
		pivCreateTooltip("txt-imageselect_tt", "txt-imageselect");
	},

	removeSubscription: function() {
//		this.xslider.unsubscribe("change");
//		this.xslider.unsubscribe("slideEnd");
	},

	addSubscription: function() {
		this.xslider.subscribe("change", _pivViewer.slider._onChange);
		this.xslider.subscribe("slideEnd", _pivViewer.sliderEnd);
	},

	_onChange: function(offsetFromStart) {
		/*$("txt-imageselect").value = _pivSeries.getCurrentImage();
		$("txt-imageselect").value = _pivViewer.slider.getImageNo();
		pivCreateTooltip("btn-imageselect" + "_tt", "btn-imageselect", pivGetMessage("tt_btn_imageselect", {no: $F("txt-imageselect")}));*/
		// On ne fait plus rien de particulier ici. Tout se passe maintenant dans
		// la fonction update() et/ou dansla fonctione sliderEnd()
	},

	/**
	* Retourne le numéro de l'image représentée par le curseur.
	*/
	getImageNo: function() {
		var noF = ( this.xslider.getValue() / _pivViewer.slider.pixelsPerImage );
		var ret = ( 1 + Math.ceil(noF) );
		if(ret >= 0 && ret <= this.nbImages ) {
			return ret;
		}
		else if(ret < 0){
			return 0;
		}
		else{
			return this.nbImages;
		}
	},

	/**
	* Met à jour la valeur représentée par le slider.
	* @param no {int} Le numéro de l'image en cours d'affichage.
	*/
	update: function(no) {
		this.xslider.setValue((no-1) * this.pixelsPerImage);
	}

}; // Fin de la définition de la classe PivImageSelectSlider

