// ZoomBar.cs created with MonoDevelop
// User: klose at 13:48 16.12.2008
// CVS release: $Id: ZoomBar.cs,v 1.14 2010-11-25 13:52:56 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/>.
//
/// \file ZoomBar.cs
/// \brief short description
///
/// detailed description

using System;
using System.Threading;
using System.Collections.Generic;
using Gtk;


namespace Hiperscan.GUI.ZoomBar
{	
	public struct Defaults
	{
		public const double HandleLength = 28;
		//public const double HandleLength = 1;
		public const double MinBarLength = 20;
		public const double ButtonLength = 20;
	}
	
	public enum ZoomType
	{
		Linear,
		Quadratic,
		Cubic,
		Proportional
	}
	
	internal class ZoomBarButton : Gtk.Button
	{
		protected ZoomBarButton() : base()
		{
//			if (MainClass.OS == MainClass.OSType.Windows)
			{
				RcStyle style = new RcStyle();
				style.Xthickness = 0;
				style.Ythickness = 0;
				this.ModifyStyle(style);			
			}
			
			this.Relief = ReliefStyle.None;
		}
	}

	internal class ScrollButton : ZoomBarButton
	{
		public ScrollButton() : base()
		{
		}
		
		protected override bool OnEnterNotifyEvent(Gdk.EventCrossing e)
		{
			return true;
		}
	}
	
	internal class Handle : ZoomBarButton
	{
		public Gdk.ModifierType ModifierState; 
		
		public delegate void DoubleClickedHandler(Handle h);
		public event         DoubleClickedHandler DoubleClicked;
				
		
		public Handle() : base()
		{
			this.Relief = ReliefStyle.Normal;
		}
		
		protected override bool OnButtonPressEvent(Gdk.EventButton e)
		{
			this.ModifierState = e.State;
			
			if ((e.Button == 1) && (e.Type == Gdk.EventType.TwoButtonPress))
			{
				if (this.DoubleClicked != null)
					this.DoubleClicked(this);
				
				return true;
			}
			
			return base.OnButtonPressEvent(e);
		}		
	}
	
	internal class Slide : ZoomBarButton
	{
		public Slide() : base() 
		{
		}

		protected override bool OnEnterNotifyEvent(Gdk.EventCrossing e)
		{
			return true;
		}
	}
	
	internal class HSlideSeparator : Gtk.HSeparator
	{
		public HSlideSeparator() : base()
		{
		}
	}
	
	internal class VSlideSeparator : Gtk.VSeparator
	{
		public VSlideSeparator() : base()
		{
		}
	}
	
	public class ZoomBar : Gtk.Alignment
	{
		internal List<ZoomBarButton> buttons;
		
		protected Frame frame;
		private   Fixed fix;
		
		private bool  is_horizontal;
		
		protected double zoom = 1f;
		protected double pan  = 0.5f;
		protected double scroll_step = 0.05f;
		private   double max_zoom  = 300f;
		private   double last_zoom = 1f;
		private   double last_pan  = 0f;
		
		private ZoomType zoom_type = ZoomType.Proportional;

		protected bool dragging = false;
		private   int  drag_xoffset, drag_yoffset;
		
		protected int frame_width, frame_height;
		protected int width = 19;
		protected double lmin, lmax, l, w, offset;
		
		public delegate void ChangedHandler(ZoomBar zb, bool released);
		public event         ChangedHandler Changed;

		public delegate void AsyncChangedHandler(ZoomBar zb, bool released);
		public event         AsyncChangedHandler AsyncChanged;
		
		public delegate void AsyncChangedCallbackHandler(ZoomBar zb);
		public event         AsyncChangedCallbackHandler AsyncChangedCallbackInvoked;

		
		protected ZoomBar(float xalign, float yalign, float xscale, float yscale)
			: base(xalign, yalign, xscale, yscale)
		{
			if (xscale == 1f)
				this.is_horizontal = true;
			else
				this.is_horizontal = false;

			// initialize frame
			this.frame = new Frame();
			this.frame.BorderWidth = 0;
			this.frame.ShadowType = ShadowType.In;

			// initialize fixed
			this.fix = new Fixed();
			this.fix.HeightRequest = this.width;
			this.fix.BorderWidth = 0;
			this.fix.HasWindow = false;
			this.fix.SizeAllocated += new SizeAllocatedHandler(OnFixedSizeAllocated);									

			// initialize handles, slides, buttons
			this.buttons = new List<ZoomBarButton>();
			for (int ix=0; ix < 7; ++ix)
			{
				if (ix < 2)
				{
					Slide slide    = new Slide();
					slide.Pressed += new EventHandler(this.OnSlidePressed);
					this.buttons.Add(slide);
				}
				else if (ix < 5)
				{
					Handle handle  = new Handle();
					handle.Events |= Gdk.EventMask.PointerMotionMask;
					handle.Pressed       += new EventHandler(this.OnHandlePressed);
					handle.Released      += new EventHandler(this.OnHandleReleased);
					handle.DoubleClicked += new Handle.DoubleClickedHandler(this.OnDoubleClicked);
					this.buttons.Add(handle);
				}
				else
				{
					ScrollButton button   = new ScrollButton();
					button.Pressed += new EventHandler(this.OnButtonPress);
					this.buttons.Add(button);
				}
				this.buttons[ix].CanFocus     = false;
				this.buttons[ix].UseUnderline = false;
				this.fix.Add(this.buttons[ix]);
			}
			this.buttons[2].MotionNotifyEvent += new MotionNotifyEventHandler(this.OnCenterHandleMotion);
			this.buttons[3].MotionNotifyEvent += new MotionNotifyEventHandler(this.OnLeftUpHandleMotion);
			this.buttons[4].MotionNotifyEvent += new MotionNotifyEventHandler(this.OnRightDownHandleMotion);
			
			// pack widgets
			this.frame.Add(this.fix);
			this.Add(this.frame);
		}
		
		protected void Move(Gtk.Widget w, int x, int y)
		{
			this.fix.Move(w, x, y);
		}

		protected new void SetSizeRequest(int x, int y)
		{
			this.fix.SetSizeRequest(x, y);
		}

		protected double NewLength(double new_zoom)
		{
			switch (zoom_type)
			{
				
			case ZoomType.Linear:
				double a = (lmin - lmax) / (max_zoom - 1f);
				double b = lmax - a;
				return a * new_zoom + b;
								
			case ZoomType.Quadratic:
				return lmax - Math.Sqrt( Math.Pow(lmax-lmin, 2) * (new_zoom-1f) * (max_zoom-1f) ) / (max_zoom-1f); 
				
			case ZoomType.Cubic:
				return lmax - Math.Pow( Math.Pow(lmax-lmin, 3) * (new_zoom-1f) * Math.Pow(max_zoom-1f, 2), 1/3f ) / (max_zoom-1f);
				
			case ZoomType.Proportional:
				return (lmax-2*Defaults.HandleLength - Defaults.MinBarLength) / new_zoom + 2*Defaults.HandleLength + Defaults.MinBarLength;
			
			default:
				throw new NotSupportedException("Unknown ZoomType.");
				
			}
		}
		
		protected double NewZoom(double new_l)
		{
			switch (zoom_type)
			{
				
			case ZoomType.Linear:
				double a = (lmin - lmax) / (max_zoom - 1f);
				double b = lmax - a;
				return (new_l - b) / a;

			case ZoomType.Quadratic:
				return (max_zoom - 1f) * Math.Pow( (lmax - new_l) / (lmax - lmin) , 2 ) + 1f; 
				
			case ZoomType.Cubic:
				return (max_zoom - 1f) * Math.Pow( (lmax - new_l) / (lmax - lmin) , 3 ) + 1f; 
				
			case ZoomType.Proportional:
				return (lmax-2*Defaults.HandleLength-Defaults.MinBarLength) / (new_l-2*Defaults.HandleLength-Defaults.MinBarLength); 
				
			default:
				throw new NotSupportedException("Unknown ZoomType.");
				
			}
		}

		protected void CalculateGeomParams(int new_length, int new_width)
		{			
			lmin   = 2 * Defaults.HandleLength + Defaults.MinBarLength;
			lmax   = (double)new_length - 2*Defaults.ButtonLength;
			l      = this.NewLength(zoom);
			w      = (double)new_width;
			offset = pan * (lmax - l);
		}
		
		public void SetPanZoom(double pan, double zoom)
		{
			if (pan < 0f) pan = 0f;
			if (pan > 1f) pan = 1f;
			if (zoom < 1f) zoom = 1f;
			if (zoom > this.max_zoom) zoom = this.max_zoom;
			
			this.pan  = pan;
			this.zoom = zoom;
			
			this.Update();
		}
		
		virtual protected void Update()
		{
			throw new MissingMethodException("The method Update() must be implemented in classes derived from ZoomBar.");
		}
		
		public void ChangeNotify(bool released)
		{
			if (this.AsyncChanged != null)
			{
				foreach (AsyncChangedHandler del in this.AsyncChanged.GetInvocationList())
					del.BeginInvoke(this, released, new AsyncCallback(AsyncChangedCallback), del);
			}
			
			if (this.Changed != null)
			{
				this.Changed(this, released);
			}
		}
		
		private void AsyncChangedCallback(IAsyncResult iar)
		{
			if (this.AsyncChangedCallbackInvoked != null)
				this.AsyncChangedCallbackInvoked(this);
		}

		private void OnButtonPress(object o, System.EventArgs e)
		{
			Button b = o as Button;
			
			double pan_step = scroll_step/(zoom-1.0);

			if (b == this.buttons[6])
			{
				if (pan + pan_step <= 1.0)
					pan += pan_step;
				else
					pan = 1.0;
			}
			else
			{
				if (pan - pan_step >= 0.0)
					pan -= pan_step;
				else
					pan = 0.0;
			}			
			this.Update();
		}		
		
		private void OnSlidePressed(object o, System.EventArgs e)
		{
			Slide s = o as Slide;
			
			double pan_step = 0.9/(zoom-1.0);

			if (s == this.buttons[1])
			{
				if (pan + pan_step <= 1.0)
					pan += pan_step;
				else
					pan = 1.0;
			}
			else
			{
				if (pan - pan_step >= 0.0)
					pan -= pan_step;
				else
					pan = 0.0;
			}
			this.Update();
		}

		protected void OnHandlePressed(object o, System.EventArgs e)
		{
			Handle h = o as Handle;
			int x, y;
			h.Parent.GetPointer(out x, out y);
			dragging = true;
			drag_xoffset = x;
			drag_yoffset = y;
		}

		protected void OnHandleReleased(object o, System.EventArgs e)
		{
			dragging = false;
			this.ChangeNotify(true);
		}

		private void OnDoubleClicked(Handle o)
		{
			if (pan != 0.5f && zoom != 1f)
			{
				last_pan  = pan;
				last_zoom = zoom;
				pan       = 0.5f;
				zoom      = 1f;
			}
			else
			{
				zoom = last_zoom;
				pan  = last_pan;
			}	
			this.Update();
		}
		
		private void OnLeftUpHandleMotion(object o, MotionNotifyEventArgs args)
		{
			Handle h = o as Handle;
			int x, y;
			h.Parent.GetPointer(out x, out y);

			if (dragging)
			{
				double drag;
				
				if (this.is_horizontal)
					drag = x - drag_xoffset;
				else
					drag = y - drag_yoffset;
				
				drag_xoffset = x;
				drag_yoffset = y;

				double new_zoom, new_pan, new_l;
				
				if ((h.ModifierState & Gdk.ModifierType.ControlMask) != Gdk.ModifierType.None)
					new_l = l - drag*2;
				else
					new_l = l - drag;
				
				new_zoom = this.NewZoom(new_l);
				new_pan  = (offset + drag) / (lmax - new_l);
				
				if (new_zoom >= 1f && 
				    new_zoom <= max_zoom &&
				    new_pan  >= 0f &&
				    new_pan  <= 1f)
				{
					zoom = new_zoom;
					pan  = new_pan;
					this.Update();
				}
			}	
		}

		private void OnCenterHandleMotion(object o, MotionNotifyEventArgs args)
		{
			Handle h = o as Handle;
			int x, y;
			h.Parent.GetPointer(out x, out y);
			
			if (dragging)
			{
				double drag;
				
				if (this.is_horizontal)
					drag = x - drag_xoffset;
				else
					drag = y - drag_yoffset;
				
				drag_xoffset = x;
				drag_yoffset = y;
				
				double new_pan  = (offset + drag) / (lmax - l);

				if (new_pan  >= 0f &&
				    new_pan  <= 1f)
				{
					pan = new_pan;
				}
				else
				{
					pan = Math.Round(pan);
				}
				this.Update();
			}
		}
		
		private void OnRightDownHandleMotion(object o, MotionNotifyEventArgs e)
		{
			Handle h = o as Handle;
			int x, y;
			h.Parent.GetPointer(out x, out y);

			if (dragging)
			{
				double drag;
				
				if (this.is_horizontal)
					drag = x - drag_xoffset;
				else
					drag = y - drag_yoffset;
				
				drag_xoffset = x;
				drag_yoffset = y;

				double new_zoom, new_l, new_pan;

				if ((h.ModifierState & Gdk.ModifierType.ControlMask) != Gdk.ModifierType.None)
				{
					new_zoom = this.NewZoom(l + 2*drag);
					new_pan  = (offset - drag) / (lmax - l - 2*drag);
				}
				else
				{
					new_l    = l + drag;
					new_zoom = this.NewZoom(new_l);
					new_pan  = offset / (lmax - new_l);
				}

				if (new_zoom >= 1f && 
				    new_zoom <= max_zoom &&
				    new_pan  >= 0f &&
				    new_pan  <= 1f)
				{
					zoom = new_zoom;
					pan  = new_pan;
					this.Update();
				}
			}						
		}

		private void OnFixedSizeAllocated(object o, SizeAllocatedArgs e)
		{
			if (frame_width  != e.Allocation.Width ||
			    frame_height != e.Allocation.Height)
			{
				frame_width  = e.Allocation.Width;
				frame_height = e.Allocation.Height;
				this.Update();
			}
		}

		public double Zoom
		{
			get
			{
				return zoom;
			}
			set
			{
				zoom = value;
				this.Update();
			}
		}
		
		public ZoomType ZoomType
		{
			get
			{
				return zoom_type;
			}
			set
			{
				zoom_type = value;
				this.Update();
			}
		}

		public double Pan
		{
			get
			{
				return pan;
			}
			set
			{
				pan = value;
				this.Update();
			}
		}
		
		public bool IsDragging
		{
			get
			{
				return dragging;
			}
		}

		public double MaxZoom
		{
			get
			{
				return max_zoom;
			}
			set
			{
				max_zoom = value;
				this.Update();
			}
		}
		
		public double ScrollStep
		{
			get
			{
				return scroll_step;
			}
			set
			{
				scroll_step = value;
			}
		}

		public ShadowType ShadowType
		{
			get
			{
				return this.frame.ShadowType;
			}
			set 
			{
				this.frame.ShadowType = value;
				this.Update();
			}
		}
		
		public new uint BorderWidth
		{
			get
			{
				return this.frame.BorderWidth;
			}
			set
			{
				this.frame.BorderWidth = value;
				this.Update();
			}
		}
		
		public int Width
		{
			get
			{
				return width;
			}
			set
			{
				width = value;
				this.fix.WidthRequest = value;
				this.Update();
			}
		}
		
//		public bool Sensitive
//		{
//			get
//			{
//				return this.buttons[3].Sensitive;
//			}
//			set
//			{
//				foreach (Button b in this.buttons)
//				{
//					b.Sensitive = value;
//				}
//				
//				if (value)
//					this.Update();
//			}
//		}
	}
		
	
	public class HZoomBar : ZoomBar
	{
		
		protected static Style scrollbar_style;

		public HZoomBar() : base(0f, 0f, 1f, 0f)
		{
			this.Name = "Horizontal Zoom Bar";

			Arrow arrow;
			HBox  hb;

			// center handle
			Alignment align = new Alignment(0.5f, 0.5f, 1.0f, 0.7f); 
			hb = new HBox();
			HBox hb2 = new HBox();
			hb.Spacing = 3;
			hb2.PackStart(hb, true, false, 0);
			
			for (int ix=1; ix < 4; ++ix)
			{
				hb.PackStart(new VSlideSeparator());
			}
			align.Add(hb2);
			this.buttons[2].Add(align);

			// first handle
			hb = new HBox();
			arrow = new Arrow(ArrowType.Left, ShadowType.In);
			hb.PackStart(arrow, true, true, 0);
			arrow = new Arrow(ArrowType.Right, ShadowType.In);
			hb.PackStart(arrow, true, true, 0);
			this.buttons[3].Add(hb);

			// right handle
			hb = new HBox();
			arrow = new Arrow(ArrowType.Left, ShadowType.In);
			hb.PackStart(arrow, true, true, 0);
			arrow = new Arrow(ArrowType.Right, ShadowType.In);
			hb.PackStart(arrow, true, true, 0);
			this.buttons[4].Add(hb);

			// scroll buttons
			arrow = new Arrow(ArrowType.Left, ShadowType.In);
			this.buttons[5].Add(arrow);
			
			arrow = new Arrow(ArrowType.Right, ShadowType.In);
			this.buttons[6].Add(arrow);
			
			this.frame.ScrollEvent += new ScrollEventHandler(OnScrollEvent);

			this.ModifyStyle();
			this.StyleSet += delegate(object o, StyleSetArgs args) {
				HZoomBar.scrollbar_style = null;
				this.ModifyStyle();
			};
			
			// update geometry
			this.Update();
		}
		
		private void ModifyStyle()
		{
			Style style = this.GetScrollbarStyle();
			if (style == null)
				return;
			foreach (ZoomBarButton b in this.buttons)
			{
				b.Style = style;
			}
		}
		
		private Style GetScrollbarStyle()
		{
			if (HZoomBar.scrollbar_style == null)
			{
				HScrollbar sb = new HScrollbar(new Adjustment(0f, 1f, 1f, 0.1f, 0.2f, 0f));
				HZoomBar.scrollbar_style = Rc.GetStyle(sb);
				int w = sb.SizeRequest().Height;
				
				if (w > 4)
					this.width = (w % 2 == 1) ? w : w+1;

				if (Settings.Default.ThemeName == "New Wave")
					HZoomBar.scrollbar_style = Rc.GetStyle(new Button());

				if (Settings.Default.ThemeName == "Adwaita")
				{
					HZoomBar.scrollbar_style = Rc.GetStyle(new Button());
					this.width = 19;
				}
			}
			
			return HZoomBar.scrollbar_style;
		}
		
		protected override void Update()
		{						
			int pl  = this.frame_width; 
			int pw  = this.Width;

			this.CalculateGeomParams(pl, pw);

			if (l < 0 || w < 0 || offset < 0)
				return; // invalid data -> do nothing
			
			this.buttons[0].WidthRequest  = (int)Math.Round(offset);
			this.buttons[0].HeightRequest = (int)Math.Round(w);
			this.buttons[1].WidthRequest  = (int)Math.Round(lmax - l - offset);
			this.buttons[1].HeightRequest = (int)Math.Round(w);
			this.buttons[2].WidthRequest  = (int)Math.Round(l); // - 2*Defaults.HandleLength);
			this.buttons[2].HeightRequest = (int)Math.Round(w);
			this.buttons[3].WidthRequest  = (int)Math.Round(Defaults.HandleLength);
			this.buttons[3].HeightRequest = (int)Math.Round(w);
			this.buttons[4].WidthRequest  = (int)Math.Round(Defaults.HandleLength);
			this.buttons[4].HeightRequest = (int)Math.Round(w);
			this.buttons[5].WidthRequest  = (int)Math.Round(Defaults.ButtonLength);
			this.buttons[5].HeightRequest = (int)Math.Round(w);
			this.buttons[6].WidthRequest  = (int)Math.Round(Defaults.ButtonLength);
			this.buttons[6].HeightRequest = (int)Math.Round(w);
	
			this.Move(this.buttons[0], (int)Math.Round(Defaults.ButtonLength), 0);
			this.Move(this.buttons[1], (int)Math.Round(Defaults.ButtonLength + offset + l), 0);
			this.Move(this.buttons[2], (int)Math.Round(Defaults.ButtonLength + offset), 0); // + Defaults.HandleLength), 0);
			this.Move(this.buttons[3], (int)Math.Round(Defaults.ButtonLength + offset), 0);
			this.Move(this.buttons[4], (int)Math.Round(Defaults.ButtonLength + l + offset - Defaults.HandleLength), 0);
			this.Move(this.buttons[5], 0, 0);
			this.Move(this.buttons[6], (int)Math.Round(Defaults.ButtonLength + lmax), 0);
			
			if (zoom == 1f || pan == 0f)
				this.buttons[5].Sensitive = false;
			else
		 		this.buttons[5].Sensitive = true;
						
			if (zoom == 1f || pan == 1f)
				this.buttons[6].Sensitive = false;
			else
				this.buttons[6].Sensitive = true;

			this.SetSizeRequest((int)(lmin + 2*Defaults.ButtonLength + 10), pw);

			// execute event handlers
			this.ChangeNotify(!this.dragging);
		}
		
		private void OnScrollEvent(object o, Gtk.ScrollEventArgs e)
		{
			double pan_step = scroll_step/(zoom-1);

			if (e.Event.Direction == Gdk.ScrollDirection.Up||
			    e.Event.Direction == Gdk.ScrollDirection.Right)
			{
				if (pan + pan_step <= 1f)
					pan += pan_step;
				else
					pan = 1f;
			}
			else
			{
				if (pan - pan_step >= 0f)
					pan -= pan_step;
				else
					pan = 0f;
			}			
			this.Update();
		}
	}		


	public class VZoomBar : ZoomBar
	{

		protected static Style scrollbar_style;

		public VZoomBar() : base(0f, 0f, 0f, 1f)
		{
			this.Name = "Vertical Zoom Bar";
			
			Arrow arrow;
			VBox  vb;

			// center handle
			Alignment align = new Alignment(0.5f, 0.5f, 0.7f, 1.0f); 
			vb = new VBox();
			VBox vb2 = new VBox();
			vb.Spacing = 3;
			vb2.PackStart(vb, true, false, 0);
			
			for (int ix=1; ix < 4; ++ix)
			{
				vb.PackStart(new HSlideSeparator());
			}
			align.Add(vb2);
			this.buttons[2].Add(align);

			// up handle
			vb = new VBox();
			arrow = new Arrow(ArrowType.Up, ShadowType.In);
			vb.PackStart(arrow, true, true, 0);
			arrow = new Arrow(ArrowType.Down, ShadowType.In);
			vb.PackStart(arrow, true, true, 0);
			this.buttons[3].Add(vb);

			// down handle
			vb = new VBox();
			arrow = new Arrow(ArrowType.Up, ShadowType.In);
			vb.PackStart(arrow, true, true, 0);
			arrow = new Arrow(ArrowType.Down, ShadowType.In);
			vb.PackStart(arrow, true, true, 0);
			this.buttons[4].Add(vb);

			// scroll buttons
			arrow = new Arrow(ArrowType.Up, ShadowType.In);
			this.buttons[5].Add(arrow);
			this.buttons[5].Name = "Up";
			
			arrow = new Arrow(ArrowType.Down, ShadowType.In);
			this.buttons[6].Add(arrow);
			this.buttons[6].Name = "Down";
			
			this.frame.ScrollEvent += new ScrollEventHandler(this.OnScrollEvent);

			this.ModifyStyle();
			this.StyleSet += delegate(object o, StyleSetArgs args) {
				VZoomBar.scrollbar_style = null;
				this.ModifyStyle();
			};
			
			// update geometry
			this.Update();
		}
		
		private void ModifyStyle()
		{
			Style style = this.GetScrollbarStyle();
			if (style == null)
				return;
			foreach (ZoomBarButton b in this.buttons)
			{
				b.Style = style;
			}
		}
		
		private Style GetScrollbarStyle()
		{
			if (VZoomBar.scrollbar_style == null)
			{
				VScrollbar sb = new VScrollbar(new Adjustment(0f, 1f, 1f, 0.1f, 0.2f, 0f));
				VZoomBar.scrollbar_style = Rc.GetStyle(sb);
				int w = sb.SizeRequest().Width;
				
				if (w > 4)
					this.width = (w % 2 == 1) ? w : w+1;
				
				if (Settings.Default.ThemeName == "New Wave")
					VZoomBar.scrollbar_style = Rc.GetStyle(new Button());

				if (Settings.Default.ThemeName == "Adwaita")
				{
					VZoomBar.scrollbar_style = Rc.GetStyle(new Button());
					this.width = 19;
				}
			}
			
			return VZoomBar.scrollbar_style;
		}
		
		protected override void Update()
		{
			int pl  = frame_height; 
			int pw  = Width;
			
			this.CalculateGeomParams(pl, pw);
			
			if (l < 0 || w < 0 || offset < 0)
				return; // invalid data -> do nothing

			this.buttons[0].HeightRequest = (int)Math.Round(offset);
			this.buttons[0].WidthRequest  = (int)Math.Round(w);
			this.buttons[1].HeightRequest = (int)Math.Round(lmax - l - offset);
			this.buttons[1].WidthRequest  = (int)Math.Round(w);
			this.buttons[2].HeightRequest = (int)Math.Round(l); // - 2*Defaults.HandleLength);
			this.buttons[2].WidthRequest  = (int)Math.Round(w);
			this.buttons[3].HeightRequest = (int)Math.Round(Defaults.HandleLength);
			this.buttons[3].WidthRequest  = (int)Math.Round(w);
			this.buttons[4].HeightRequest = (int)Math.Round(Defaults.HandleLength);
			this.buttons[4].WidthRequest  = (int)Math.Round(w);
			this.buttons[5].HeightRequest = (int)Math.Round(Defaults.ButtonLength);
			this.buttons[5].WidthRequest  = (int)Math.Round(w);
			this.buttons[6].HeightRequest = (int)Math.Round(Defaults.ButtonLength);
			this.buttons[6].WidthRequest  = (int)Math.Round(w);
	
			this.Move(this.buttons[0], 0, (int)Math.Round(Defaults.ButtonLength));
			this.Move(this.buttons[1], 0, (int)Math.Round(Defaults.ButtonLength + offset + l));
			this.Move(this.buttons[2], 0, (int)Math.Round(Defaults.ButtonLength + offset)); // + Defaults.HandleLength));
			this.Move(this.buttons[3], 0, (int)Math.Round(Defaults.ButtonLength + offset));
			this.Move(this.buttons[4], 0, (int)Math.Round(Defaults.ButtonLength + l + offset - Defaults.HandleLength));
			this.Move(this.buttons[5], 0, 0);
			this.Move(this.buttons[6], 0, (int)Math.Round(Defaults.ButtonLength + lmax));
			
			if (zoom == 1f || pan == 0f)
				this.buttons[5].Sensitive = false;
			else
		 		this.buttons[5].Sensitive = true;
						
			if (zoom == 1f || pan == 1f)
				this.buttons[6].Sensitive = false;
			else
				this.buttons[6].Sensitive = true;

			this.SetSizeRequest(pw, (int)(lmin + 2*Defaults.ButtonLength + 10f));

			// execute event handlers
			this.ChangeNotify(!this.dragging);
		}
		
		private void OnScrollEvent(object o, Gtk.ScrollEventArgs e)
		{
			double pan_step = scroll_step/(zoom-1);

			if (e.Event.Direction == Gdk.ScrollDirection.Down)
			{
				if (pan + pan_step <= 1f)
					pan += pan_step;
				else
					pan = 1f;
			}
			else
			{
				if (pan - pan_step >= 0f)
					pan -= pan_step;
				else
					pan = 0f;
			}			
			this.Update();
		}	
	}		
}
