// ScrolledHSPlot.cs created with MonoDevelop
// User: klose at 10:03 27.12.2008
// CVS release: $Id: ScrolledPlot.cs,v 1.27 2011-04-19 12:36:58 klose Exp $
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2009  Thomas Klose, Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Threading;
using System.Drawing;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using Gtk;

using Hiperscan.Spectroscopy;
using Hiperscan.GUI.ZoomBar;
using Hiperscan.FileIO;
using Hiperscan.Unix;


namespace Hiperscan.GUI.Figure
{
	
	public class ScrolledPlot : Gtk.Table
	{
		private class DragInfo
		{
			private int physical_x_start;
			private int physical_y_start;
			private int physical_width;
			private int physical_height;
			private double x_min_start, x_max_start;
			private double y_min_start, y_max_start;
			private double x_delta, y_delta;
			private double new_x_min, new_x_max;
			private double new_y_min, new_y_max;
			
			private ScrolledPlot scrolled;
			
			
			public DragInfo(ScrolledPlot scrolled_plot, Gdk.EventButton e)
			{
				this.scrolled = scrolled_plot;

				this.physical_width  = this.scrolled.Plot.PhysicalXMax - this.scrolled.Plot.PhysicalXMin;
				this.physical_height = this.scrolled.Plot.PhysicalYMax - this.scrolled.Plot.PhysicalYMin;
				
				this.physical_x_start = (int)e.X;
				this.physical_y_start = (int)e.Y;
				
				this.x_min_start = scrolled_plot.XMin;
				this.x_max_start = scrolled_plot.XMax;
				this.y_min_start = scrolled_plot.YMin;
				this.y_max_start = scrolled_plot.YMax;

				this.x_delta = this.x_max_start - this.x_min_start;
				this.y_delta = this.y_max_start - this.y_min_start;
			}
			
			public void ShowPan(Gdk.EventMotion e)
			{
				int physical_delta_x = (int)e.X - this.physical_x_start;
				int physical_delta_y = (int)e.Y - this.physical_y_start;
				
				double x_pan = (double)physical_delta_x * this.x_delta / (double)this.physical_width;
				double y_pan = (double)physical_delta_y * this.y_delta / (double)this.physical_height;
				
				this.new_x_min = this.scrolled.plot.ReverseXAxes ? this.x_min_start + x_pan : this.x_min_start - x_pan;
				this.new_x_max = this.scrolled.plot.ReverseXAxes ? this.x_max_start + x_pan : this.x_max_start - x_pan;
				this.new_y_min = this.y_min_start - y_pan;
				this.new_y_max = this.y_max_start - y_pan;
				
				this.scrolled.Plot.XMin = this.new_x_min;
				this.scrolled.Plot.XMax = this.new_x_max;
				this.scrolled.Plot.YMin = this.new_y_min;
				this.scrolled.Plot.YMax = this.new_y_max;
			}
			
			public void ApplyPan()
			{
				if (this.new_x_min == 0.0 && this.new_x_max == 0.0 &&
				    this.new_y_min == 0.0 && this.new_y_max == 0.0)
					return;
				this.scrolled.SetXYMinMax(this.new_x_min, this.new_x_max,
				                          this.new_y_min, this.new_y_max, true);
			}
			
			public int PhysicalXStart
			{
				get { return this.physical_x_start; }
			}
			
			public int PhysicalYStart
			{
				get { return this.physical_y_start; }
			}
		}

		internal HZoomBar hzb;
		internal VZoomBar vzb;
		private  IPlotEngine plot;
		private double current_x_zoom = 1.0;
		private double current_y_zoom = 1.0;
		private double current_x_pan  = 0.5;
		private double current_y_pan  = 0.5;
		
		private DragInfo button1_drag_info = null;
		private DragInfo button2_drag_info = null;

		private PlotInfo plot_info;
		
		public bool HasPopupDialog { get; set; }
		
		public delegate void PointerCoordsChangedHandler(double x, double y);
		public event         PointerCoordsChangedHandler PointerCoordsChanged;
		
		public delegate void XAxesReversedHandler(IPlotEngine plot);
		public event         XAxesReversedHandler XAxesReversed;
		

		public ScrolledPlot(PlotInfo plot_info) : base(2, 2, false)
		{
			this.plot = new NPlotEngine();

			this.plot_info = plot_info;

			this.hzb = new HZoomBar();
			this.vzb = new VZoomBar();
			
			// set event handlers
			this.hzb.AsyncChanged += this.OnHAsyncChanged;
			this.hzb.Changed      += this.OnHChanged;
			this.hzb.AsyncChangedCallbackInvoked += this.OnHCallbackInvoked;

			this.vzb.AsyncChanged += this.OnVAsyncChanged;
			this.vzb.Changed      += this.OnVChanged;
			this.vzb.AsyncChangedCallbackInvoked += this.OnVCallbackInvoked;
			
			this.plot.DataSetAdded  += this.OnDataSetAdded;
			this.plot.XAxesReversed += this.OnXAxesReversed;

			// pack widgets in table
			this.Attach(this.plot.GtkWidget, 0, 1, 0, 1);
			this.Attach(this.hzb,  0, 1, 1, 2, AttachOptions.Fill, AttachOptions.Fill, 0, 0);
			this.Attach(this.vzb,  1, 2, 0, 1, AttachOptions.Fill, AttachOptions.Fill, 0, 0);
			
			this.ZoomType = ZoomType.Proportional;
			this.HasPopupDialog = false;
		}
		
		~ScrolledPlot()
		{
			((IDisposable)this.plot).Dispose();
		}
		
//		internal void ReplacePlot(IPlotEngine plot)
//		{
//			this.plot.StopRefreshing();
//			IPlotEngine old_plot = this.plot;
//			
//			this.plot = plot;
//			this.plot.DataSetAdded += new EventHandler<EventArgs>(this.OnDataSetAdded);
//
//			this.plot.SetXAxis(this.XPan, this.XZoom);
//			this.plot.SetYAxis(this.YPan, this.YZoom);
//			this.plot.GtkWidget.Show();
//			
//			Monitor.Enter(old_plot);
//			this.Remove(old_plot.GtkWidget);
//
//			try
//			{
//				this.Attach(this.plot.GtkWidget, 0, 1, 0, 1);
//			}
//			catch (System.Exception ex)
//			{
//				Console.WriteLine("Cannot attach new plot: " + ex.Message);
//			}
//
//			try
//			{
//				old_plot.GtkWidget.Destroy();
//				old_plot.GtkWidget.Dispose();
//			}
//			catch (System.Exception ex)
//			{
//				Console.WriteLine("Cannot dispose old plot: " + ex.Message);
//			}
//		}
		
		public void ZoomIn()
		{
			double new_zoom = this.XZoom * 1.2;
			
			if (new_zoom > this.MaxZoom)
				new_zoom = this.MaxZoom;
			
			this.XZoom = new_zoom;

			new_zoom = this.YZoom * 1.2;
			
			if (new_zoom > this.MaxZoom)
				new_zoom = this.MaxZoom;
			
			this.YZoom = new_zoom;
		}
		
		public void ZoomIn(double xw, double yw)
		{
			double xmin_dist = xw - this.Plot.XMin;
			double xmax_dist = xw - this.Plot.XMax;
			double ymin_dist = yw - this.Plot.YMin;
			double ymax_dist = yw - this.Plot.YMax;
			this.SetXYMinMax(xw - xmin_dist/1.2, 
			                 xw - xmax_dist/1.2,
			                 yw - ymin_dist/1.2,
			                 yw - ymax_dist/1.2);
		}
		
		public void ZoomOut()
		{
			double new_zoom = this.XZoom * 0.8;
			
			if (new_zoom < 1.0)
				new_zoom = 1.0;
			
			this.XZoom = new_zoom;
			
			new_zoom = this.YZoom * 0.8f;
			
			if (new_zoom < 1f)
				new_zoom = 1f;
			
			this.YZoom = new_zoom;
		}

		private void OnHChanged(ZoomBar.ZoomBar zb, bool released)
		{						
		}
		
		private void OnHAsyncChanged(ZoomBar.ZoomBar zb, bool released)
		{
			if (!released && zb.Zoom > this.current_x_zoom && !this.Plot.DisableSelections)
			{
				double min, max;
				this.plot.GetXMinMax(zb.Pan, zb.Zoom, out min, out max);
				int physical_min = this.plot.GetPhysicalXCoord(min);
				int physical_max = this.plot.GetPhysicalXCoord(max);
				this.plot.HSelection = new HorizontalSelection(this.Plot, physical_min, physical_max);
				this.plot.AsyncRefresh(true);
			}
			else
			{
				this.plot.SetXAxis(zb.Pan, zb.Zoom);
				this.current_x_zoom = zb.Zoom;
				this.current_x_pan  = zb.Pan;
				this.plot.HSelection = null;
				this.plot.AsyncRefresh();
			}
		}
		
		private void OnHCallbackInvoked(ZoomBar.ZoomBar zb)
		{
		}
		
		private void OnVChanged(ZoomBar.ZoomBar zb, bool released)
		{
		}
		
		private void OnVAsyncChanged(ZoomBar.ZoomBar zb, bool released)
		{
			if (!released && zb.Zoom > this.current_y_zoom && !this.Plot.DisableSelections)
			{
				double min, max;
				this.plot.GetYMinMax(zb.Pan, zb.Zoom, out min, out max);
				int physical_min = this.plot.GetPhysicalYCoord(min);
				int physical_max = this.plot.GetPhysicalYCoord(max);
				this.plot.VSelection = new VerticalSelection(this.plot, physical_min, physical_max);
				this.plot.AsyncRefresh(true);
			}
			else
			{
				this.plot.SetYAxis(zb.Pan, zb.Zoom);
				this.current_y_zoom = zb.Zoom;
				this.current_y_pan  = zb.Pan;
				this.plot.VSelection = null;
				this.plot.AsyncRefresh();
			}
		}
		
		private void OnVCallbackInvoked(ZoomBar.ZoomBar zb)
		{
		}
		
		private void OnDataSetAdded(object o, EventArgs e)
		{
			this.plot.SetXAxis(this.current_x_pan, this.current_x_zoom);
			this.plot.SetYAxis(this.current_y_pan, this.current_y_zoom);
		}
		
		private void OnXAxesReversed(object o, EventArgs e)
		{
			IPlotEngine plot = o as IPlotEngine;
			
			if (this.XAxesReversed != null)
				this.XAxesReversed(plot);
		}
		
		protected override bool OnButtonPressEvent(Gdk.EventButton e)
		{
			if (this.Plot.IsEmpty || this.Plot.IsValid == false)
				return base.OnButtonPressEvent(e);

			try
			{
				switch (e.Button)
				{
				
				case 1:
					if (e.Type == Gdk.EventType.TwoButtonPress)
					{
						// double click
						this.Plot.FitView();
						this.XZoom = 1f;
						this.YZoom = 1f;
					}
					else
					{				
						// single click
						this.button1_drag_info = new DragInfo(this, e);
						foreach (Annotation a in this.Plot.Annotations)
						{
							if (a.Interactive)
							{
								a.ButtonPressed((int)e.X, (int)e.Y, e.State);            
								this.Plot.NotifyPlotChanges();
							}
						}
					}
					break;
					
				case 2:
					this.button2_drag_info = new DragInfo(this, e);
					this.Plot.IsDragging = true;
	//				CrossHair marker = new CrossHair(this.Plot, (int)e.X, (int)e.Y);
	//				marker.Changed += delegate(object sender, EventArgs ee) {
	//					this.Plot.AsyncRefresh(true);
	//				};
	//				this.Plot.Markers.Add(marker);
	//				this.Plot.Refresh(true);
					break;
				
				case 3:
					if (this.HasPopupDialog)
						MainWindow.UI.EditMenu.Popup();
	//				if (this.Plot.Markers.Count > 0)
	//				{
	//					this.Plot.Markers.Clear();
	//					this.Plot.Refresh();
	//				}
					break;
				}
			}
			catch (NullReferenceException)
			{
				// plot may be in undefinded state
				Console.WriteLine("NullReferenceException in ScrolledPlot.OnButtonPressEvent()");
			}

			return base.OnButtonPressEvent(e);
		}
		
		protected override bool OnMotionNotifyEvent(Gdk.EventMotion e)
		{
			bool interactive_annotation = false;
			
			foreach (Annotation a in this.Plot.Annotations)
			{
				if (!a.Interactive)
					continue;
					
				interactive_annotation = true;

				a.Motion((int)e.X, (int)e.Y, e.State);
				this.Plot.AsyncRefresh(true);
			}
			
			if (!interactive_annotation)
			{
				if (this.Plot.Selection == null && this.button1_drag_info != null)
				{
					this.Plot.SetSelection(this.button1_drag_info.PhysicalXStart, this.button1_drag_info.PhysicalYStart, 0, 0);
				}
				
				if (this.Plot.Selection != null)
				{
					this.Plot.Selection.SetSize((int)e.X-this.Plot.Selection.XStart,
					                            (int)e.Y-this.Plot.Selection.YStart);
				}
				else if (this.button2_drag_info != null)
				{
					//				if (this.Plot.Markers.Count > 0)
					//					this.Plot.Markers[this.Plot.Markers.Count-1].SetPosition((int)e.X, (int)e.Y);
					
					this.button2_drag_info.ShowPan(e);
				}
			}
			
			
			int x, y, w, h;
			this.Plot.GtkWidget.GdkWindow.GetOrigin(out x, out y);
			this.Plot.GtkWidget.GdkWindow.GetSize(out w, out h);

			if (this.PointerCoordsChanged != null &&
			    (int)e.XRoot >= x && (int)e.XRoot <= x+w &&
			    (int)e.YRoot >= y && (int)e.YRoot <= y+h)
			{
				try
				{
					double xw = this.Plot.GetWorldXCoord((int)e.X);
					double yw = this.Plot.GetWorldYCoord((int)e.Y);
					
					if (this.PointerCoordsChanged != null)
						this.PointerCoordsChanged(xw, yw);
				} catch {}
			}
			
			return base.OnMotionNotifyEvent(e);
		}

		protected override bool OnButtonReleaseEvent(Gdk.EventButton e)
		{
			switch (e.Button)
			{
				
			case 1:
				this.button1_drag_info = null;
				foreach (Annotation a in this.Plot.Annotations)
				{
					if (a.Interactive)
					{
						a.ButtonReleased((int)e.X, (int)e.Y, e.State);            
						this.Plot.NotifyPlotChanges();
						this.Plot.AsyncRefresh(true);
					}
				}
				if (this.Plot.Selection == null)
					break;
				if (this.Plot.Selection.Width > 5 || this.Plot.Selection.Height > 5)
				{
					double x_min = this.Plot.GetWorldXCoord(this.Plot.Selection.X);
					double x_max = this.Plot.GetWorldXCoord(this.Plot.Selection.X + this.Plot.Selection.Width);
					double y_max = this.Plot.GetWorldYCoord(this.Plot.Selection.Y);
					double y_min = this.Plot.GetWorldYCoord(this.Plot.Selection.Y + this.Plot.Selection.Height);
					
					if (this.Plot.ReverseXAxes)
						this.SetXYMinMax(x_max, x_min, y_min, y_max);
					else
						this.SetXYMinMax(x_min, x_max, y_min, y_max);
				}
				this.Plot.Selection = null;
				this.Plot.Refresh();
				break;
				
			case 2:
				if (this.button2_drag_info == null)
					break;
				this.button2_drag_info.ApplyPan();
				this.button2_drag_info = null;
				this.Plot.IsDragging = false;
				break;
			}
			
			return base.OnButtonReleaseEvent(e);
		}
		
		protected override bool OnScrollEvent(Gdk.EventScroll e)
		{
			if (this.Plot.IsEmpty || this.Plot.IsValid == false)
				return base.OnScrollEvent(e);

			try
			{
				int w, h, x, y;
				Gdk.ModifierType modifier;
				this.Plot.GtkWidget.GdkWindow.GetSize(out w, out h);
				this.Plot.GtkWidget.GdkWindow.GetPointer(out x, out y, out modifier);
				
				if (x <= w && y <= h)
				{
					bool zoomed = true;
					
					switch (e.Direction)
					{

					case Gdk.ScrollDirection.Up:
						double xw = this.Plot.GetWorldXCoord((int)e.X);
						double yw = this.Plot.GetWorldYCoord((int)e.Y);
						this.ZoomIn(xw, yw);
						break;
						
					case Gdk.ScrollDirection.Down:
						this.ZoomOut();
						break;
						
					default:
						zoomed = false;
						break;
					
					}
					
					if (zoomed)
					{
						try
						{
							double xw = this.Plot.GetWorldXCoord((int)e.X);
							double yw = this.Plot.GetWorldYCoord((int)e.Y);
							if (this.PointerCoordsChanged != null)
								this.PointerCoordsChanged(xw, yw);	
						}
						catch (Exception ex)
						{
							Console.WriteLine("Exception while scroll event: " + ex.ToString());
						}
					}
				}
			}
			catch (NullReferenceException)
			{
				// plot may be in undefined state
				Console.WriteLine("NullReferenceException in ScrolledPlot.OnScrollEvent()");
			}

			return base.OnScrollEvent(e);
		}

		public double MaxZoom
		{
			get
			{
				return this.hzb.MaxZoom;
			}
			set
			{
				this.hzb.MaxZoom = value;
				this.vzb.MaxZoom = value;
				this.plot.AsyncRefresh();
			}
		}

		public ZoomBar.ZoomType ZoomType
		{
			get
			{
				return this.hzb.ZoomType;
			}
			set
			{
				this.hzb.ZoomType = value;
				this.vzb.ZoomType = value;
				this.plot.AsyncRefresh();
			}
		}

		public double XZoom
		{
			get { return this.hzb.Zoom;  }
			set	{ this.hzb.Zoom = value; }
		}
		
		public double XPan
		{
			get { return this.hzb.Pan;  }
			set	{ this.hzb.Pan = value; }
		}
		
		public double YZoom
		{
			get { return this.vzb.Zoom;  }
			set { this.vzb.Zoom = value; }
		}
		
		public double YPan
		{
			get { return this.vzb.Pan;  }
			set { this.vzb.Pan = value; }
		}
		
		public IPlotEngine Plot
		{
			get { return this.plot;  }
		}
		
		public double XMin
		{
			get { return this.plot.XMin;  }
			set 
			{ 
				double xmin = value;				
				double xmax = this.plot.XMax;
				double gxmn = this.plot.GlobalXMin;
				double gxmx = this.plot.GlobalXMax;
				
				double global_range = gxmx - gxmn;
				double new_range    = xmax - xmin;
				
				if (global_range/new_range > this.hzb.MaxZoom || xmin >= xmax)
					xmin = this.plot.XMax - global_range/this.hzb.MaxZoom;
				
				double pan, zoom;
				if ((gxmn - gxmx + xmax - xmin) == 0.0)
					pan = 0.5;
				else
					pan  = (gxmn - xmin) / (gxmn - gxmx + xmax - xmin);
				zoom = (gxmx - gxmn) / (xmax -xmin);
				
				if (this.Plot.ReverseXAxes)
					pan = 1.0 - pan;
				
				this.hzb.SetPanZoom(pan, zoom);
			}
		}
		
		public void SetXYMinMax(double x_min, double x_max, double y_min, double y_max, bool expand_range)
		{
			if (expand_range)
			{
				if (x_min < this.plot.GlobalXMin)
					this.plot.SetGlobalXMinMax(x_min, this.plot.GlobalXMax);
				if (x_max > this.plot.GlobalXMax)
					this.plot.SetGlobalXMinMax(this.plot.GlobalXMin, x_max);
				if (y_min < this.plot.GlobalYMin)
					this.plot.SetGlobalYMinMax(y_min, this.plot.GlobalYMax);
				if (y_max > this.plot.GlobalYMax)
					this.plot.SetGlobalYMinMax(this.plot.GlobalYMin, y_max);
			}

			this.SetXYMinMax(x_min, x_max, y_min, y_max);
		}
		
		public void SetXYMinMax(double x_min, double x_max, double y_min, double y_max)
		{
			double xmin = x_min;
			double xmax = x_max;
			double gxmn = this.plot.ExtendedGlobalXMin;
			double gxmx = this.plot.ExtendedGlobalXMax;
			
			double global_range = gxmx - gxmn;
			double new_range    = xmax - xmin;
				
			if (global_range/new_range > this.hzb.MaxZoom || xmin >= xmax)
				xmin = x_max - global_range/this.hzb.MaxZoom;
			
			double pan, zoom;
			if ((gxmn - gxmx + xmax - xmin) == 0.0)
				pan = 0.5;
			else
				pan  = (gxmn - xmin) / (gxmn - gxmx + xmax - xmin);
			zoom = (gxmx - gxmn) / (xmax -xmin);
			
			if (this.plot.ReverseXAxes)
				pan = 1.0 - pan;
			
			this.hzb.SetPanZoom(pan, zoom);
			
			
			double ymin = y_min;
			double ymax = y_max;
			double gymn = this.plot.ExtendedGlobalYMin;
			double gymx = this.plot.ExtendedGlobalYMax;
			
			global_range = gymx - gymn;
			new_range    = ymax - ymin;
			
			if (global_range/new_range > this.vzb.MaxZoom || ymin >= ymax)
				ymin = y_max - global_range/this.vzb.MaxZoom;
			
			if ((gymx - gymn - ymax + ymin) == 0.0)
				pan = 0.5;
			else
				pan  = (gymx - ymax) / (global_range - ymax + ymin);
			zoom = global_range / (ymax -ymin);
			
			this.vzb.SetPanZoom(pan, zoom);
		}
		
		public List<MetaData> CreateMetaData()
		{
			List<MetaData> meta_data = new List<MetaData>();

			meta_data.Add(new MetaData("Title", MetaData.SerializeString(this.plot_info.Name)));

			foreach (Annotation a in this.Plot.Annotations)
			{
				meta_data.Add(new MetaData("Annotation", a.Serialize()));
			}
			
			return meta_data;
		}
		
		public void RestoreAnnotations(List<MetaData> meta_data)
		{
			if (meta_data == null)
				return;
			
			foreach (MetaData md in meta_data)
			{
				if (md.Type != "Annotation")
					continue;
				
				Annotation a = Annotation.Deserialize(this.Plot, md.Data);
				
				if (!this.Plot.Annotations.Contains(a))
				    this.Plot.Annotations.Add(a);
			}
		}
				
		public double XMax
		{
			get
			{
				return this.plot.XMax;
			}
			set
			{
				double xmin = this.plot.XMin;
				double xmax = value;
				double gxmn = this.plot.ExtendedGlobalXMin;
				double gxmx = this.plot.ExtendedGlobalXMax;
				
				double global_range = gxmx - gxmn;
				double new_range    = xmax - xmin;
				
				if (global_range/new_range > this.hzb.MaxZoom || xmin >= xmax)
					xmax = this.plot.XMin + global_range/this.hzb.MaxZoom;
				
				double pan, zoom;
				if ((gxmn - gxmx + xmax - xmin) == 0.0)
					pan = 0.5;
				else
					pan  = (gxmn - xmin) / (gxmn - gxmx + xmax - xmin);
				zoom = (gxmx - gxmn) / (xmax -xmin);
				
				if (this.Plot.ReverseXAxes)
					pan = 1.0 - pan;

				this.hzb.SetPanZoom(pan, zoom);
			}
		}
				
		public double YMin
		{
			get
			{
				return this.plot.YMin;
			}
			set
			{
				if (value < this.plot.GlobalYMin)
					this.plot.SetGlobalYMinMax(value, this.plot.GlobalYMax);
					
				double ymin = value;
				double ymax = this.plot.YMax;
				double gymn = this.plot.ExtendedGlobalYMin;
				double gymx = this.plot.ExtendedGlobalYMax;

				double global_range = gymx - gymn;
				double new_range    = ymax - ymin;
				
				if (global_range/new_range > this.vzb.MaxZoom || ymin >= ymax)
					ymin = this.plot.YMax - global_range/this.vzb.MaxZoom;

				double pan, zoom;
				if ((gymx - gymn - ymax + ymin) == 0.0)
					pan = 0.5;
				else
					pan  = (gymx - ymax) / (global_range - ymax + ymin);
				zoom = global_range / (ymax -ymin);
				
				this.vzb.SetPanZoom(pan, zoom);
			}
		}
				
		public double YMax
		{
			get
			{
				return this.plot.YMax;
			}
			set
			{
				if (value > this.plot.GlobalYMax)
					this.plot.SetGlobalYMinMax(this.plot.GlobalYMin, value);
				
				double ymin = this.plot.YMin;
				double ymax = value;
				double gymn = this.plot.ExtendedGlobalYMin;
				double gymx = this.plot.ExtendedGlobalYMax;
				
				double global_range = gymx - gymn;
				double new_range    = ymax - ymin;
				
				if (global_range/new_range > this.vzb.MaxZoom || ymin >= ymax)
					ymax = this.plot.YMin + global_range/this.vzb.MaxZoom;
				
				double pan, zoom;
				if ((gymx - gymn - ymax + ymin) == 0.0)
					pan = 0.5;
				else
					pan  = (gymx - ymax) / (gymx - gymn - ymax + ymin);
				zoom = (gymx - gymn) / (ymax -ymin);
				
				this.vzb.SetPanZoom(pan, zoom);
			}
		}		
		
		public new bool Sensitive
		{
			get { return this.vzb.Sensitive && this.hzb.Sensitive; }
			set
			{
				this.hzb.Sensitive = value;
				this.vzb.Sensitive = value;
			}
		}
	}
}
