context: //  
  //  Copyright (C) 2009-2010 Jason Smith, Robert Dyer, Rico Tzschichhholz
  //  Copyright (C) 2011 Robert Dyer
  // 
  //  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.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.Linq;
  
  using Cairo;
  using Gdk;
  using Gtk;
  using Mono.Unix;
  using Wnck;
  
  using Docky.DBus;
  using Docky.Items;
  using Docky.CairoHelper;
  using Docky.Menus;
  using Docky.Painters;
  using Docky.Services;
  using Docky.Services.Xlib;
  
  namespace Docky.Interface
  {
  	public class DockWindow : Gtk.Window
  	{
  		struct DrawValue 
  		{
  			public PointD Center;
  			public PointD StaticCenter;
  			public Gdk.Rectangle HoverArea;
  			public double Zoom;
  			
  			public DrawValue MoveIn (DockPosition position, double amount)
  			{
  				DrawValue result = new DrawValue {
  					Center = Center,
  					StaticCenter = StaticCenter,
  					HoverArea = HoverArea,
  					Zoom = Zoom
  				};
  				
  				switch (position) {
  				case DockPosition.Top:
  					result.Center.Y += amount;
  					result.StaticCenter.Y += amount;
  					break;
  				case DockPosition.Left:
  					result.Center.X += amount;
  					result.StaticCenter.X += amount;
  					break;
  				case DockPosition.Right:
  					result.Center.X -= amount;
  					result.StaticCenter.X -= amount;
  					break;
  				case DockPosition.Bottom:
  					result.Center.Y -= amount;
  					result.StaticCenter.Y -= amount;
  					break;
  				}
  				
  				return result;
  			}
  			
  			public DrawValue MoveRight (DockPosition position, double amount)
  			{
  				DrawValue result = new DrawValue {
  					Center = Center,
  					StaticCenter = StaticCenter,
  					HoverArea = HoverArea,
  					Zoom = Zoom
  				};
  				
  				switch (position) {
  				case DockPosition.Top:
  					result.Center.X += amount;
  					result.StaticCenter.X += amount;
  					break;
  				case DockPosition.Left:
  					result.Center.Y += amount;
  					result.StaticCenter.Y += amount;
  					break;
  				case DockPosition.Right:
  					result.Center.Y -= amount;
  					result.StaticCenter.Y -= amount;
  					break;
  				case DockPosition.Bottom:
  					result.Center.X -= amount;
  					result.StaticCenter.X -= amount;
  					break;
  				}
  				
  				return result;
  			}
  		}
  		
  		static DateTime UpdateTimeStamp (DateTime lastStamp, TimeSpan animationLength)
  		{
  			TimeSpan delta = DateTime.UtcNow - lastStamp;
  			if (delta < animationLength)
  				return DateTime.UtcNow.Subtract (animationLength - delta);
  			return DateTime.UtcNow;
  		}
  		
  		static DockyItem DockyItem { get; set; }
  		
  		/*******************************************
  		 * Note to reader:
  		 * All values labeled X or width reference x or width as thought of from a horizontally positioned dock.
  		 * This is because as the dock rotates, the math is largely unchanged, however there needs to be a consistent
  		 * name for these directions regardless of orientation. The catch is that when speaking to cairo, x/y are
  		 * normal
  		 * *****************************************/
  		
  		
  		
  		const int UrgentBounceHeight = 80;
  		const int LaunchBounceHeight = 30;
  		const int BackgroundWidth = 1000;
  		const int BackgroundHeight = 150;
  		const int NormalIndicatorSize = 20;
  		const int UrgentIndicatorSize = 26;
  		const int GlowSize = 30;
  		
  		const int WaitGroups = 3;
  		const int WaitArmsPerGroup = 8;
  		
  		readonly TimeSpan BaseAnimationTime = new TimeSpan (0, 0, 0, 0, 150);
  		readonly TimeSpan PainterAnimationTime = new TimeSpan (0, 0, 0, 0, 350);
  		readonly TimeSpan PanelAnimationTime = new TimeSpan (0, 0, 0, 0, 300);
  		readonly TimeSpan BounceTime = new TimeSpan (0, 0, 0, 0, 600);
  		readonly TimeSpan SlideTime = new TimeSpan (0, 0, 0, 0, 200);
  		readonly TimeSpan PulseTime = new TimeSpan (0, 0, 0, 0, 2000);
  		
  		DateTime hidden_change_time;
  		DateTime dock_hovered_change_time;
  		DateTime items_change_time;
  		DateTime painter_change_time;
  		DateTime threedimensional_change_time;
  		DateTime panel_change_time;
  		DateTime render_time;
  		DateTime remove_time;
  		
  		IDockPreferences preferences;
  		DockySurface main_buffer, background_buffer, icon_buffer, painter_buffer;
  		DockySurface normal_indicator_buffer, urgent_indicator_buffer, urgent_glow_buffer;
  		DockySurface config_hover_buffer, drop_hover_buffer, launch_hover_buffer;
  		DockySurface wait_buffer;
  		AbstractDockItem next_hoveredItem;
  		AbstractDockItem hoveredItem;
  		AbstractDockItem lastClickedItem;
  		AbstractDockPainter painter;
  		AbstractDockItem painterOwner;
  		
  		Gdk.Rectangle monitor_geo;
  		Gdk.Rectangle current_mask_area;
  		Gdk.Rectangle painter_area;
  		Gdk.Point window_position;
  		
  		double? zoom_in_buffer;
  		bool rendering;
  		bool update_screen_regions;
  		bool repaint_painter;
  		bool active_glow;
  		bool config_mode;
  		
  		/// <summary>
  		/// Used as a decimal representation of the "index" of where the old item used to be
  		/// </summary>
  		double remove_index;
  		int remove_size;
  		
  		uint animation_timer;
  		uint icon_size_timer;
  		uint size_request_timer;
  		
  		public event EventHandler<HoveredItemChangedArgs> HoveredItemChanged;
  		
  		public int Width { get; private set; }
  		
  		public int Height { get; private set; }
  		
  		bool ExternalDragActive { get { return DragTracker.ExternalDragActive; } }
  		
  		bool InternalDragActive { get { return DragTracker.InternalDragActive; } }
  		
  		bool HoveredAcceptsDrop { get; set; }
  		
  		internal AutohideManager AutohideManager { get; private set; }
  		
  		internal CursorTracker CursorTracker { get; private set; }
  		
  		internal DockDragTracker DragTracker { get; private set; }
  		
  		internal HoverTextManager TextManager { get; private set; }
  		
  		AnimationState AnimationState { get; set; }
  		
  		DockItemMenu Menu { get; set; }
  		
  		Dictionary<AbstractDockItem, DrawValue> DrawValues { get; set; }
  		
  		public IDockPreferences Preferences {
  			get { return preferences; }
  			set {
  				if (preferences == value)
  					return;
  				if (preferences != null)
  					UnregisterPreferencesEvents (preferences);
  				preferences = value;
  				RegisterPreferencesEvents (value);
  				
  				// Initialize value
  				MaxIconSize = Preferences.IconSize;
  				UpdateMaxIconSize ();
  			}
  		}
  		
  		public bool ActiveGlow {
  			get {
  				return active_glow;
  			}
  			set {
  				if (active_glow == value)
  					return;
  				active_glow = value;
  				UpdateHoverText ();
  				SetTooltipVisibility ();
  				AnimatedDraw ();
  			}
  		}
  		
  		public bool ConfigurationMode {
  			get {
  				return config_mode;
  			}
  			set {
  				if (config_mode == value)
  					return;
  				config_mode = value;
  				DragTracker.RepositionMode = config_mode;
  				if (value)
  					DragTracker.DragDisabled = false;
  				AutohideManager.ConfigMode = value;
  				UpdateScreenRegions ();
  				
  				SetTooltipVisibility ();
  				if (background_buffer != null) {
  					background_buffer.Dispose ();
  					background_buffer = null;
  				}
  				AnimatedDraw ();
  			}
  		}
  		
  		List<AbstractDockItem> collection_backend;
  		ReadOnlyCollection<AbstractDockItem> collection_frontend;
  		
  		/// <summary>
  		/// Provides a list of all items to be displayed on the dock. Nulls are
  		/// inserted where separators should go.
  		/// </summary>
  		public ReadOnlyCollection<AbstractDockItem> Items {
  			get {
  				if (collection_backend.Count == 0) {
  					UpdateScreenRegions ();
  					bool priorItems = false;
  					bool separatorNeeded = false;
  					
  					if (Preferences.DefaultProvider.IsWindowManager && DockyItem.Show) {
  						collection_backend.Add (DockyItem);
  					
  						if (!Preferences.DefaultProvider.Items.Any())
  							collection_backend.Add (new SeparatorItem ());
  					}
  					
  					foreach (AbstractDockItemProvider provider in ItemProviders) {
  						if (!provider.Items.Any ())
  							continue;
  						
  						if ((provider.Separated && priorItems) || separatorNeeded)
  							collection_backend.Add (new SeparatorItem ());
  					
  						collection_backend.AddRange (provider.Items.OrderBy (i => i.Position));
  						priorItems = true;
  						
  						separatorNeeded = provider.Separated;
  					}
  					
  					for (int i = collection_backend.Count; i < 2; i++)
  						collection_backend.Add (new SpacingItem ());
  				}
  				
  				return collection_frontend;
  			}
  		}
  		
  		#region Shortcuts
  		AutohideType Autohide {
  			get { return Preferences.Autohide; }
  		}
  		
  		internal bool DockHovered {
  			get { return AutohideManager.DockHovered; }
  		}
  		
  		internal AbstractDockItem HoveredItem {
  			get {
  				if (!DockHovered)
  					return null;
  				return hoveredItem;
  			}
  			private set {
  				if (hoveredItem == value)
  					return;
  				AbstractDockItem last = hoveredItem;
  				hoveredItem = value;
  				SetHoveredAcceptsDrop ();
  				OnHoveredItemChanged (last);
  				
  				UpdateHoverText ();
  				SetTooltipVisibility ();
  			}
  		}
  		
  		DockySurface DrawHoverText (DockySurface surface, string text)
  		{
  			using (Pango.Layout layout = DockServices.Drawing.ThemedPangoLayout ()) {
  				layout.FontDescription = Style.FontDescription;
  				layout.FontDescription.AbsoluteSize = Pango.Units.FromPixels (11);
  				layout.FontDescription.Weight = Pango.Weight.Bold;
  				layout.Ellipsize = Pango.EllipsizeMode.End;
  				layout.Width = Pango.Units.FromPixels (500);
  				
  				layout.SetText (text);
  				
  				Pango.Rectangle inkRect, logicalRect;
  				layout.GetPixelExtents (out inkRect, out logicalRect);
  				
  				const int HoverTextHeight = 26;
  				int textWidth = inkRect.Width;
  				int textHeight = logicalRect.Height;
  				int buffer = HoverTextHeight - textHeight;
  				surface = new DockySurface (Math.Max (HoverTextHeight, textWidth + buffer), HoverTextHeight, background_buffer);
  				
  				surface.Context.MoveTo ((surface.Width - textWidth) / 2, buffer / 2);
  				Pango.CairoHelper.LayoutPath (surface.Context, layout);
  				surface.Context.Color = HoverTextManager.IsLight ? new Cairo.Color (0.1, 0.1, 0.1) : new Cairo.Color (1, 1, 1);
  				surface.Context.Fill ();
  				
  				layout.Context.Dispose ();
  			}
  			
  			return surface;
  		}
  		
  		internal void UpdateHoverText ()
  		{
  			int top = (int) (IconSize * (ZoomPercent + .2));
  			Gdk.Point point = new Gdk.Point (-1, -1);
  			DockySurface hover = null;
  			
  			if (ActiveGlow) {
  				if (config_hover_buffer == null)
  					config_hover_buffer = DrawHoverText (config_hover_buffer, Catalog.GetString ("Drag to reposition"));
  				hover = config_hover_buffer;
  				top = IconSize + 2 * DockHeightBuffer;
  			} else if (hoveredItem != null && background_buffer != null) {
  				if (ExternalDragActive) {
  					if (DragTracker.ItemAcceptsDrop ()) {
  						if (launch_hover_buffer != null)
  							launch_hover_buffer.Dispose ();
  						launch_hover_buffer = DrawHoverText (launch_hover_buffer, hoveredItem.DropText);
  						hover = launch_hover_buffer;
  					} else if (DragTracker.ProviderAcceptsDrop ()) {
  						if (drop_hover_buffer == null)
  							drop_hover_buffer = DrawHoverText (drop_hover_buffer, Catalog.GetString ("Drop to add to dock"));
  						hover = drop_hover_buffer;
  					}
  				} else {
  					hover = hoveredItem.HoverTextSurface (background_buffer, Style, HoverTextManager.IsLight);
  					
  					DrawValue loc = DrawValues[hoveredItem].MoveIn (Position, IconSize * (ZoomPercent + .1) - IconSize / 2);
  					
  					point = new Gdk.Point ((int) loc.StaticCenter.X, (int) loc.StaticCenter.Y);
  					point.X += window_position.X;
  					point.Y += window_position.Y;
  				}
  			} else if (hoveredItem == null && ExternalDragActive && DragTracker.ProviderAcceptsDrop ()) {
  				if (drop_hover_buffer == null)
  					drop_hover_buffer = DrawHoverText (drop_hover_buffer, Catalog.GetString ("Drop to add to dock"));
  				hover = drop_hover_buffer;
  			} else {
  				return;
  			}
  			
  			// default centers it on the dock
  			if (point.X == -1 && point.Y == -1) {
  				int offset = 8;
  				switch (Position) {
  				default:
  				case DockPosition.Top:
  					point = new Gdk.Point (window_position.X + Allocation.Width / 2, offset + window_position.Y + top);
  					break;
  				case DockPosition.Bottom:
  					point = new Gdk.Point (window_position.X + Allocation.Width / 2, window_position.Y + Allocation.Height - top - offset);
  					break;
  				case DockPosition.Left:
  					point = new Gdk.Point (offset + window_position.X + top, window_position.Y + Allocation.Height / 2);
  					break;
  				case DockPosition.Right:
  					point = new Gdk.Point (window_position.X + Allocation.Width - top - offset, window_position.Y + Allocation.Height / 2);
  					break;
  				}
  			}
  			
  			TextManager.Gravity = Position; // FIXME
  			TextManager.Monitor = Monitor;
  			TextManager.SetSurfaceAtPoint (hover, point);
  		}
  		
  		internal AbstractDockItem ClosestItem {
  			get {
  				return Items
  					.Where (adi => !(adi is INonPersistedItem) && DrawValues.ContainsKey (adi))
  					.OrderBy (adi => Math.Abs (Preferences.IsVertical ? DrawValues[adi].Center.Y - LocalCursor.Y : DrawValues[adi].Center.X - LocalCursor.X))
  					.DefaultIfEmpty (null)
  					.FirstOrDefault ();
  			}
  		}
  		
  		internal AbstractDockItem RightMostItem {
  			get {
  				return Items
  					.Where (adi => !(adi is INonPersistedItem) && DrawValues.ContainsKey (adi))
  					.Where (adi => (Preferences.IsVertical ? DrawValues[adi].Center.Y - LocalCursor.Y : DrawValues[adi].Center.X - LocalCursor.X) > 0)
  					.OrderBy (adi => Math.Abs (Preferences.IsVertical ? DrawValues[adi].Center.Y - LocalCursor.Y : DrawValues[adi].Center.X - LocalCursor.X))
  					.DefaultIfEmpty (null)
  					.FirstOrDefault ();
  			}
  		}
  		
  		internal AbstractDockItemProvider HoveredProvider {
  			get {
  				if (!DockHovered)
  					return null;
  				
  				AbstractDockItem closest = HoveredItem ?? ClosestItem;
  				if (closest != null && closest.Owner != null) {
  					return closest.Owner;
  				}
  				
  				return Preferences.DefaultProvider;
  			}
  		}
  		
  		internal IEnumerable<AbstractDockItemProvider> ItemProviders {
  			get { return Preferences.ItemProviders; }
  		}
  
  		AbstractDockPainter Painter {
  			get { return painter; }
  			set { 
  				if (painter == value)
  					return;
  				painter_change_time = DateTime.UtcNow;
  				painter = value; 
  			}
  		}
  		
  		int PainterBufferSize {
  			get { return 2 * IconSize + 3 * DockWidthBuffer; }
  		}
  		
  		/// <summary>
  		/// Only valid durring a rendering sequence
  		/// </summary>
  		double PainterOpacity {
  			get {
  				double progress =
  					Math.Min (1, ((rendering ? render_time : DateTime.UtcNow) - painter_change_time).TotalMilliseconds / PainterAnimationTime.TotalMilliseconds);
  				if (Painter != null)
  					return progress;
  				return 1 - progress;
  			}
  		}
  		
  		internal int IconSize {
  			get { return Math.Min (MaxIconSize, Preferences.IconSize); }
  		}
  		
  		int MaxIconSize { get; set; }
  		
  		int Monitor {
  			get { return Preferences.MonitorNumber; }
  		}
  		
  		internal DockPosition Position {
  			get { return Preferences.Position; }
  		}
  		
  		bool ThreeDimensional {
  			get { return Position == DockPosition.Bottom && Preferences.ThreeDimensional; }
  		}
  		
  		
  		bool ZoomEnabled {
  			get { return !Preferences.PanelMode && Preferences.ZoomEnabled; }
  		}
  		
  		double ZoomPercent {
  			get {
  				if (!ZoomEnabled)
  					return 1;
  				return (Preferences.IconSize * Preferences.ZoomPercent) / MaxIconSize;
  			}
  		}
  		#endregion
  		
  		#region Internal Properties
  		Gdk.Point Cursor {
  			get {
  				if (Screen != CursorTracker.Screen) {
  					return new Gdk.Point (-1000, -1000);
  				}
  				return CursorTracker.Cursor; 
  			}
  		}
  		
  		Gdk.Point LocalCursor {
  			get { return new Gdk.Point (Cursor.X - window_position.X, Cursor.Y - window_position.Y); }
  		}
  		
  		int DockHeight {
  			get {
  				if (Painter != null)
  					if (Preferences.IsVertical)
  						return Math.Max (IconSize, Painter.MinimumWidth) + 2 * DockHeightBuffer;
  					else
  						return Math.Max (IconSize, Painter.MinimumHeight) + 2 * DockHeightBuffer;
  				return IconSize + 2 * DockHeightBuffer;
  			}
  		}
  		
  		int VisibleDockHeight {
  			get {
  				return ThreeDimensional ? (int) ((Preferences.PanelMode ? 0.33 : 0.4 ) * DockHeight) : DockHeight;
  			}
  		}
  		
  		int DockWidth {
  			get {
  				if (GdkWindow == null)
  					return 0;
  				
  				int dockWidth = Items.Sum (adi => (int) ((adi.Square ? IconSize : adi.IconSurface (background_buffer, IconSize, IconSize, VisibleDockHeight).Width) * 
  						Math.Min (1, (DateTime.UtcNow - adi.AddTime).TotalMilliseconds / BaseAnimationTime.TotalMilliseconds)));
  				dockWidth += 2 * DockWidthBuffer + (Items.Count - 1) * ItemWidthBuffer;
  				
  				if (remove_index != 0)
  					dockWidth += (int) ((ItemWidthBuffer + remove_size) *
  							(1 - Math.Min (1, (DateTime.UtcNow - remove_time).TotalMilliseconds / BaseAnimationTime.TotalMilliseconds)));
  				
  				return dockWidth;
  			}
  		}
  		
  		int last_painter_width;
  		int PainterDockWidth {
  			get {
  				if (Painter == null)
  					return last_painter_width;
  				
  				int max, last;
  				
  				if (Preferences.IsVertical) {
  					max = monitor_geo.Height;
  					
  					Gdk.Rectangle allocation = new Gdk.Rectangle (
  						0,
  						0,
  						IconSize,
  						DockWidth - PainterBufferSize);
  					
  					if (Painter.Allocation != allocation) {
  						Painter.SetAllocation (allocation);
  						allocation.Height = Math.Min (max - PainterBufferSize, Painter.MinimumHeight);
  						allocation.Width = Math.Max (allocation.Width, Painter.MinimumWidth);
  						Painter.SetAllocation (allocation);
  					}
  					last = allocation.Height;
  				} else {
  					max = monitor_geo.Width;
  					
  					Gdk.Rectangle allocation = new Gdk.Rectangle (
  						0,
  						0,
  						DockWidth - PainterBufferSize,
  						IconSize);
  					
  					if (Painter.Allocation != allocation) {
  						Painter.SetAllocation (allocation);
  						allocation.Width = Math.Min (max - PainterBufferSize, Painter.MinimumWidth);
  						allocation.Height = Math.Max (allocation.Height, Painter.MinimumHeight);
  						Painter.SetAllocation (allocation);
  					}
  					last = allocation.Width;
  				}
  				
  				last_painter_width = Math.Min (max, last + PainterBufferSize);
  				return last_painter_width;
  			}
  		}
  		
  		int DockHeightBuffer {
  			get { return (Preferences.PanelMode) ? 3 : 7 + (ThreeDimensional ? 5 : 0); }
  		}
  		
  		int DockWidthBuffer {
  			get { return 5; }
  		}
  		
  		int ItemWidthBuffer {
  			get { return (int) (0.08 * IconSize); }
  		}
  		
  		/// <summary>
  		/// The int size a fully zoomed icon will display at.
  		/// </summary>
  		internal int ZoomedIconSize {
  			get { 
  				return ZoomEnabled ? (int) (IconSize * ZoomPercent) : IconSize; 
  			}
  		}
  		
  		int ZoomedDockHeight {
  			get { return ZoomedIconSize + 2 * DockHeightBuffer; }
  		}
  		
  		double HideOffset {
  			get {
  				if (Painter != null)
  					return 0;
  				double progress = Math.Min (1, (render_time - hidden_change_time).TotalMilliseconds / 
  				                            BaseAnimationTime.TotalMilliseconds);
  				if (AutohideManager.Hidden)
  					return progress;
  				return 1 - progress;
  			}
  		}
  
  		double PanelModeToggleProgress {
  			get {
  				return Math.Min (1, ((rendering ? render_time : DateTime.UtcNow) - panel_change_time).TotalMilliseconds / PanelAnimationTime.TotalMilliseconds);
  			}
  		}
  		
  		double DockOpacity {
  			get {
  				return Math.Min (1, (1 - HideOffset) + Preferences.FadeOpacity);
  			}
  		}
  		
  		double ZoomIn {
  			get {
  				if (ConfigurationMode)
  					return 0;
  				
  				// we buffer this value during renders since it will be checked many times and we dont need to 
  				// recalculate it each time
  				if (zoom_in_buffer.HasValue && rendering)
  					return zoom_in_buffer.Value;
  				
  				double zoom = Math.Min (1, (render_time - dock_hovered_change_time).TotalMilliseconds / 
  				                        BaseAnimationTime.TotalMilliseconds);
  				
  				if (!DockHovered)
  					zoom = 1 - zoom;
  				
  				zoom *= 1 - PainterOpacity;
  				
  				if (rendering)
  					zoom_in_buffer = zoom;
  				
  				return zoom;
  			}
  		}
  
  		#endregion
  		
  		static DockWindow ()
  		{
  			DockyItem = new DockyItem ();
  		}
  	
  		public DockWindow () : base(Gtk.WindowType.Toplevel)
  		{
  			DrawValues = new Dictionary<AbstractDockItem, DrawValue> ();
  			Menu = new DockItemMenu (this);
  			Menu.Shown  += HandleMenuShown;
  			Menu.Hidden += HandleMenuHidden;
  			
  			TextManager = new HoverTextManager ();
  			DragTracker = new DockDragTracker (this);
  			AnimationState = new AnimationState ();
  			BuildAnimationEngine ();
  			
  			collection_backend = new List<AbstractDockItem> ();
  			collection_frontend = collection_backend.AsReadOnly ();
  			
  			AppPaintable = true;
  			AcceptFocus = false;
  			Decorated = false;
  			DoubleBuffered = false;
  			SkipPagerHint = true;
  			SkipTaskbarHint = true;
  			Resizable = false;
  			CanFocus = false;
  			TypeHint = WindowTypeHint.Dock;
  			
  			this.SetCompositeColormap ();
  			Stick ();
  			
  			AddEvents ((int) (Gdk.EventMask.ButtonPressMask |
  			                  Gdk.EventMask.ButtonReleaseMask |
  			                  Gdk.EventMask.EnterNotifyMask |
  			                  Gdk.EventMask.LeaveNotifyMask |
  					          Gdk.EventMask.PointerMotionMask |
  			                  Gdk.EventMask.ScrollMask));
  			
  			Wnck.Screen.Default.WindowOpened += HandleWindowOpened;
  			Realized                         += HandleRealized;
  			DockServices.Theme.ThemeChanged  += DockyControllerThemeChanged;
  			
  			// fix for nvidia bug
  			if (UserArgs.BufferTime > 0)
  				GLib.Timeout.Add (UserArgs.BufferTime * 60 * 1000, delegate {
  					ResetBuffers ();
  					return true;
  				});
  		}
  
  
  		#region Event Handling
  		void BuildAnimationEngine ()
  		{
  			AnimationState.AddCondition (Animations.DockHoveredChanged, 
  			                             () => (DockHovered && ZoomIn != 1) || (!DockHovered && ZoomIn != 0));
  			AnimationState.AddCondition (Animations.HideChanged,
  			                             () => ((DateTime.UtcNow - hidden_change_time) < BaseAnimationTime));
  			AnimationState.AddCondition (Animations.ItemsChanged,
  				                         () => ((DateTime.UtcNow - items_change_time) < BaseAnimationTime));
  			AnimationState.AddCondition (Animations.PainterChanged,
  			                             () => ((DateTime.UtcNow - painter_change_time) < PainterAnimationTime));
  			AnimationState.AddCondition (Animations.ThreeDimensionalChanged,
  			                             () => ((DateTime.UtcNow - threedimensional_change_time) < BaseAnimationTime));
  			AnimationState.AddCondition (Animations.PanelChanged,
  			                             () => ((DateTime.UtcNow - panel_change_time) < PanelAnimationTime));
  			AnimationState.AddCondition (Animations.ItemStatesChanged, ItemsWithStateChange);
  		}
  		
  		bool ItemsWithStateChange () {
  			DateTime now = DateTime.UtcNow;
  			
  			foreach (AbstractDockItem adi in Items) {
  				//Waiting Items
  				if ((adi.State & ItemState.Wait) != 0)
  					return true;
  				//Moving Items
  				if (now - adi.StateSetTime (ItemState.Move) < SlideTime)
  					return true;
  				//Bouncing Items
  				if ((now - adi.LastClick) < BounceTime)
  					return true;
  				if ((adi.State & ItemState.Urgent) != ItemState.Urgent)
  					continue;
  				if ((now - adi.StateSetTime (ItemState.Urgent)) < BounceTime)
  					return true;
  				//Glowing Items
  				if ((now - adi.StateSetTime (ItemState.Urgent)) < DockServices.Theme.GlowTime)
  					return true;
  			}
  			return false;
  		}
  		
  		void HandleMenuHidden (object sender, EventArgs e)
  		{
  			UpdateScreenRegions ();
  			SetTooltipVisibility ();
  			AnimatedDraw ();
  		}
  
  		void HandleMenuShown (object sender, EventArgs e)
  		{
  			UpdateScreenRegions ();
  			SetTooltipVisibility ();
  			AnimatedDraw ();
  		}
  
  		void DockyControllerThemeChanged (object sender, EventArgs e)
  		{
  			ResetBuffers ();
  			AnimatedDraw ();
  		}
  		
  		void HandleRealized (object sender, EventArgs e)
  		{
  			GdkWindow.SetBackPixmap (null, false);
  			
  			CursorTracker = CursorTracker.ForDisplay (Display);
  			CursorTracker.CursorPositionChanged += HandleCursorPositionChanged;	
  			
  			AutohideManager = new AutohideManager (Screen);
  			AutohideManager.StartupMode = true;
  			AutohideManager.Behavior = Preferences.Autohide;
  			
  			AutohideManager.HiddenChanged      += HandleHiddenChanged;
  			AutohideManager.DockHoveredChanged += HandleDockHoveredChanged;
  			
  			Screen.SizeChanged += ScreenSizeChanged;
  			
  			SetSizeRequest ();
  		}
  
  		void ScreenSizeChanged (object sender, EventArgs e)
  		{
  			Reconfigure ();
  			UpdateMaxIconSize ();
  			AnimatedDraw ();
  		}
  
  		void HandleDockHoveredChanged (object sender, EventArgs e)
  		{
  			dock_hovered_change_time = UpdateTimeStamp (dock_hovered_change_time, BaseAnimationTime);
  			
  			if (DockHovered)
  				CursorTracker.RequestHighResolution (this);
  			else
  				CursorTracker.CancelHighResolution (this);
  			
  			DragTracker.EnsureDragAndDropProxy ();
  			SetTooltipVisibility ();
  			AnimatedDraw ();
  		}
  
  		void HandleHiddenChanged (object sender, EventArgs e)
  		{
  			hidden_change_time = UpdateTimeStamp (hidden_change_time, BaseAnimationTime);
  			AnimatedDraw ();
  		}
  
  		void HandleCursorPositionChanged (object sender, CursorPostionChangedArgs e)
  		{
  			if (DockHovered && e.LastPosition != Cursor)
  				AnimatedDraw ();
  			DragTracker.EnsureDragAndDropProxy ();
  			
  			UpdateMonitorGeometry ();
  			int distance;
  			switch (Position) {
  			default:
  			case DockPosition.Top:
  				distance = e.LastPosition.Y - ZoomedDockHeight;
  				break;
  			case DockPosition.Left:
  				distance = e.LastPosition.X - ZoomedDockHeight;
  				break;
  			case DockPosition.Bottom:
  				distance = monitor_geo.Height - ZoomedDockHeight - e.LastPosition.Y;
  				break;
  			case DockPosition.Right:
  				distance = monitor_geo.Width - ZoomedDockHeight - e.LastPosition.X;
  				break;
  			}
  			if (distance > 0.3 * DockHeight)
  				HidePainter ();
  		}
  		
  		void RegisterItemProvider (AbstractDockItemProvider provider)
  		{
  			provider.ItemsChanged += ProviderItemsChanged;
  			
  			foreach (AbstractDockItem item in provider.Items)
  				RegisterItem (item);
  			
  			provider.Registered ();
  			
  			UpdateMaxIconSize ();
  			DelayedSetSizeRequest ();
  		}
  		
  		void UnregisterItemProvider (AbstractDockItemProvider provider)
  		{
  			provider.ItemsChanged -= ProviderItemsChanged;
  			
  			foreach (AbstractDockItem item in provider.Items)
  				UnregisterItem (item);
  			
  			provider.Unregistered ();
  			
  			UpdateMaxIconSize ();
  			DelayedSetSizeRequest ();
  		}
  		
  		void ProviderItemsChanged (object sender, ItemsChangedArgs args)
  		{
  			foreach (AbstractDockItem item in args.AddedItems)
  				RegisterItem (item);
  			
  			foreach (AbstractDockItem item in args.RemovedItems) {
  				remove_time = DateTime.UtcNow;
  				UnregisterItem (item);
  				
  				remove_index = Items.IndexOf (item) - .5;
  				remove_size = IconSize; //FIXME
  			}
  			
  			UpdateCollectionBuffer ();
  			UpdateMaxIconSize ();
  			DelayedSetSizeRequest ();
  			
  			AnimatedDraw ();
  			
  			// if the provider has no more items and its set to auto disable, remove it from the dock
  			AbstractDockItemProvider provider = sender as AbstractDockItemProvider;
  			
  			if (provider.Items.Count () == 0 && provider.AutoDisable)
  				preferences.RemoveProvider (provider);
  		}
  		
  		void RegisterItem (AbstractDockItem item)
  		{
  			item.SetStyle (Style);
  			item.HoverTextChanged += ItemHoverTextChanged;
  			item.PaintNeeded      += ItemPaintNeeded;
  			item.PainterRequest   += ItemPainterRequest;
  			
  			DBusManager.Default.RegisterItem (item);
  		}
  
  		void UnregisterItem (AbstractDockItem item)
  		{
  			item.HoverTextChanged -= ItemHoverTextChanged;
  			item.PaintNeeded      -= ItemPaintNeeded;
  			item.PainterRequest   -= ItemPainterRequest;
  			DrawValues.Remove (item);
  			
  			DBusManager.Default.UnregisterItem (item);
  		}
  		
  		void ItemHoverTextChanged (object sender, EventArgs e)
  		{
  			if ((sender as AbstractDockItem) == HoveredItem)
  				UpdateHoverText ();
  			AnimatedDraw ();
  		}
  		
  		void ItemPaintNeeded (object sender, PaintNeededEventArgs e)
  		{
  			AnimatedDraw ();
  		}
  
  		void ItemPainterRequest (object sender, PainterRequestEventArgs e)
  		{
  			AbstractDockItem owner = sender as AbstractDockItem;
  			
  			if (!Items.Contains (owner) || e.Painter == null)
  				return;
  			
  			if (e.Type == ShowHideType.Show) {
  				ShowPainter (owner, e.Painter);
  			} else if (e.Type == ShowHideType.Hide && Painter == e.Painter) {
  				HidePainter ();
  			}
  		}
  		
  		void RegisterPreferencesEvents (IDockPreferences preferences)
  		{
  			preferences.AutohideChanged         += PreferencesAutohideChanged;
  			preferences.PanelModeChanged        += PreferencesPanelModeChanged;
  			preferences.IconSizeChanged         += PreferencesIconSizeChanged;
  			preferences.PositionChanged         += PreferencesPositionChanged;
  			preferences.ThreeDimensionalChanged += PreferencesThreeDimensionalChanged;
  			preferences.ZoomEnabledChanged      += PreferencesZoomEnabledChanged;
  			preferences.ZoomPercentChanged      += PreferencesZoomPercentChanged;
  			preferences.ItemProvidersChanged    += PreferencesItemProvidersChanged;
  			
  			foreach (AbstractDockItemProvider provider in preferences.ItemProviders)
  				RegisterItemProvider (provider);
  		}
  
  		void UnregisterPreferencesEvents (IDockPreferences preferences)
  		{
  			preferences.AutohideChanged         -= PreferencesAutohideChanged;
  			preferences.PanelModeChanged        -= PreferencesPanelModeChanged;
  			preferences.IconSizeChanged         -= PreferencesIconSizeChanged;
  			preferences.PositionChanged         -= PreferencesPositionChanged;
  			preferences.ThreeDimensionalChanged -= PreferencesThreeDimensionalChanged;
  			preferences.ZoomEnabledChanged      -= PreferencesZoomEnabledChanged;
  			preferences.ZoomPercentChanged      -= PreferencesZoomPercentChanged;
  			preferences.ItemProvidersChanged    -= PreferencesItemProvidersChanged;
  			
  			foreach (AbstractDockItemProvider provider in preferences.ItemProviders)
  				UnregisterItemProvider (provider);
  		}
  		
  		void PreferencesItemProvidersChanged (object sender, ItemProvidersChangedEventArgs e)
  		{
  			foreach (AbstractDockItemProvider provider in e.AddedProviders)
  				RegisterItemProvider (provider);
  			foreach (AbstractDockItemProvider provider in e.RemovedProviders)
  				UnregisterItemProvider (provider);
  			UpdateCollectionBuffer ();
  			AnimatedDraw ();
  		}
  		
  		void PreferencesThreeDimensionalChanged (object sender, EventArgs e)
  		{
  			threedimensional_change_time = DateTime.UtcNow;
  			ResetBuffers ();
  			AnimatedDraw ();
  		}
  
  		void PreferencesZoomPercentChanged (object sender, EventArgs e)
  		{
  			SetSizeRequest ();
  			AnimatedDraw ();
  		}
  
  		void PreferencesZoomEnabledChanged (object sender, EventArgs e)
  		{
  			SetSizeRequest ();
  			AnimatedDraw ();
  		}
  
  		void PreferencesPositionChanged (object sender, EventArgs e)
  		{
  			Reconfigure ();
  		}
  
  		void PreferencesIconSizeChanged (object sender, EventArgs e)
  		{
  			Reconfigure ();
  			UpdateMaxIconSize ();
  			AnimatedDraw ();
  		}
  
  		void PreferencesPanelModeChanged (object sender, EventArgs e)
  		{
  			panel_change_time = DateTime.UtcNow;
  			Reconfigure ();
  		}
  
  		void PreferencesAutohideChanged (object sender, EventArgs e)
  		{
  			AutohideManager.Behavior = Autohide;
  			SetStruts ();
  		}
  		
  		void OnHoveredItemChanged (AbstractDockItem lastItem)
  		{
  			if (HoveredItemChanged != null) {
  				HoveredItemChanged (this, new HoveredItemChangedArgs (lastItem));
  			}
  		}
  		#endregion
  		
  		#region Input Handling
  		
  		protected override bool OnKeyPressEvent (EventKey evnt)
  		{
  			if (Painter != null) {
  				if (evnt.Key == Gdk.Key.Escape) {
  					HidePainter ();
  				} else if (Painter is PagingDockPainter) {
  					if (evnt.Key == Gdk.Key.Left)
  						(Painter as PagingDockPainter).PreviousPage ();
  					else if (evnt.Key == Gdk.Key.Right)
  						(Painter as PagingDockPainter).NextPage ();
  				}
  			}
  			
  			return base.OnKeyPressEvent (evnt);
  		}
  		
  		protected override bool OnMotionNotifyEvent (EventMotion evnt)
  		{
  			if (!ConfigurationMode)
  				CursorTracker.SendManualUpdate (evnt);
  			
  			if (Painter != null) {
  				int x, y;
  				
  				x = LocalCursor.X - painter_area.X;
  				y = LocalCursor.Y - painter_area.Y;
  				
  				if (painter_area.Contains (LocalCursor))
  					Painter.MotionNotify (x, y, evnt.State);
  				else
  					Painter.MotionNotify (-1, -1, ModifierType.None);
  			}
  			
  			return base.OnMotionNotifyEvent (evnt);
  		}
  		
  		protected override bool OnEnterNotifyEvent (EventCrossing evnt)
  		{
  			if (!ConfigurationMode)
  				CursorTracker.SendManualUpdate (evnt);
  			return base.OnEnterNotifyEvent (evnt);
  		}
  
  		protected override bool OnLeaveNotifyEvent (EventCrossing evnt)
  		{
  			if (!ConfigurationMode)
  				CursorTracker.SendManualUpdate (evnt);
  			
  			if (Painter != null)
  				Painter.MotionNotify (-1, -1, ModifierType.None);
  			
  			return base.OnLeaveNotifyEvent (evnt);
  		}
  		
  		protected override bool OnButtonPressEvent (EventButton evnt)
  		{
  			if (InternalDragActive || ConfigurationMode)
  				return base.OnButtonPressEvent (evnt);
  			
  			lastClickedItem = null;
  			
  			MenuButton button;
  			switch (evnt.Button) {
  			case 1:
  				button = MenuButton.Left;
  				break;
  			case 2:
  				button = MenuButton.Middle;
  				break;
  			case 3:
  				button = MenuButton.Right;
  				break;
  			default:
  				button = MenuButton.None;
  				break;
  			}
  			
  			if (Painter != null) {
  				int x, y;
  				
  				x = LocalCursor.X - painter_area.X;
  				y = LocalCursor.Y - painter_area.Y;
  				
  				if (painter_area.Contains (LocalCursor))
  					Painter.ButtonPressed (x, y, evnt.State);
  			} else if (HoveredItem != null && !(HoveredItem is SeparatorItem) && !(HoveredItem is SpacingItem)) {
  				if ((button & HoveredItem.MenuButton) == button) {
  					MenuList list;
  					
  					if (HoveredItem.Owner != null)
  						list = HoveredItem.Owner.GetMenuItems (HoveredItem);
  					else
  						list = HoveredItem.GetMenuItems ();
  					
  					if (list.Any ()) {
  						DrawValue val = DrawValues[HoveredItem];
  						val = val.MoveIn (Position, ZoomedIconSize / 2 + DockHeightBuffer);
  						Menu.Anchor = new Gdk.Point ((int) val.Center.X + window_position.X, (int) val.Center.Y + window_position.Y);
  						Menu.Orientation = Position;
  						Menu.Monitor = Monitor;
  						Menu.SetItems (list);
  						Menu.Show ();
  					}
  				} else {
  					lastClickedItem = HoveredItem;
  				}
  			} else if (button == MenuButton.Right) {
  				ShowDockyItemMenu ();
  			}
  			
  			return base.OnButtonPressEvent (evnt);
  		}
  		
  		protected override bool OnButtonReleaseEvent (EventButton evnt)
  		{
  			// This event gets fired before the drag end event, in this case
  			// we ignore it.
  			if (InternalDragActive || ConfigurationMode)
  				return base.OnButtonPressEvent (evnt);
  			
  			MenuButton button;
  			switch (evnt.Button) {
  			case 1:
  				button = MenuButton.Left;
  				break;
  			case 2:
  				button = MenuButton.Middle;
  				break;
  			case 3:
  				button = MenuButton.Right;
  				break;
  			default:
  				button = MenuButton.None;
  				break;
  			}
  			
  			if (Painter != null) {
  				int x, y;
  				
  				x = LocalCursor.X - painter_area.X;
  				y = LocalCursor.Y - painter_area.Y;
  				
  				if (!painter_area.Contains (LocalCursor))
  					HidePainter ();
  				else
  					Painter.ButtonReleased (x, y, evnt.State);
  			} else if (HoveredItem != null && !(HoveredItem is SeparatorItem) && !(HoveredItem is SpacingItem)) {
  				if (lastClickedItem == HoveredItem) {
  					Gdk.Rectangle region = DrawRegionForItem (HoveredItem);
  					
  					double x;
  					double y = Math.Min (1, Math.Max (0, (Cursor.Y - window_position.Y - region.Y) / (double) region.Height));
  					
  					if (Preferences.IsVertical)
  						x = Math.Min (1, Math.Max (0, (Cursor.X - window_position.X - region.X) / (double) region.Width));
  					else
  						x = Math.Min (1, Math.Max (0, (Cursor.X + window_position.X - region.X) / (double) region.Width));
  					
  					if (HoveredItem.RotateWithDock) {
  						if (Position == DockPosition.Top) {
  							x = 1 - x;
  							y = 1 - y;
  						} else if (Preferences.IsVertical) {
  							double tmp = x;
  							x = y;
  							y = tmp;
  							
  							if (Position == DockPosition.Left)
  								y = 1 - y;
  							else
  								x = 1 - x;
  						}
  					}
  					
  					HoveredItem.Clicked (evnt.Button, evnt.State, x, y);
  
  					AnimatedDraw ();
  				}
  			} else if (button == MenuButton.Right) {
  				ShowDockyItemMenu ();
  			}
  			
  			return base.OnButtonReleaseEvent (evnt);
  		}
  		
  		void ShowDockyItemMenu ()
  		{
  			switch (Position) {
  			case DockPosition.Bottom:
  				Menu.Anchor = new Gdk.Point (LocalCursor.X + window_position.X, window_position.Y + Allocation.Height - DockHeight + 10);
  				break;
  			case DockPosition.Top:
  				Menu.Anchor = new Gdk.Point (LocalCursor.X + window_position.X, window_position.Y + DockHeight - 10);
  				break;
  			case DockPosition.Left:
  				Menu.Anchor = new Gdk.Point (window_position.X + DockHeight - 10, LocalCursor.Y + window_position.Y);
  				break;
  			case DockPosition.Right:
  				Menu.Anchor = new Gdk.Point (window_position.X + Allocation.Width - DockHeight + 10, LocalCursor.Y + window_position.Y);
  				break;
  			}
  			Menu.Orientation = Position;
  			Menu.Monitor = Monitor;
  			Menu.SetItems (DockyItem.GetMenuItems ());
  			Menu.Show ();
  		}
  
  		protected override bool OnScrollEvent (EventScroll evnt)
  		{
  			if (InternalDragActive || ConfigurationMode)
  				return base.OnScrollEvent (evnt);
  			
  			if (Painter != null) {
  				int x, y;
  				
  				x = LocalCursor.X - painter_area.X;
  				y = LocalCursor.Y - painter_area.Y;
  				
  				if (painter_area.Contains (LocalCursor))
  					Painter.Scrolled (evnt.Direction, x, y, evnt.State);
  			} else if ((evnt.State & ModifierType.ControlMask) == ModifierType.ControlMask) {
  				if (evnt.Direction == ScrollDirection.Up)
  					Preferences.IconSize++;
  				else if (evnt.Direction == ScrollDirection.Down)
  					Preferences.IconSize--;
  				return false;
  			} else if (HoveredItem != null) {
  				HoveredItem.Scrolled (evnt.Direction, evnt.State);
  			}
  			
  			return base.OnScrollEvent (evnt);
  		}
  		#endregion
  		
  		#region Misc.
  		void HandleWindowOpened (object o, WindowOpenedArgs args)
  		{
  			UpdateScreenRegions ();
  			AnimatedDraw ();
  		}
  		
  		void Reconfigure ()
  		{
  			SetSizeRequest ();
  			Reposition ();
  			ResetBuffers ();
  			AnimatedDraw ();
  		}
  		
  		internal void SetTooltipVisibility ()
  		{
  			bool visible = (HoveredItem != null && !InternalDragActive && !ExternalDragActive &&
  								!Menu.Visible && !ConfigurationMode && Painter == null) ||
  							ActiveGlow ||
  							(ExternalDragActive && DockHovered && !(hoveredItem is INonPersistedItem));
  						
  			if (visible)
  				TextManager.Show ();
  			else
  				TextManager.Hide ();
  		}
  		
  		internal void SetHoveredAcceptsDrop ()
  		{
  			HoveredAcceptsDrop = false;
  			
  			if (HoveredItem != null && Painter == null && !ConfigurationMode)
  				DragTracker.DragDisabled = HoveredItem is INonPersistedItem;
  			
  			if (HoveredItem != null && ExternalDragActive && DragTracker.DragData != null && HoveredItem.CanAcceptDrop (DragTracker.DragData))
  				HoveredAcceptsDrop = true;
  		}
  		
  		internal void UpdateCollectionBuffer ()
  		{
  			if (rendering) {
  				// resetting during a render is bad. Complete the render, then reset.
  				GLib.Idle.Add (delegate {
  					// dispose of our separators as we made them ourselves,
  					// this could be a bit more elegant
  					foreach (AbstractDockItem item in Items.Where (adi => adi is INonPersistedItem && adi != DockyItem)) {
  						DrawValues.Remove (item);
  						item.Dispose ();
  					}
  					
  					collection_backend.Clear ();
  					return false;
  				});
  			} else {
  				foreach (AbstractDockItem item in Items.Where (adi => adi is INonPersistedItem && adi != DockyItem)) {
  					DrawValues.Remove (item);
  					item.Dispose ();
  				}
  				
  				collection_backend.Clear ();
  			}
  			
  			items_change_time = DateTime.UtcNow;
  		}
  		
  		void ResetBuffers ()
  		{
  			if (main_buffer != null) {
  				main_buffer.Dispose ();
  				main_buffer = null;
  			}
  			
  			if (painter_buffer != null) {
  				painter_buffer.Dispose ();
  				painter_buffer = null;
  			}
  			
  			if (background_buffer != null) {
  				background_buffer.Dispose ();
  				background_buffer = null;
  			}
  			
  			if (icon_buffer != null) {
  				icon_buffer.Dispose ();
  				icon_buffer = null;
  			}
  			
  
gt: 			if (normal_indicator_buffer != null) {