context: using Gtk;
  using Gdk;
  using Gnome;
  using GtkSharp;
  using System;
  using GLib;
  using System.Runtime.InteropServices;
  using FSpot;
  
  namespace FSpot {
  	public class XScreenSaverSlide : Gtk.Window {
  		public const string ScreenSaverEnviroment = "XSCREENSAVER_WINDOW";
  
  		public XScreenSaverSlide () : base (String.Empty)
  		{
  		}
  	       
  		protected override void OnRealized ()
  		{
  			string env = Environment.GetEnvironmentVariable (ScreenSaverEnviroment);
  			
  			if (env != null) {
  				try {
  					env = env.ToLower ();
  					
  					if (env.StartsWith ("0x"))
  						env = env.Substring (2);
  
  					uint xid = UInt32.Parse (env, System.Globalization.NumberStyles.HexNumber);
  					
  					GdkWindow = Gdk.Window.ForeignNew (xid);
  					Style.Attach (GdkWindow);
  					GdkWindow.Events = EventMask.ExposureMask 
  						| EventMask.StructureMask 
  						| EventMask.EnterNotifyMask 
  						| EventMask.LeaveNotifyMask 
  						| EventMask.FocusChangeMask;
  					
  					Style.SetBackground (GdkWindow, Gtk.StateType.Normal);
  					GdkWindow.SetDecorations ((Gdk.WMDecoration) 0);
  					GdkWindow.UserData = this.Handle;
  					SetFlag (WidgetFlags.Realized);
  					SizeRequest ();
  					Gdk.Rectangle geom;
  					int depth;
  					GdkWindow.GetGeometry (out geom.X, out geom.Y, out geom.Width, out geom.Height, out depth);
  					SizeAllocate (new Gdk.Rectangle (geom.X, geom.Y, geom.Width, geom.Height));
  					Resize (geom.Width, geom.Height);
  					return;
  				} catch (System.Exception e) {
  					System.Console.WriteLine (e);
  				}
  			} else {
  				System.Console.WriteLine ("{0} not set, falling back to window", ScreenSaverEnviroment);
  			}
  
  			SetSizeRequest (640, 480);
  			base.OnRealized ();
  		}
  	}
  
  	public class FullSlide : Gtk.Window {
  		private SlideView slideview;
  		private Gdk.Pixbuf screenshot;
  		private Delay hide;
  		private Gdk.Cursor busy;
  		private Gdk.Cursor none;
  		
  		public FullSlide (Gtk.Window parent, IBrowsableItem [] items) : base ("Slideshow")
  		{
  			screenshot =  PixbufUtils.LoadFromScreen (parent.GdkWindow);
  			
  			this.Destroyed += HandleDestroyed;
  
  			this.TransientFor = parent;
  
  			this.ButtonPressEvent += HandleSlideViewButtonPressEvent;
  			this.KeyPressEvent += HandleSlideViewKeyPressEvent;
  			this.AddEvents ((int) (EventMask.ButtonPressMask | EventMask.KeyPressMask | EventMask.PointerMotionMask));
  			slideview = new SlideView (screenshot, items);
  			this.Add (slideview);
  			this.Decorated = false;
  			this.Fullscreen();
  			this.Realize ();
  
  			busy = new Gdk.Cursor (Gdk.CursorType.Watch);
  			this.GdkWindow.Cursor = busy;
  			none = Empty ();
  
  			hide = new Delay (2000, new GLib.IdleHandler (HideCursor));
  		}
  
  		public Gdk.Cursor Empty () 
  		{
  			Gdk.Cursor cempty = null;
  			
  			try {
  				Gdk.Pixbuf empty = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, true, 8, 1, 1);
  			        empty.Fill (0x00000000);
  				cempty = new Gdk.Cursor (GdkWindow.Display, empty, 0, 0);
  			} catch (System.Exception e){
  				System.Console.WriteLine (e.ToString ());
  				return null;
  			}
  
  			return cempty;
  		}
  
  
  
  		public void Play ()
  		{
  			Gdk.GCValues values = new Gdk.GCValues ();
  			values.SubwindowMode = SubwindowMode.IncludeInferiors;
  			Gdk.GC fillgc = new Gdk.GC (this.GdkWindow, values, Gdk.GCValuesMask.Subwindow);
  			
  			slideview.Show ();
  			this.GdkWindow.SetBackPixmap (null, false);
  			this.Show ();
  			screenshot.RenderToDrawable (this.GdkWindow, fillgc, 
  						     0, 0, 0, 0, -1, -1, RgbDither.Normal, 0, 0);
  			
  			slideview.Play ();
  			hide.Start ();
  		}
  		
  		[GLib.ConnectBefore]
  		private void HandleSlideViewKeyPressEvent (object sender, KeyPressEventArgs args)
  		{
  			this.Destroy ();
  			args.RetVal = true;
  		}
  
  		protected override bool OnMotionNotifyEvent (Gdk.EventMotion args)
  		{
  			base.OnMotionNotifyEvent (args);
  			this.GdkWindow.Cursor = busy;
  			hide.Start ();
  			return true;
  		}
  		
  		private bool HideCursor ()
  		{
  			this.GdkWindow.Cursor = none;
  			return false;
  		}
  		
  		private void HandleDestroyed (object sender, System.EventArgs args)
  		{
  			hide.Stop ();
  		}
  
  		private void HandleSlideViewButtonPressEvent (object sender, ButtonPressEventArgs args)
  		{
  			this.Destroy ();
  			args.RetVal = true;
  		}
  	}
  	
  	public class SlideView : Gtk.Image {
  		IBrowsableItem [] photos;
  		Pixbuf last;
  		Pixbuf next;
  		
  		
  		Pixbuf [] tweens = new Pixbuf [10];	
  		int current_tween;
  		uint tween_idle;
  		uint resize_idle;
  		
  		int current_idx = 0;	
  		int next_idx = 0;
  		
  		uint flip_timer = 0;
  		uint transition_timer = 0;
  		
  		uint fail_count = 0;
  		bool animate = true;
  		uint animate_max = 200;
  		
  		bool black = false;
  		uint flip_interval = 2000;
  		uint transition_interval = 75;
  		
  		public bool Running {
  			get {
  				return flip_timer != 0 || transition_timer != 0;
  			}
  		}
  		
  		public bool Animate {
  			get { return animate; }
  			set { animate = value; }
  		}
  		
  		public void Play () 
  		{
  			if (photos.Length < 1)
  				return;
  
  			StopTweenIdle ();
  			if (current_idx >= 0) {
  				Pixbuf frame = GetScaled (photos[current_idx]);
  				this.Pixbuf = frame;
  				frame.Dispose ();
  			} 
  			
  			if (PreloadNextImage (current_idx + 1))
  				StartFlipTimer ();
  		}
  		
  		public void Pause () 
  		{
  			StopTranstionTimer ();
  			StopFlipTimer ();
  		}
  		
  		public void Stop ()
  		{
  			StopTweenIdle ();
  			StopTranstionTimer ();
  			StopFlipTimer ();
  		}
  		
  		public void Forward ()
  		{
  			if (PreloadNextImage (current_idx + 1))
  				ShowNext ();
  		}
  		
  		public void Back ()
  		{
  			if (PreloadNextImage (current_idx - 1))
  				ShowNext ();
  		}	
  		
  		private void ShowNext ()
  		{
  			StopTweenIdle ();
  			
  			if (current_idx != next_idx && next != null)
  				this.Pixbuf = next;
  			
  			current_idx = next_idx;
  			
  			black = false;
  			transition_interval = 75;
  			flip_interval = 2000;
  		}
  		
  		private bool PreloadNextImage (int idx)
  		{
  			try {
  				if (idx < photos.Length && idx >= 0) {
  					if (next != null)
  						next.Dispose ();
  					
  					next = GetScaled (photos [idx]);
  					if (next == null)
  						next = GetScaled (PixbufUtils.ShallowCopy (PixbufUtils.ErrorPixbuf));
  					
  					next_idx = idx;
  					StartTweenIdle ();
  					
  					return true;
  				} else {
  					if (next != null)
  						next.Dispose ();
  
  					next = GetScaled (photos [0]);
  					if (next == null)
  						next = GetScaled (PixbufUtils.ShallowCopy (PixbufUtils.ErrorPixbuf));
  					next_idx = 0;
  					StartTweenIdle ();
  					
  					return false;
  				}
  			} catch (GLib.GException e) {
  				System.Console.WriteLine (e);
  				idx = (idx + 1) % photos.Length;
  				return PreloadNextImage (idx);
  			}
  		}
  		
  		private Pixbuf CrossFade (Pixbuf current, Pixbuf prev, Pixbuf next, double percent)
  		{ 
  			Rectangle area = new Rectangle (0, 0, Allocation.Width, Allocation.Height);
  			BlockProcessor proc = new BlockProcessor (area, 256);
  			Rectangle subarea;
  
  			while (proc.Step (out subarea)) {
  				if (IsRealized)
  					GdkWindow.ProcessUpdates (false);
  				
  				prev.CopyArea (subarea.X, subarea.Y, subarea.Width, subarea.Height, current, subarea.X, subarea.Y);
  				next.Composite (current, subarea.X, subarea.Y, subarea.Width, subarea.Height, 0, 0, 1, 1,
  						Gdk.InterpType.Nearest, (int) System.Math.Round (255 * percent));
  			}
  			return current;
  		}
  		
  		private Pixbuf BlackFade (Pixbuf current, Pixbuf prev, Pixbuf next, double percent)
  		{ 
  			int width = Allocation.Width;
  			int height = Allocation.Height;
  			
  			current.Fill (0);		
  			
  			if (percent < 0.5)
  				prev.Composite (current, 0,0, width, height, 0, 0, 1, 1,
  						Gdk.InterpType.Nearest, (int)System.Math.Round (255  * (1 - percent * 2)));
  			else
  				next.Composite (current, 0,0, width, height, 0, 0, 1, 1,
  						Gdk.InterpType.Nearest, (int)System.Math.Round (255 * (percent * 2 - 1)));
  			return current;
  		}
  		
  		private Pixbuf Blend (Pixbuf current, Pixbuf prev, Pixbuf next, double percent)
  		{
  			if (black) {
  				return BlackFade (current, prev, next, percent);
  			} else {
  				return CrossFade (current, prev, next, percent);
  			}
  		}
  		
  		private Pixbuf GetScaled (Pixbuf orig)
  		{
  			Gdk.Rectangle pos;
  			int width = Allocation.Width;
  			int height = Allocation.Height;
  			double scale = PixbufUtils.Fit (orig, width, height, false, out pos.Width, out pos.Height);
  			pos.X = (width - pos.Width) / 2;
  			pos.Y = (height - pos.Height) / 2;
  			
  			Pixbuf scaled = new Pixbuf (Colorspace.Rgb, false, 8, width, height);
  			scaled.Fill (0x000000); 
  			
  			Rectangle rect = new Rectangle (pos.X, pos.Y, 256, 256);
  			Rectangle subarea;
  			
  			while (rect.Top < pos.Bottom) {
  				while (rect.X < pos.Right) {
  					if (IsRealized) 
  						GdkWindow.ProcessUpdates (false);
  
  
gt: 					rect.Intersect (pos, out subarea);