/* 
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * 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; version 2 of the
 * License.
 * 
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;

using MySQL.Utilities;
using MySQL.Utilities.SysUtils;

namespace MySQL.Controls
{
	/// <summary>
	/// Summary description for FlatTabControl.
	/// </summary>
	[ToolboxBitmap(typeof(System.Windows.Forms.TabControl))]

  public class FlatTabControl : System.Windows.Forms.TabControl
	{
    // Summary: determines position and look of the tabs.
    public enum TabStyleType
    {
      TopNormal,      // Tabs standing on the content above the top line. Focus color is a constant color.
      TopHanging,     // Tabs hanging down above the content. Focus color is a gradient.
      BottomNormal,   // Tabs hanging down below the content, selected color is Tab.BackColor.
      TopTransparent, // Tabs standing on the content, like TopNormal. Different colors, though.
                      // Background of space not covered by a tab is transparent. Slightly different outline too.
    }

    public enum CloseButtonVisiblity
    {
      ShowButton,
      HideButton,
      InheritVisibilitiy,
    }

    private const String tabRelayoutMessageName = "WindowsForms12_TabBaseReLayout";
    private uint tabRelayoutMessageID;

    // TabInfo keeps all necessary layout information.
    private class TabInfo
    {
      internal TabPage page;
      internal bool isValid;
      internal CloseButtonVisiblity closeButtonVisibility; // For individual close buttons.
      internal bool isBusy; // Show a busy indicator is to be shown for that tab (in place of the close button).
      internal Rectangle tabArea;
      internal Rectangle buttonArea;

      internal TabInfo()
      {
        this.page = null;
        isValid = false;
        closeButtonVisibility = CloseButtonVisiblity.InheritVisibilitiy;
      }
    }

    /// <summary> 
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

    private ImageList leftRightImages = null;
    private Bitmap darkCloseButton = null;
    private Bitmap lightCloseButton = null;
    private Dictionary<string, Bitmap> busyIndicators = new Dictionary<string, Bitmap>();
    private SubClass scroller = null;
    private Form activationTracker = null;

    private Color backColor = Color.FromArgb(0xff, 0x28, 0x37, 0x52);
    private Color topHangingDarkBackgroundColor = Color.FromArgb(0xff, 0x18, 0x25, 0x3b);
    private Color topNormalContentBackgroundColor = Color.FromArgb(0xff, 0x4a, 0x61, 0x84);
    private Color topStyleFocusedTextColor = Color.FromArgb(0xff, 0xcc, 0xd3, 0xdf);
    private Color topNormalFocusedTabGradientStart = Color.FromArgb(0xff, 0xff, 0xe9, 0xb6);

    private int glowSize = 12; // The size of the glow behind text on glass tabs.

    private bool showCloseButton = true; // Central flag for close buttons. Can be overridden by each page.
    private bool showFocusState = true;
    private bool hideWhenEmpty = false;

    // Painting/Layouting
    private TabStyleType tabStyle = TabStyleType.TopNormal;
    private List<TabInfo> layoutInfo = new List<TabInfo>();
    private int scrollOffset;
    private int pendingScrollIntoViewIndex = -1; // Set when scrollIntoView was called while we were minimized.

    // Some layout constants.
    private const int buttonSpacing = 5; // Number of pixels between text and close button.
    private const int scrollerSpacing = 5; // Distance of last tab and the scroller when scrolled to the end.
    private const int scrollScaleFactor = 5; // Number of pixel we scroll on a single scroll click.

    // The padding within the tabs.
    private Padding itemPadding = new Padding(6, 0, 6, 0);

    // The padding around the tab pages in the tab control.
    private Padding contentPadding = new Padding();

    // Maximum tab size.
    private int maxTabSize = 200;

    // Mouse handling.
    private int lastTabHit = -1;
    private Point lastClickPosition;
    private bool buttonHit = false;

    #region Construction and destruction

    public FlatTabControl()
		{
			// This call is required by the Windows.Forms Form Designer.
			InitializeComponent();

			// Some special styles for the control.
			SetStyle(ControlStyles.UserPaint, true);
			SetStyle(ControlStyles.AllPaintingInWmPaint, true);
			SetStyle(ControlStyles.DoubleBuffer, true);
			//SetStyle(ControlStyles.ResizeRedraw, true); // Doesn't really work. Need Invalidate() call in OnResize too.
			SetStyle(ControlStyles.SupportsTransparentBackColor, true);
			SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
      UpdateStyles();

			leftRightImages = new ImageList();

			System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(FlatTabControl));
			Bitmap icons = ((System.Drawing.Bitmap)(resources.GetObject("TabIcons")));
			if (icons != null)
			{
				icons.MakeTransparent(Color.White);
				leftRightImages.Images.AddStrip(icons);
			}

      // Both buttons really should have the same size.
      darkCloseButton = ((System.Drawing.Bitmap)(resources.GetObject("tab_close_dark")));
      lightCloseButton = ((System.Drawing.Bitmap)(resources.GetObject("tab_close_light")));

      CanReorderTabs = false;
      AllowDrop = false;
    }

    protected override void OnCreateControl()
    {
      base.OnCreateControl();

      FindScroller();

      // Hacker alarm: in order to allow proper scroller customization/setup we have to listen to
      //               a message registered by the tab control, which is obviously doing the scroller
      //               (up/down) setup. We need this to do our own setup. Regular Windows messages
      //               like WM_WINDOWPOSCHANGED are not sufficient.
      tabRelayoutMessageID = Win32.RegisterWindowMessage(tabRelayoutMessageName);
    }

    /// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (components != null)
				{
					components.Dispose();
				}

				leftRightImages.Dispose();
        darkCloseButton.Dispose();
        lightCloseButton.Dispose();

        foreach (Bitmap image in busyIndicators.Values)
        {
          // Must be the same delegate we used to start the animation with!
          ImageAnimator.StopAnimate(image, new EventHandler(BusyAnimationStep));
          // image.Dispose(); Images are references to our resource bundle and need to disposal.
        }
      }
			base.Dispose(disposing);
		}

    #endregion

    #region Native Windows code needed for some adjustments

    protected override void WndProc(ref System.Windows.Forms.Message m)
    {
      switch ((WM) m.Msg)
      {
        case (WM)TCM.ADJUSTRECT:
        {
          // We need to adjust the display rectangle, which directly determines where the content is shown.
          Win32.RECT rectangle = (Win32.RECT) m.GetLParam(typeof(Win32.RECT));

          // The "larger" value indicates the direction of the computation.
          // A 1 means from display rectangle to window rectangle. A 0 the other way around.
          int topOffset = 0;
          if (tabStyle == TabStyleType.TopNormal || tabStyle == TabStyleType.TopHanging || 
            tabStyle == TabStyleType.TopTransparent)
            topOffset = ItemSize.Height;
          int bottomOffset = 0;
          if (tabStyle == TabStyleType.BottomNormal)
            bottomOffset = ItemSize.Height;

          int larger = m.WParam.ToInt32();
          if (larger == 0)
          {
            rectangle.Left += Margin.Left + contentPadding.Left;
            rectangle.Right -= Margin.Right + contentPadding.Right;
            rectangle.Top += Margin.Top + ContentPadding.Top + topOffset;
            rectangle.Bottom -= Margin.Bottom + ContentPadding.Bottom + bottomOffset;
          }
          else
          {
            rectangle.Left -= Margin.Left + contentPadding.Left;
            rectangle.Right += Margin.Right + contentPadding.Right;
            rectangle.Top -= Margin.Top + contentPadding.Top + topOffset;
            rectangle.Bottom += Margin.Bottom + contentPadding.Bottom + bottomOffset;
          }

          Marshal.StructureToPtr(rectangle, m.LParam, true); 

          break;
        }

        case WM.HSCROLL:
          switch ((SB) Win32.LoWord(m.WParam.ToInt32()))
          {
            case SB.THUMBPOSITION:
              scrollOffset = -scrollScaleFactor * Win32.HiWord(m.WParam.ToInt32());
              Invalidate();
              break;
          }
          m.Result = new IntPtr(1);
          break;

        case WM.WINDOWPOSCHANGED:
          base.WndProc(ref m);
          UpdateScroller();
          if (tabStyle == TabStyleType.BottomNormal)
            AdjustLayoutInfo(null);
          break;

        case WM.SIZE:
          base.WndProc(ref m);
          if (m.WParam.ToInt32() == Win32.SIZE_RESTORED && m.LParam.ToInt32() != 0)
          {
            // Window has been restored after it was minimized.
            // Now we can recompute our layout and do other pending tasks.
            AdjustLayoutInfo(null);
            if (pendingScrollIntoViewIndex > -1)
              ScrollIntoView(pendingScrollIntoViewIndex);
            pendingScrollIntoViewIndex = -1;
          }
          break;

        case WM.NCHITTEST:
        {
          // Declare everything as being part of the client area, so we get mouse events for that.
          m.Result = new IntPtr(1); // HT_CLIENT
          break;
        }

        case WM.LBUTTONDOWN:
        {
          // Handle this message directly to keep the base class from doing unwanted things.
          uint lparam = (uint)m.LParam.ToInt32();
          int x = Win32.LoWord(lparam);
          int y = Win32.HiWord(lparam);
          MouseEventArgs args = new MouseEventArgs(MouseButtons.Left, 1, x, y, 0);
          OnMouseDown(args);
          m.Result = IntPtr.Zero;

          break;
        }
        default:
          base.WndProc(ref m);

          // The tab relayout message is a message registered by the original tab control
          // and obviously the one where the tab control also sets the position of the scroller.
          // Since we have our own view of how that should be done we listen to this message to
          // make our own adjustments.
          if (tabRelayoutMessageID != 0 && m.Msg == tabRelayoutMessageID)
            UpdateScroller();
          break;
      }
    }

    private int ScrollerWndProc(ref Message m)
    {
      switch ((WM) m.Msg)
      {
        case WM.ERASEBKGND:
          m.Result = new IntPtr(1);
          break;
        
        case WM.PAINT:
          {
            IntPtr hDC = Win32.GetDC(m.HWnd);
            Graphics g = Graphics.FromHdc(hDC);
            DrawScroller(g);
            g.Dispose();
            Win32.ReleaseDC(m.HWnd, hDC);

            Win32.RECT clientArea = new Win32.RECT();
            Win32.GetClientRect(m.HWnd, ref clientArea);
            Win32.ValidateRect(m.HWnd, ref clientArea);

            // return 0 (processed)
            m.Result = IntPtr.Zero;

          }
          return 1;

        case (WM) UDM.SETRANGE:
          // The systab control sends this message when it wants to update the scroller, however
          // that works diametrically opposed to our own intents, so it is simply switched off here.
          // Our implementation uses UDM_SETRANGE32.
          m.Result = new IntPtr(1);
          return 1;

      }

      return 0;
    }

    protected void RecalculateFrame()
    {
      // Trigger a resize event which will make the base control send the recalculate message.
      Width += 1;
      Width -= 1;
    }

    #endregion

    #region Drawing the control and its parts.

    protected override void OnPaintBackground(PaintEventArgs e)
    {
      // Do not erase the background. We either use a transparent background or completely
      // draw it in the DrawControl function.
    }

    protected override void OnPaint(PaintEventArgs e)
	  {
	    base.OnPaint(e); 
  			
	    DrawControl(e.Graphics);
	  }

		internal void DrawControl(Graphics g)
		{
			if (!Visible)
				return;

      g.SmoothingMode = SmoothingMode.HighQuality;

      bool drawFocused = showFocusState && ControlUtilities.IsHierarchyFocused(this);
      RectangleF clientArea = ClientRectangle;
      clientArea.Offset(-0.5f, -0.5f);

			// Fill client area with the control's background color
      // (everything outside the inner rectangle, e.g. including margin).
      // Exclude the transparent part if TopTransparent is set as tab style (though only if
      // we are running on Aero).
      if (tabStyle == TabStyleType.TopTransparent && ControlUtilities.IsCompositionEnabled())
      {
        int topSpace = ItemSize.Height + Margin.Top;
        clientArea.Y += topSpace;
        clientArea.Height -= topSpace;
      }

      using (Brush brush = new SolidBrush(BackgroundColor))
        g.FillRectangle(brush, clientArea);

      // Prepare other background effects (like gradient).
      // Depending on the tab style this might include or ignore the margin.
      switch (tabStyle)
      {
        case TabStyleType.TopHanging:
          {
            // In top hanging style we need a background shade simulating a shadow.
            using (LinearGradientBrush gradientBrush = new LinearGradientBrush(
              new Rectangle(0, Margin.Top, 1, ItemSize.Height),
              topHangingDarkBackgroundColor, BackgroundColor, LinearGradientMode.Vertical))
              g.FillRectangle(gradientBrush, 0, Margin.Top, clientArea.Width, ItemSize.Height);
          }
          break;

        case TabStyleType.TopNormal:
          // Fill the inner rectangle (tab control area within margins but not covered by content)
          // with the inner color but keep the top padding area the same color as that of the tab.
          if (SelectedTab != null)
          {
            RectangleF innerArea = clientArea;
            innerArea.X += Margin.Left;
            innerArea.Width -= Margin.Horizontal;
            innerArea.Y += Margin.Top + ItemSize.Height + ContentPadding.Top;
            innerArea.Height -= Margin.Vertical + ItemSize.Height + ContentPadding.Top;

            using (SolidBrush brush = new SolidBrush(topNormalContentBackgroundColor))
              g.FillRectangle(brush, innerArea);

            innerArea.Y -= ContentPadding.Top;
            innerArea.Height = ContentPadding.Top;
            Color color;
            if (drawFocused)
              color = topNormalFocusedTabGradientStart;
            else
              color = topNormalContentBackgroundColor;
            using (SolidBrush brush = new SolidBrush(color))
              g.FillRectangle(brush, innerArea);
          }

          break;

        case TabStyleType.BottomNormal:
          // Fill the inner rectangle (tab control area within margins but not covered by content or tabs)
          // with the inner color but keep the top padding area the same color as that of the tab.
          if (SelectedTab != null)
          {
            RectangleF innerArea = clientArea;
            innerArea.X += Margin.Left;
            innerArea.Width -= Margin.Horizontal;
            innerArea.Y += Margin.Top;
            innerArea.Height -= Margin.Vertical + ItemSize.Height;

            using (SolidBrush brush = new SolidBrush(SelectedTab.BackColor))
              g.FillRectangle(brush, innerArea);
          }

          break;

        case TabStyleType.TopTransparent:
          {
          }
          break;
      }

      if (TabPages.Count == 0)
        return;

      // Draw only tabs which lie at least partially in the visible area.
      Rectangle clipRect = new Rectangle(Margin.Left, Margin.Top, ClientSize.Width - Margin.Horizontal,
        ItemSize.Height);
      if (tabStyle == TabStyleType.BottomNormal)
        clipRect.Y = ClientSize.Height - Margin.Bottom - ItemSize.Height;
      g.SetClip(clipRect);

      int leftBorder = Margin.Left - scrollOffset;
      float rightBorder = clientArea.Width - Margin.Horizontal;
      for (int i = 0; i < TabCount; i++)
      {
        ValidateTab(i);
        if (layoutInfo[i].tabArea.Right > leftBorder || layoutInfo[i].tabArea.Left < rightBorder)
          DrawTab(g, i, drawFocused);
      }

      // The last thing we need is a separator line in certain styles.
      if (tabStyle == TabStyleType.TopTransparent)
      {
        // Most of the separator is gray, so draw this first and then "remove" the piece
        // below the active tab. The color of this line is that of the tab outline.
        using (Pen grayPen = new Pen(Color.FromArgb(0xff, 0x92, 0x9b, 0xa2)))
          g.DrawLine(grayPen, Margin.Left - 0.5f, Margin.Top + ItemSize.Height - 1, rightBorder, Margin.Top + ItemSize.Height - 1);

        // Fade out the gray line on the left hand side of the active tab (if it is not the first one).
        if (SelectedIndex >= 0)
        {
          RectangleF area = GetTabRect(SelectedIndex);
          area.X += 0.5f;
          if (SelectedIndex > 0)
          {
            using (LinearGradientBrush gradientBrush = new LinearGradientBrush(
              new RectangleF(area.Left - 2, 0, 4, 1),
              Color.FromArgb(0xff, 0x92, 0x9b, 0xa2), Color.FromArgb(0xff, 0xd6, 0xdc, 0xe5),
              LinearGradientMode.Horizontal))
            {
              using (Pen pen = new Pen(gradientBrush))
                g.DrawLine(pen, area.Left - 2, area.Bottom - 1, area.Left + 3, area.Bottom - 1);
            }
          }


          // The color to erase the separator line is what is the final color of the tab gradient.
          using (Pen lightPen = new Pen(Color.FromArgb(0xff, 0xd6, 0xdc, 0xe5)))
          {
            g.DrawLine(lightPen, area.Left, Margin.Top + ItemSize.Height - 1, area.Right - 2,
              Margin.Top + ItemSize.Height - 1);
          }

          // Fade out the gray line on the right hand side of the active tab.
          using (LinearGradientBrush gradientBrush = new LinearGradientBrush(
            new RectangleF(area.Right - 2, 0, 3, 1),
            Color.FromArgb(0xff, 0xd6, 0xdc, 0xe5), Color.FromArgb(0xff, 0x92, 0x9b, 0xa2),
            LinearGradientMode.Horizontal))
          {
            using (Pen pen = new Pen(gradientBrush))
              g.DrawLine(pen, area.Right - 2, area.Bottom - 1, area.Right + 1, area.Bottom - 1);
          }
        }
      }
		}

    internal void DrawTab(Graphics g, int index, bool drawFocused)
    {
	    RectangleF bounds = GetTabRect(index);

      TabPage page = TabPages[index];
      bool isSelected = (SelectedIndex == index);

      // For high quality drawing with antialiased lines we need to specify line and gradient coordinates
      // which lie between two pixels (not exactly *on* one, so adjust by a half pixel offset for these cases.
      if (tabStyle != TabStyleType.TopTransparent)
      {
        bounds.X -= 0.5f;
        bounds.Y -= 0.5f;
      }
      else
        if (isSelected)
          bounds.X -= 0.5f;

      if (isSelected || tabStyle == TabStyleType.TopTransparent)
      {
        GraphicsPath outline = GetTabOutline(bounds, index == 0);

        // Draw interior first.
        GraphicsPath interior = (GraphicsPath) outline.Clone();

        // In order to avoid artifacts result from overlapping filled interior and the border
        // move the interior down a bit.
        float border = 0; // Area not covered by gradient.
        if (tabStyle == TabStyleType.TopTransparent)
        {
          Matrix matrix = new Matrix();
          matrix.Translate(0, 0.5f);
          interior.Transform(matrix);
          border = 1.5f;
        }

        ColorBlend gradientTupels = GetTabGradient(index, drawFocused);

        using (LinearGradientBrush gradientBrush = new LinearGradientBrush(
          new RectangleF(Margin.Left, Margin.Top - 0.5f, 1, ItemSize.Height - border),
          Color.Black, Color.White,
          LinearGradientMode.Vertical))
        {
          gradientBrush.InterpolationColors = gradientTupels;
          gradientBrush.ScaleTransform(-1, -1);

          g.FillPath(gradientBrush, interior);
        }

        // Next draw the outline if the current tab style has one.
        if (tabStyle == TabStyleType.TopTransparent)
        {
          // Draw the outline of the tab. For some reasons the outline must be specified
          // "on-pixel", instead of intermediate positions. Go figure...
          // Undo the previous move of 1 pixel on the way.
          Matrix matrix = new Matrix();
          matrix.Translate(0.5f, 0);
          outline.Transform(matrix);

          int alpha = 192;
          if (isSelected)
            alpha = 255;
          using (LinearGradientBrush gradientBrush = new LinearGradientBrush(
            new RectangleF(0, Margin.Top, 1, ItemSize.Height - Margin.Top + 0.5f),
            Color.FromArgb(alpha, 0x89, 0x89, 0x8a), Color.FromArgb(alpha, 0x92, 0x9b, 0xa2),
            LinearGradientMode.Vertical))
          {
            using (Pen pen = new Pen(gradientBrush))
              g.DrawPath(pen, outline);
          }

          // The first tab is a bit special as its left hand side goes straight down, so we
          // don't have curve there. Which in turn means there is a little gap. Fill that too.
          using (Pen pen = new Pen(Color.FromArgb(0xff, 0x92, 0x9b, 0xa2)))
            g.DrawLine(pen, Margin.Left, Margin.Top + ItemSize.Height - 2, Margin.Left, Margin.Top + ItemSize.Height + 1);
        }
      }

      bool drawingOnGlass = (tabStyle == TabStyleType.TopTransparent &&
                             !isSelected &&
                             ControlUtilities.IsCompositionEnabled());

      // Tab icon. Compute position here but draw after the text (to draw it over the text glow
      // if necessary).
      bool drawImage = false;
      Point imagePosition = new Point(0, 0);
      if ((page.ImageIndex >= 0) && (ImageList != null) && (ImageList.Images[page.ImageIndex] != null))
      {
        drawImage = true;
        int iconSpacing = 4;

        imagePosition = new Point((int) bounds.X + itemPadding.Left, (int) bounds.Y);
				
        int adjustment = ImageList.ImageSize.Width + iconSpacing;

        imagePosition.Y += 1 + ((int)bounds.Height - ImageList.ImageSize.Height) / 2;
        bounds.X += adjustment;
        bounds.Width -= adjustment;
      }

      bool buttonSpaceNeeded = IsCloseButtonVisible(index) || (layoutInfo[index].isBusy);
      Rectangle buttonRect = GetTabButtonRect(index);
      
      // Tab text.
      if (page.Text.Length > 0)
      {
        using (StringFormat stringFormat = new StringFormat())
        {
          stringFormat.Alignment = StringAlignment.Near;
          stringFormat.Trimming = StringTrimming.EllipsisCharacter;
          stringFormat.FormatFlags = StringFormatFlags.NoWrap;
          stringFormat.LineAlignment = StringAlignment.Center;

          // Create format flags variant of the StringFormat for aero rendering.
          TextFormatFlags formatFlags = TextFormatFlags.EndEllipsis | TextFormatFlags.VerticalCenter |
            TextFormatFlags.SingleLine;

          Brush brush;
          if (tabStyle == TabStyleType.TopTransparent)
          {
            // In Aero we always draw a black caption.
            if (ControlUtilities.IsCompositionEnabled() || isSelected)
              brush = new SolidBrush(Color.Black);
            else
              brush = new SolidBrush(Color.White);
          }
          else
            if (isSelected)
            {
              if (tabStyle == TabStyleType.TopHanging || tabStyle == TabStyleType.TopNormal)
              {
                if (drawFocused)
                  brush = new SolidBrush(Color.Black);
                else
                  brush = new SolidBrush(topStyleFocusedTextColor);
              }
              else
                brush = new SolidBrush(Color.Black);
            }
            else
              brush = new SolidBrush(Color.White);

          int buttonPart = 0;
          if (buttonSpaceNeeded)
            buttonPart = buttonSpacing + buttonRect.Width;

          // When computing the left padding take the necessary glow around text into account, so we
          // don't add too much extra space.
          if (drawingOnGlass)
          {
            int leftSpace = itemPadding.Left > glowSize ? itemPadding.Left - glowSize : 0;
            int rightSpace = itemPadding.Right > glowSize ? itemPadding.Right - glowSize : 0;
            bounds.X += leftSpace;
            bounds.Width -= buttonPart + leftSpace + rightSpace;
          }
          else
          {
            bounds.X += itemPadding.Left;
            bounds.Width -= itemPadding.Horizontal + buttonPart;
          }
          bounds.Height -= itemPadding.Vertical;

          // For writing text on a (semi) transparent surface (e.g. when using Aero)
          // normal text output routines will mess up the ClearType/anti aliasing extra pixels
          // or the text color (sometimes both together). Hence we use a path to draw the text.
          if (tabStyle != TabStyleType.TopTransparent || isSelected)
          {
            // Since GraphicsPath.AddString renders text significantly different we use it only
            // if we really need to.
            g.DrawString(page.Text, Font, brush, bounds, stringFormat);
          }
          else
          {
            if (ControlUtilities.IsCompositionEnabled())
            {
              Rectangle intBounds = Rectangle.Ceiling(bounds);
              Win32.DrawTextOnGlass(g, page.Text, Font, intBounds, Color.Black, formatFlags, true);
            }
            else
              using (GraphicsPath textPath = new GraphicsPath())
              {
                float emSize = g.DpiY * Font.SizeInPoints / 72;
                if (tabStyle == TabStyleType.TopTransparent)
                  bounds.Y--; // Account for outline border.
                textPath.AddString(page.Text, Font.FontFamily, (int)Font.Style, emSize, bounds, stringFormat);
                g.FillPath(brush, textPath);
              }
          }
          brush.Dispose();
        }
      }

      if (drawImage)
        ImageList.Draw(g, imagePosition, page.ImageIndex);

      // Finally the close button if this tab is active (or we use the TopTransparent style or
      // the tab is marked as busy).
      if ((isSelected || tabStyle == TabStyleType.TopTransparent || layoutInfo[index].isBusy) &&
        buttonSpaceNeeded)
      {
        if (layoutInfo[index].isBusy)
        {
          Bitmap indicator;
          switch (tabStyle)
          {
            case TabStyleType.TopNormal:
              if (isSelected)
              {
                if (drawFocused)
                  indicator = GetBusyIndicatorForStyle("yellow");
                else
                  indicator = GetBusyIndicatorForStyle("blue");
              }
              else
                indicator = GetBusyIndicatorForStyle("darkblue");
              break;

            default:
              indicator = GetBusyIndicatorForStyle("white");
              break;
          }
          ImageAnimator.UpdateFrames(indicator);
          g.DrawImageUnscaled(indicator, buttonRect);
        }
        else
          if (drawFocused || tabStyle == TabStyleType.TopTransparent)
            g.DrawImage(darkCloseButton, buttonRect);
          else
            g.DrawImage(lightCloseButton, buttonRect);
      }
		}

    /// <summary>
    /// Creates the outline of a tab depending on its style.
    /// </summary>
    /// <param name="bounds">The bounding rectangle for the tab.</param>
    /// <returns>The outline of the tab as path, ready to be filled.</returns>
    private GraphicsPath GetTabOutline(RectangleF recBounds, bool isFirst)
    {
      GraphicsPath path = new GraphicsPath();
      float cornerSize = 6.5f;

      switch (tabStyle)
      {
        case TabStyleType.TopHanging:
          path.AddLine(recBounds.Left, recBounds.Top, recBounds.Right, recBounds.Top);
          path.AddLine(recBounds.Right, recBounds.Top, recBounds.Right, recBounds.Bottom - cornerSize);
          path.AddArc(recBounds.Right - cornerSize, recBounds.Bottom - cornerSize, cornerSize, cornerSize, 0, 90);
          path.AddLine(recBounds.Right - cornerSize, recBounds.Bottom, recBounds.Left + cornerSize, recBounds.Bottom);
          path.AddArc(recBounds.Left, recBounds.Bottom - cornerSize, cornerSize, cornerSize, 90, 90);
          path.AddLine(recBounds.Left, recBounds.Bottom - cornerSize, recBounds.Left, recBounds.Top);
          break;

        case TabStyleType.TopNormal:
          path.AddLine(recBounds.Right, recBounds.Bottom, recBounds.Left, recBounds.Bottom);
          path.AddLine(recBounds.Left, recBounds.Bottom, recBounds.Left, recBounds.Top - cornerSize);
          path.AddArc(recBounds.Left, recBounds.Top, cornerSize, cornerSize, 180, 90);
          path.AddLine(recBounds.Left + cornerSize, recBounds.Top, recBounds.Right - cornerSize, recBounds.Top);
          path.AddArc(recBounds.Right - cornerSize, recBounds.Top, cornerSize, cornerSize, -90, 90);
          path.AddLine(recBounds.Right, recBounds.Top + cornerSize, recBounds.Right, recBounds.Bottom);
          break;

        case TabStyleType.BottomNormal:
          path.AddLine(recBounds.Left, recBounds.Top, recBounds.Right, recBounds.Top);
          path.AddArc(recBounds.Right - cornerSize, recBounds.Bottom - cornerSize, cornerSize, cornerSize, 0, 90);
          path.AddArc(recBounds.Left, recBounds.Bottom - cornerSize, cornerSize, cornerSize, 90, 90);
          path.AddLine(recBounds.Left, recBounds.Bottom - cornerSize, recBounds.Left, recBounds.Top);
          break;

        case TabStyleType.TopTransparent:
          if (isFirst)
          {
            path.AddLine(recBounds.Left, recBounds.Bottom - 2, recBounds.Left, recBounds.Top + cornerSize);
            path.AddArc(recBounds.Left, recBounds.Top, cornerSize, cornerSize, 180, 90);
          }
          else
          {
            path.AddArc(recBounds.Left - cornerSize + 2, recBounds.Bottom - 2 - cornerSize,
              cornerSize, cornerSize, 90, -90);
            path.AddArc(recBounds.Left + 2, recBounds.Top, cornerSize, cornerSize, 180, 90);
          }
          path.AddArc(recBounds.Right - cornerSize - 2, recBounds.Top, cornerSize, cornerSize, -90, 90);
          path.AddArc(recBounds.Right - 2, recBounds.Bottom - 2 - cornerSize,
            cornerSize, cornerSize, 180, -90);
          break;
      }

      return path;
    }

    /// <summary>
    /// Creates a color gradient for a tab depending on its style.
    /// </summary>
    /// <returns>The color gradient which can be used in a gradient brush.</returns>
    private ColorBlend GetTabGradient(int index, bool drawFocused)
    {
      ColorBlend result = new ColorBlend();
      switch (tabStyle)
      {
        case TabStyleType.TopHanging:
          if (drawFocused)
          {
            result.Colors = new Color[] {
                topNormalFocusedTabGradientStart,
                // Other color constants depend on the actual gradient definition and are hence
                // not worth to be kept in a class local member.
                Color.FromArgb(0xff, 0xff, 0xe9, 0xb7),
                Color.FromArgb(0xff, 0xff, 0xf3, 0xd9),
                Color.FromArgb(0xff, 0xff, 0xf3, 0xd9),
                Color.FromArgb(0xff, 0xc9, 0xc3, 0xb8),
                Color.FromArgb(0xff, 0x6b, 0x69, 0x66)
              };

            result.Positions = new float[] {
                0, 0.45f, 0.52f, 0.75f, 0.87f, 1
              };
          }
          else
          {
            result.Colors = new Color[] {
                topNormalContentBackgroundColor,
                topNormalContentBackgroundColor,
                Color.FromArgb(0xff, 0x3a, 0x4c, 0x68),
                Color.FromArgb(0xff, 0x1f, 0x29, 0x37)
              };

            result.Positions = new float[] {
                0, 0.75f, 0.87f, 1
              };
          }
          break;

        case TabStyleType.TopNormal:
          if (drawFocused)
          {
            result.Colors = new Color[] {
                topNormalFocusedTabGradientStart,
                Color.FromArgb(0xff, 0xff, 0xe9, 0xb7),
                Color.FromArgb(0xff, 0xff, 0xf3, 0xd9),
                Color.FromArgb(0xff, 0xff, 0xfa, 0xf4)
              };

            result.Positions = new float[] {
                0, 0.34f, 0.43f, 1
              };
          }
          else
          {
            result.Colors = new Color[] {
                topNormalContentBackgroundColor,
                topNormalContentBackgroundColor
              };

            result.Positions = new float[] { 0, 1 };
          }
          break;
      
        case TabStyleType.BottomNormal:
          result.Colors = new Color[] {
              TabPages[index].BackColor,
              TabPages[index].BackColor
            };

          result.Positions = new float[] {
              0, 1
            };
          break;

        case TabStyleType.TopTransparent:
          {
            int alpha = 64;
            if (index == SelectedIndex)
              alpha = 255;
            result.Colors = new Color[] {
                Color.FromArgb(alpha, 0xd6, 0xdc, 0xe5),
                Color.FromArgb(alpha, 0xe5, 0xe9, 0xee),
                Color.FromArgb(alpha, 0xee, 0xf2, 0xf6),
                Color.FromArgb(alpha, 0xed, 0xf0, 0xf5)
              };

            result.Positions = new float[] {
                0, 0.4f, 0.49f, 1
              };
          }
          break;
      }

      return result;
    }

    /// <summary>
    /// Returns the rectangle of the given tab for drawing.
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    new public Rectangle GetTabRect(int index)
    {
      ValidateTab(index);

      Rectangle area = layoutInfo[index].tabArea;
      area.Offset(scrollOffset, 0);
      return area;
    }

    /// <summary>
    /// Returns the rectangle of the close button for drawing.
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    public Rectangle GetTabButtonRect(int index)
    {
      ValidateTab(index);

      Rectangle area = layoutInfo[index].buttonArea;
      area.Offset(scrollOffset, 0);
      return area;
    }

    /// <summary>
    /// Checks if the busy animation for the given style has been loaded already. If not
    /// the image is loaded and the animation for it started.
    /// </summary>
    /// <returns>The image registered as busy animation.</returns>
    internal Bitmap GetBusyIndicatorForStyle(string type)
    {
      if (!busyIndicators.ContainsKey(type))
      {
        System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(FlatTabControl));
        switch (type)
        {
          case "blue":
            busyIndicators[type] = ((System.Drawing.Bitmap)(resources.GetObject("busy-indicator-blue")));
            break;
          case "darkblue":
            busyIndicators[type] = ((System.Drawing.Bitmap)(resources.GetObject("busy-indicator-darkblue")));
            break;
          case "yellow":
            busyIndicators[type] = ((System.Drawing.Bitmap)(resources.GetObject("busy-indicator-yellow")));
            break;
          default:
            busyIndicators[type] = ((System.Drawing.Bitmap)(resources.GetObject("busy-indicator-white")));
            break;
        }
        ImageAnimator.Animate(busyIndicators[type], new EventHandler(BusyAnimationStep));
      }
      return busyIndicators[type];
    }

		internal void DrawScroller(Graphics g)
		{
			if ((leftRightImages == null) || (leftRightImages.Images.Count != 4))
				return;

			Rectangle clientArea = this.ClientRectangle;

      Win32.RECT r0 = new Win32.RECT();
			Win32.GetClientRect(scroller.Handle, ref r0);

      // Fill background of the scroller. Decrease width and height by 1 to
      // account for right and bottom borders which should not be used in drawing.
      r0.Right--;
      r0.Bottom--;
      Rectangle scrollerRect = new Rectangle(r0.Location, r0.Size);
			using (Brush br = new SolidBrush(SystemColors.Control))
        g.FillRectangle(br, scrollerRect);
			
      // Make a small outer border.
			using (Pen border = new Pen(SystemColors.ControlDark))
      {
        Rectangle rborder = scrollerRect;
        rborder.Inflate(-1, -1);
        g.DrawRectangle(border, rborder);
      }

			int nMiddle = (r0.Width / 2);
			int nTop = (r0.Height - leftRightImages.ImageSize.Height) / 2 + 1;
			int nLeft = (nMiddle - leftRightImages.ImageSize.Width) / 2;

			Rectangle r1 = new Rectangle(new Point(nLeft, nTop), leftRightImages.ImageSize);
			Rectangle r2 = new Rectangle(new Point(nMiddle + nLeft, nTop), leftRightImages.ImageSize);

			// Finally draw the buttons.
			Image img = leftRightImages.Images[1];
			if (img != null)
			{
				if (TabCount > 0)
				{
					Rectangle r3 = this.GetTabRect(0);
					if (r3.Left < clientArea.Left)
						g.DrawImage(img, r1);
					else
					{
						img = leftRightImages.Images[3];
						if (img != null)
							g.DrawImage(img, r1);
					}
				}
			}

			img = leftRightImages.Images[0];
			if (img != null)
			{
				if (this.TabCount > 0)
				{
					Rectangle r3 = this.GetTabRect(this.TabCount - 1);
					if (r3.Right > (clientArea.Width - r0.Width))
						g.DrawImage(img, r2);
					else
					{
						img = leftRightImages.Images[2];
						if (img != null)
							g.DrawImage(img, r2);
					}
				}
			}
		}

    /// <summary>
    /// Searches for a child control of type UpDown32, which the standard tab control
    /// maintains and we want to customize.
    /// </summary>
    private void FindScroller()
    {
      // Find the UpDown control.
      IntPtr pWnd = Win32.GetWindow(this.Handle, Win32.GW_CHILD);

      while (pWnd != IntPtr.Zero)
      {
        // Get the window class name
        char[] className = new char[33];

        int length = Win32.GetClassName(pWnd, className, 32);
        string s = new string(className, 0, length);

        if (s == "msctls_updown32")
        {
          scroller = new SubClass(pWnd, true);
          scroller.SubClassedWndProc += new SubClass.SubClassWndProcEventHandler(ScrollerWndProc);
          break;
        }

        pWnd = Win32.GetWindow(pWnd, Win32.GW_HWNDNEXT);
      }
    }

    private void UpdateScroller()
    {
      // Seems the scroll is sometimes re-created, so also check for a 0 handle.
      if (scroller == null || scroller.Handle == IntPtr.Zero)
        FindScroller();

      if (scroller != null)
      {
        Win32.RECT rect = new Win32.RECT();
        Win32.GetClientRect(scroller.Handle, ref rect);
        int totalTabWidth = GetTotalTabWidth();

        int availableWidth = ClientSize.Width - Margin.Horizontal;
        if (totalTabWidth > availableWidth)
        {
          // Decrease available space also by that used of the scroller now.
          availableWidth -= rect.Width;
          Win32.ShowWindow(scroller.Handle, (int)SW.SHOWNOACTIVATE);

          // The scroll range is the number of steps (scaled pixels) we need to scroll over the full tab space.
          int scrollRange = (int) Math.Ceiling((totalTabWidth - availableWidth) / (float) scrollScaleFactor) +
            (int) ((scrollerSpacing / (float) scrollScaleFactor));
          Win32.SendMessage(scroller.Handle, (int) UDM.SETRANGE32, IntPtr.Zero, new IntPtr(scrollRange));

          // Make sure the scroll position is still ok before setting it in the scroller.
          // Always scroll so that the right border stays close to the scroller.
          Rectangle bounds = GetTabRect(TabCount - 1);
          if (bounds.Right < ClientSize.Width - Margin.Right - scrollerSpacing - rect.Width)
            scrollOffset += ClientSize.Width - Margin.Right - scrollerSpacing - rect.Width - bounds.Right;
          Win32.SendMessage(scroller.Handle, (int)UDM.SETPOS32, IntPtr.Zero, new IntPtr(-scrollOffset / scrollScaleFactor));

          // Disable scroll acceleration by setting a single scroll increment.
          Win32.UDACCEL acceleration = new Win32.UDACCEL();
          acceleration.nInc = 5;
          acceleration.nSec = 0;
          IntPtr parameter = Marshal.AllocHGlobal(Marshal.SizeOf(acceleration));
          Marshal.StructureToPtr(acceleration, parameter, true);
          Win32.SendMessage(scroller.Handle, (int)UDM.SETACCEL, new IntPtr(1), parameter);
          Marshal.FreeHGlobal(parameter);
        }
        else
        {
          scrollOffset = 0;
          Win32.ShowWindow(scroller.Handle, (int)SW.HIDE);
        }

        Win32.InvalidateRect(scroller.Handle, ref rect, true);
        int scrollerWidth = rect.Width;
        rect.Left = Width - scrollerWidth - Margin.Right;
        rect.Right = rect.Left + scrollerWidth;

        if (tabStyle == TabStyleType.BottomNormal)
          rect.Top = ClientSize.Height - ItemSize.Height - Margin.Bottom;
        else
          rect.Top = Margin.Top;

        rect.Bottom = rect.Top + ItemSize.Height;
        Win32.MoveWindow(scroller.Handle, rect.Left, rect.Top, rect.Width, rect.Height, true);
      }
    }

    /// <summary>
    /// Computes the overall size of all tabs.
    /// </summary>
    /// <returns></returns>
    private int GetTotalTabWidth()
    {
      int result = 0;
      for (int i = 0; i < TabCount; i++)
        result += GetTabRect(i).Width;

      return result;
    }

    #endregion

    #region Event handling

    protected override void OnHandleCreated(EventArgs e)
    {
      base.OnHandleCreated(e);

      RegisterForActivation(this, new EventArgs());
    }

    /// <summary>
    /// Triggered when the form we use to track activation changes is disposed. If we get this notification
    /// it means we are still alive, which can only be true if we have been re-parented in the meantime.
    /// Hence query the current top level control and use this as new tracker.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void RegisterForActivation(object sender, EventArgs e)
    {
      activationTracker = TopLevelControl as Form;
      if (activationTracker != null)
      {
        activationTracker.Deactivate += new EventHandler(ActivationChanged);
        activationTracker.Activated += new EventHandler(ActivationChanged);
        activationTracker.Disposed += new EventHandler(RegisterForActivation);
      }
    }

    protected override void OnHandleDestroyed(EventArgs e)
    {
      base.OnHandleDestroyed(e);

      // Unregister our activation listener from our host form.
      if (activationTracker != null)
      {
        activationTracker.Deactivate -= ActivationChanged;
        activationTracker.Activated -= ActivationChanged;
      }
    }

    private void ActivationChanged(object sender, EventArgs e)
    {
      Invalidate();
    }

    override protected void OnControlAdded(ControlEventArgs e)
		{
      base.OnControlAdded(e);

      if (e.Control is TabPage)
      {
        TabPage page = e.Control as TabPage;
        page.TextChanged += new EventHandler(PageTextChanged);
        if (tabStyle == TabStyleType.BottomNormal)
          page.BackColor = Color.White;
        AdjustLayoutInfo(e.Control as TabPage);
        UpdateScroller();

        Show();
      }
		}

		override protected void OnControlRemoved(ControlEventArgs e)
		{
      base.OnControlRemoved(e);

      if (e.Control is TabPage)
      {
        TabPage page = e.Control as TabPage;
        page.TextChanged -= PageTextChanged;
        AdjustLayoutInfo(e.Control as TabPage);
        UpdateScroller();

        // The tab is still there and removed when execution returns to the caller.
        // Hence the test on 1 instead 0.
        if (!DesignMode && hideWhenEmpty && TabCount == 1)
          Hide();
      }
		}

		override protected void OnSelectedIndexChanged(EventArgs e)
		{
      base.OnSelectedIndexChanged(e);

      ScrollIntoView(SelectedIndex);
      if (scroller != null)
      {
        Win32.RECT clientArea = new Win32.RECT();
        Win32.GetClientRect(scroller.Handle, ref clientArea);
        Win32.InvalidateRect(scroller.Handle, ref clientArea, false);
      }
			Invalidate();	// We need to update border and background colors.
		}

    override protected void OnMouseDown(MouseEventArgs e)
    {
      switch (e.Button)
      {
        case MouseButtons.Left:
          Focus();
          lastTabHit = TabIndexFromPosition(e.Location);
          if (lastTabHit > -1)
          {
            lastClickPosition = e.Location;
            if (SelectedIndex != lastTabHit)
              SelectedIndex = lastTabHit;
            else
              buttonHit = GetTabButtonRect(lastTabHit).Contains(e.Location);
          }
          break;

        default:
          base.OnMouseDown(e);
          break;
      }
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
      switch (e.Button)
      {
        case MouseButtons.Left:
          if (lastTabHit > -1)
          {
            if (buttonHit && GetTabButtonRect(lastTabHit).Contains(e.Location) &&
              (TabCount > 1 || CanCloseLastTab))
            {
              // Close tab if the application agrees.
              TabPage page = TabPages[lastTabHit];
              CloseTabPage(page);
            }
          }
          break;

        default:
          base.OnMouseUp(e);
          break;
      }
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
      switch (e.Button)
      {
        case MouseButtons.Left:
          if (lastTabHit > -1 && !buttonHit && CanReorderTabs &&
            (Math.Abs(lastClickPosition.X - e.Location.X) > 5 || Math.Abs(lastClickPosition.Y - e.Location.Y) > 5))
          {
            TabPage page = TabPages[lastTabHit];
            if (DoDragDrop(page, DragDropEffects.Move) == DragDropEffects.Move)
            {
              int newIndex = TabPages.IndexOf(page);
              OnTabMoved(new TabMovedEventArgs(lastTabHit, newIndex));
            }
          }
          else
            base.OnMouseMove(e);
          break;

        default:
          base.OnMouseMove(e);
          break;
      }
    }

    protected override void OnDragOver(DragEventArgs e)
    {
      // See if a tab page is being dragged.
      if (e.Data.GetData(typeof(TabPage)) == null)
      {
        base.OnDragOver(e);
        return;
      }

      TabPage dragTab = (TabPage) e.Data.GetData(typeof(TabPage));
      int dragTabIndex = TabPages.IndexOf(dragTab);
      if (dragTabIndex < 0)
      {
        // Tab page doesn't belong to this view. Ignore it.
        e.Effect = DragDropEffects.None;
        return;
      }

      // Hover over a tab?
      int hoverIndex = TabIndexFromPosition(PointToClient(new Point(e.X, e.Y)));
      if (hoverIndex < 0)
      {
        e.Effect = DragDropEffects.None;
        return;
      }

      e.Effect = DragDropEffects.Move;

      // Do this check after we set the drop effect so we don't get a block cursor
      // when we already moved a page and still hover with the mouse over that.
      if (dragTabIndex == hoverIndex)
        return;

      Rectangle dragTabRect = GetTabRect(dragTabIndex);
      Rectangle hoverTabRect = GetTabRect(hoverIndex);

      if (dragTabRect.Width < hoverTabRect.Width)
      {
        Point tcLocation = PointToScreen(Location);

        if (dragTabIndex < hoverIndex)
        {
          if ((e.X - tcLocation.X) > ((hoverTabRect.X + hoverTabRect.Width) - dragTabRect.Width))
            MovePage(dragTab, hoverIndex);
        }
        else
          if (dragTabIndex > hoverIndex)
          {
            if ((e.X - tcLocation.X) < (hoverTabRect.X + dragTabRect.Width))
              MovePage(dragTab, hoverIndex);
          }
      }
      else
        MovePage(dragTab, hoverIndex);
    }

    protected override void OnEnter(EventArgs e)
    {
      base.OnEnter(e);
      Invalidate();
    }

    protected override void OnLeave(EventArgs e)
    {
      base.OnLeave(e);
      Invalidate();
    }

    protected override void OnSizeChanged(EventArgs e)
    {
      if (IsHandleCreated)
      {
        this.BeginInvoke((MethodInvoker) delegate
        {
          base.OnSizeChanged(e);
        });
      }
    }

    void PageTextChanged(object sender, EventArgs e)
    {
      AdjustLayoutInfo(sender as TabPage);
    }

    public event EventHandler<TabClosingEventArgs> TabClosing;
    protected internal void OnTabClosing(TabClosingEventArgs args)
    {
      if (TabClosing != null)
        TabClosing(this, args);
    }

    public event EventHandler<TabClosedEventArgs> TabClosed;
    protected internal void OnTabClosed(TabClosedEventArgs args)
    {
      if (TabClosed != null)
        TabClosed(this, args);
    }

    public event EventHandler<TabMovingEventArgs> TabMoving;
    protected internal void OnTabMoving(TabMovingEventArgs args)
    {
      if (TabMoving != null)
        TabMoving(this, args);
    }

    public event EventHandler<TabMovedEventArgs> TabMoved;
    protected internal void OnTabMoved(TabMovedEventArgs args)
    {
      if (TabMoved != null)
        TabMoved(this, args);
    }

    #endregion

    #region Component Designer generated code

    /// <summary>
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			components = new System.ComponentModel.Container();
		}

		#endregion

    #region Other implementation

    /// <summary>
    /// Determines if the the close button for a given tab should be shown.
    /// </summary>
    private bool IsCloseButtonVisible(int index)
    {
      return (layoutInfo[index].closeButtonVisibility == CloseButtonVisiblity.ShowButton) ||
        (layoutInfo[index].closeButtonVisibility == CloseButtonVisiblity.InheritVisibilitiy && showCloseButton);
    }

    /// <summary>
    /// Adjusts the size of the layout info list and marks all entries that follow the given page
    /// as invalid, so that they re-compute their layout next time it is required.
    /// </summary>
    private void AdjustLayoutInfo(TabPage page)
    {
      if (layoutInfo.Count > TabCount)
        layoutInfo.RemoveRange(TabCount, layoutInfo.Count - TabCount);

      for (int i = 0; i < TabCount; i++)
      {
        if (i >= layoutInfo.Count)
          layoutInfo.Add(new TabInfo());
        layoutInfo[i].page = TabPages[i];
      }

      bool invalidate = (page == null) ? true : false;
      foreach (TabInfo entry in layoutInfo)
      {
        // Invalidate this page and all following.
        if (entry.page == page)
          invalidate = true;
        if (invalidate)
          entry.isValid = false;
      }
    }

    /// <summary>
    /// Recomputes layout information for this tab if not yet done.
    /// </summary>
    private void ValidateTab(int index)
    {
      if (!layoutInfo[index].isValid)
      {
        Graphics g = CreateGraphics();

        // Find first valid entry before the given one.
        // We have to validate all entries after that until this one.
        int currentIndex = index;
        while (currentIndex >= 0 && !layoutInfo[currentIndex].isValid)
          currentIndex--;

        int offsetX = Margin.Left;
        if (currentIndex > -1)
          offsetX = layoutInfo[currentIndex].tabArea.Right;
        int offsetY = Margin.Top;
        if (tabStyle == TabStyleType.BottomNormal)
          offsetY = ClientSize.Height - ItemSize.Height - Margin.Bottom;
        while (++currentIndex <= index)
        {
          TabInfo info = layoutInfo[currentIndex];
          int textWidth = 0;

          if (info.page.Text.Length > 0)
          {
            // In order to determine the space needed for text we do a double measurement.
            // The reason is that we need to render text in two different ways depend on tab style
            // and selection state of the tab. Main reason is that certain styles are semi-transparent
            // which does not fit at all to ClearType-rendered text.
            SizeF measuredTextSize = g.MeasureString(info.page.Text, Font);
            using (GraphicsPath textPath = new GraphicsPath())
            {
              // This path only for text on semi-transparent tabs.
              StringFormat stringFormat = new StringFormat();

              stringFormat.Alignment = StringAlignment.Near;
              stringFormat.FormatFlags = StringFormatFlags.NoWrap;

              float emSize = g.DpiY * Font.Size / 72;
              textPath.AddString(info.page.Text, Font.FontFamily, (int)Font.Style, emSize,
                new Point(0, 0), stringFormat);
              RectangleF textSize = textPath.GetBounds();

              // The following computation does some additional magic. Even though for string
              // measurement and drawing (in DrawTab) we use the same calls the GraphicsPath.AddString
              // method sees its own width computation as too small (thus adding ellipsis where not needed).
              // By trial-and-error I found a difference of 6 pixels, which partially can be compensated
              // by the left offset. The rest is just the remaining rest.
              textWidth = (int)Math.Ceiling(textSize.Left + textSize.Width + 3);
            }

            // Finally use the wider of both widths.
            if (measuredTextSize.Width > textWidth)
              textWidth = (int)Math.Ceiling(measuredTextSize.Width);
          }

          int buttonPart = 0;
          bool buttonSpaceNeeded = IsCloseButtonVisible(currentIndex) || layoutInfo[currentIndex].isBusy;
          Size buttonSize;
          if (layoutInfo[index].isBusy)
          {
            // Assuming here all busy indicator images are of the same size.
            // Not a strong limitation actually, since all tabs are of same height too.
            System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(FlatTabControl));
            Bitmap image = ((System.Drawing.Bitmap)(resources.GetObject("busy-indicator-blue")));
            buttonSize = image.Size;
          }
          else
            buttonSize = new Size(darkCloseButton.Width, darkCloseButton.Height);
          if (buttonSpaceNeeded)
            buttonPart = buttonSpacing + buttonSize.Width;
          int tabWidth = itemPadding.Horizontal + textWidth + buttonPart;
          if ((info.page.ImageIndex >= 0) && (ImageList != null) &&
            (ImageList.Images[info.page.ImageIndex] != null))
          {
            tabWidth += ImageList.ImageSize.Width;
            
            // Add 4 px spacing if there is something after the icon.
            if (buttonSpaceNeeded || textWidth > 0)
              tabWidth += 4;
          }

          if (ItemSize.Width > tabWidth)
            tabWidth = ItemSize.Width;
          if (tabWidth > maxTabSize)
            tabWidth = maxTabSize;
          info.tabArea = new Rectangle(offsetX, offsetY, tabWidth, ItemSize.Height);
          offsetX = info.tabArea.Right;

          if (buttonSpaceNeeded)
          {
            int buttonOffset = offsetY + (info.tabArea.Height - buttonSize.Height) / 2;
            info.buttonArea = new Rectangle(offsetX - itemPadding.Right - buttonSize.Width, buttonOffset,
              buttonSize.Width, buttonSize.Height);
          }
          else
            info.buttonArea = Rectangle.Empty;

          info.isValid = true;
        }

        g.Dispose();
      }
    }

    /// <summary>
    /// Allows to set individual close buttons for a tab.
    /// </summary>
    public void SetCloseButtonVisibility(int index, CloseButtonVisiblity visibility)
    {
      AdjustLayoutInfo(TabPages[index]);
      layoutInfo[index].closeButtonVisibility = visibility;
    }

    /// <summary>
    /// Mark individual tabs as busy causing them to show a busy indicator instead of the close button.
    /// </summary>
    public void SetBusy(int index, bool busy)
    {
      if (layoutInfo[index].isBusy != busy)
      {
        if (!busy)
        {
          layoutInfo[index].isBusy = false;
          AdjustLayoutInfo(TabPages[index]);
        }
        else
        {
          AdjustLayoutInfo(TabPages[index]);
          layoutInfo[index].isBusy = true;
        }
        Invalidate();
      }
    }

    private void BusyAnimationStep(object sender, EventArgs args)
    {
      foreach (TabInfo info in layoutInfo)
        if (info.isBusy)
        {
          Rectangle area = info.tabArea;
          area.Offset(scrollOffset, 0);
          Invalidate(area, false);
        }
    }

    /// <summary>
    /// Does the actual work to close a tab page, sending out appropriate events and updating
    /// internal structures. Returns true if the page was actually closed.
    /// </summary>
    public bool CloseTabPage(TabPage page)
    {
      if (TabCount > 1 || CanCloseLastTab)
      {
        TabClosingEventArgs args = new TabClosingEventArgs(page, true);
        OnTabClosing(args);
        if (args.canClose)
        {
          // The page could be removed in the OnTabClosing event, so prepare for that.
          int tabIndex = TabPages.IndexOf(page);
          if (tabIndex > -1)
          {
            SetBusy(TabPages.IndexOf(page), false);

            // Remove this tab and select its predecessor if possible.
            if (SelectedIndex > 0)
              SelectedIndex = SelectedIndex - 1;
            lastTabHit = -1;
            TabPages.Remove(page);
          }

          AdjustLayoutInfo(null);
          Update();

          OnTabClosed(new TabClosedEventArgs(page));

          // In the case OnTabClosed did not dispose of the page do it now.
          if (!page.IsDisposed)
            page.Dispose();

          return true;
        }
      }
      return false;
    }

    /// <summary>
    /// Determines if the given position lies within the bounds of a tab and, if so, returns the tab's
    /// index to the caller.
    /// </summary>
    /// <param name="point">The position to check for in local coordinates.</param>
    /// <returns>The index of the tab found or -1 if none.</returns>
    public int TabIndexFromPosition(Point point)
    {
      for (int i = 0; i < TabCount; i++)
        if (GetTabRect(i).Contains(point))
          return i;
      return -1;
    }

    /// <summary>
    /// Adjusts the scroll offset so that the given tab is in the visible area.
    /// </summary>
    /// <param name="index">The index of the tab to make visible.</param>
    /// <returns>True if an adjustment was necessary, otherwise False.</returns>
    public bool ScrollIntoView(int index)
    {
      bool result = false;
      if (index < 0 || index >= TabCount)
        return result;

      // If our bounds are empty then the application is likely minimized and we delay setting
      // the horizontal offset until we are restored.
      if (Width == 0 && Height == 0)
      {
        pendingScrollIntoViewIndex = index;
        return result;
      }

      Rectangle area = GetTabRect(index);
      if (area.Left < 0)
      {
        scrollOffset -= area.Left;
        result = true;
      }
      else
      {
        Win32.RECT rect = new Win32.RECT();
        if (scroller != null)
          Win32.GetClientRect(scroller.Handle, ref rect);

        int rightBorder = ClientSize.Width - Margin.Right - rect.Width - scrollerSpacing;
        if (area.Right > rightBorder)
        {
          scrollOffset -= area.Right - rightBorder;
          result = true;
        }
      }

      if (result && scroller != null)
        Win32.SendMessage(scroller.Handle, (int)UDM.SETPOS32, IntPtr.Zero, new IntPtr(-scrollOffset / scrollScaleFactor));
      return result;
    }

    /// <summary>
    /// Moves the given page to the new index.
    /// </summary>
    private void MovePage(TabPage page, int newIndex)
    {
      int oldIndex = TabPages.IndexOf(page);

      TabMovingEventArgs args = new TabMovingEventArgs(oldIndex, newIndex);
      OnTabMoving(args);
      if (args.Cancel)
        return;

      bool selectTab = (oldIndex == SelectedIndex);

      // The simple approach here would be to remove the page and insert it at the new location.
      // However this will produce heavy flickering, so we move the pages one by one instead.
      // While moving the pages also move their corresponding tab info to keep current states.
      TabInfo oldInfo = layoutInfo[oldIndex];
      if (oldIndex < newIndex)
      {
        for (int index = oldIndex; index < newIndex; index++)
        {
          TabPages[index] = TabPages[index + 1];
          layoutInfo[index] = layoutInfo[index + 1];
        }
        TabPages[newIndex] = page;
        layoutInfo[newIndex] = oldInfo;
        AdjustLayoutInfo(TabPages[oldIndex]);
      }
      else
      {
        for (int index = oldIndex; index > newIndex; index--)
        {
          TabPages[index] = TabPages[index - 1];
          layoutInfo[index] = layoutInfo[index - 1];
        }
        TabPages[newIndex] = page;
        layoutInfo[newIndex] = oldInfo;
        AdjustLayoutInfo(page);
      }

      // Keep the tab selected which was selected before.
      if (selectTab)
        SelectedIndex = newIndex;

      Invalidate();
    }

    // In order to simplify general handling of page content (which is very often just a container
    // with the actual content) we introduce here the concept of documents.
    // A document is the content of a tab. When using this concept you should only have one control on each
    // tab to make this work. Otherwise use the tab pages directly and handle search etc. yourself.
    // See also ITabDocument and TabDocument and the Documents property below.

    /// <summary>
    /// Scans the given control and all its children to find a docked ITabDocument.
    /// Due to various docking structures the document can be anywhere in the hierarchy.
    /// </summary>
    /// <param name="control"></param>
    /// <returns></returns>
    private ITabDocument DocumentFromHierarchy(Control control)
    {
      if (control is ITabDocument)
        return control as ITabDocument;

      foreach (Control child in control.Controls)
      {
        ITabDocument result = DocumentFromHierarchy(child);
        if (result != null)
          return result;
      }

      return null;
    }

    /// <summary>
    /// Returns the document (i.e. the first control on a tab page) whose page has the given title.
    /// </summary>
    /// <param name="text"></param>
    /// <returns></returns>
    public ITabDocument FindDocument(string text)
    {
      foreach (TabPage page in TabPages)
        if (page.Text == text)
        {
          ITabDocument result = DocumentFromHierarchy(page);
          if (result != null)
            return result;
        }

      return null;
    }

    /// <summary>
    /// Returns the document stored on the tab page with the given index or null if there is none.
    /// </summary>
    public ITabDocument DocumentFromIndex(int index)
    {
      return DocumentFromPage(TabPages[index]);
    }

    /// <summary>
    /// Returns the index for the given document or -1 if not found.
    /// </summary>
    public int IndexFromDocument(ITabDocument document)
    {
      int i = 0;
      foreach (TabPage page in TabPages)
      {
        if (DocumentFromHierarchy(page) == document)
          return i;
        i++;
      }

      return -1;
    }

    /// <summary>
    /// Returns the document stored on the given tab page or null if there is none.
    /// </summary>
    public ITabDocument DocumentFromPage(TabPage page)
    {
      return DocumentFromHierarchy(page);
    }

    /// <summary>
    /// Checks if the document is the first control on any of our pages.
    /// </summary>
    /// <returns>True if the document was found, otherwise false.</returns>
    public bool HasDocument(ITabDocument document)
    {
      foreach (TabPage page in TabPages)
        if (DocumentFromHierarchy(page) == document)
          return true;

      return false;
    }

    /// <summary>
    /// Adds the given document to a new tab page and returns the index of that new page.
    /// </summary>
    /// <param name="document"></param>
    /// <returns></returns>
    public int AddDocument(ITabDocument document)
    {
      TabPage page = new TabPage();
      TabDocument control = document as TabDocument;
      control.SetHost(page);
      page.Controls.Add(control);
      control.Dock = DockStyle.Fill;
      control.Margin = new Padding(0);
      control.Padding = new Padding(0);
      control.Show();
      TabPages.Add(page);

      return TabPages.Count - 1;
    }

    /// <summary>
    /// Removes a previously added document from this control. This method removes the hosting tab,
    /// regardless of other content on it. The document itself is not freed.
    /// </summary>
    /// <param name="document"></param>
    public void RemoveDocument(ITabDocument document)
    {
      foreach (TabPage page in TabPages)
        if (DocumentFromHierarchy(page) == document)
        {
          page.Controls.Clear();
          TabPages.Remove(page);

          return;
        }
    }

    /// <summary>
    /// Tries to close the given document by sending out TabClosing. If that returns true
    /// the document is closed and its hosting tab removed.
    /// </summary>
    /// <returns>True if the document was closed, otherwise false.</returns>
    public bool CloseDocument(ITabDocument document)
    {
      foreach (TabPage page in TabPages)
        if (DocumentFromHierarchy(page) == document)
          return CloseTabPage(page);

      return false;
    }

    /// <summary>
    /// Returns the list of documents in an array, e.g. to allow manipulation of this list in a loop.
    /// </summary>
    /// <returns></returns>
    public ITabDocument[] DocumentsToArray()
    {
      ITabDocument[] result = new ITabDocument[TabCount];
      int i = 0;
      foreach (ITabDocument content in Documents)
        result[i++] = content;

      return result;
    }

    #endregion

    #region Properties

    /// <summary>
    /// Returns the currently active document if there is one.
    /// </summary>
    public ITabDocument ActiveDocument
    {
      get
      {
        if (SelectedTab != null)
          return DocumentFromHierarchy(SelectedTab);
        return null;
      }
    }

    /// <summary>
    /// Helper enumeration that returns the control which represents an ITabDocument on each page
    /// or null if there is none on the specific page.
    /// </summary>
    /// <returns></returns>
    public IEnumerable<ITabDocument> Documents
    {
      get
      {
        foreach (TabPage page in TabPages)
          yield return DocumentFromHierarchy(page);
      }
    }

		public TabStyleType TabStyle
		{
			get { return tabStyle; }
			set 
      {
        if (value != tabStyle)
        {
          tabStyle = value;
          RecalculateFrame();
          AdjustLayoutInfo(null);
          UpdateScroller();
        }
      }
		}

    public Padding ItemPadding
    {
      get { return itemPadding; }
      set
      {
        itemPadding = value;
        AdjustLayoutInfo(null);
        Invalidate();
      }
    }

    public Padding ContentPadding
    {
      get { return contentPadding; }
      set
      {
        contentPadding = value;
        AdjustLayoutInfo(null);
        Invalidate();
      }
    }

    [Browsable(true)]
		public Color BackgroundColor
		{
			get { return backColor; }
			set
      {
        if (value != backColor)
        {
          backColor = value;
          Invalidate();
        }
      }
		}

    [Browsable(true)]
    public bool ShowCloseButton
    {
      get { return showCloseButton; }
      set
      {
        if (value != showCloseButton)
        {
          showCloseButton = value;
          RecalculateFrame();
          AdjustLayoutInfo(null);
          Invalidate();
        }
      }
    }

    [Browsable(true)]
    public bool ShowFocusState
    {
      get { return showFocusState; }
      set
      {
        if (value != showFocusState)
        {
          showFocusState = value;
          Invalidate();
        }
      }
    }

    [Browsable(true)]
    public bool CanCloseLastTab { get; set; }

    [Browsable(true)]
    public bool HideWhenEmpty
    {
      get { return hideWhenEmpty; }
      set
      {
        if (value != hideWhenEmpty)
        {
          hideWhenEmpty = value;
          if (!DesignMode && hideWhenEmpty && TabCount == 0)
            Hide();
          else
            Show();
        }
      }
    }

    [Browsable(true)]
    public int MaxTabSize
    {
      get { return maxTabSize; }
      set
      {
        if (value < ItemSize.Width)
          value = ItemSize.Width;
        if (value != maxTabSize)
        {
          maxTabSize = value;
          RecalculateFrame();
          AdjustLayoutInfo(null);
          Invalidate();
        }
      }
    }

    [Browsable(true)]
    public bool CanReorderTabs { get; set; }

    // With the switch to .NET 4.0 client profile we have to separate runtime
    // and design time code.
    [Editor("MySQL.Controls.FlatTabControlCollectionEditor", "UITypeEditor")]
    public new TabPageCollection TabPages
    {
      get
      {
        return base.TabPages;
      }
    }

    #endregion
	}

  //------------------------------------------------------------------------------------------------

  // In order to aid managing documents on tab pages (instead of plain controls) we define an interface and 
  // a base class which implements it, to be used by the application, if it wants to maintain
  // the "document metaphor".
  public interface ITabDocument
  {
    void Activate();
    void Close();

    String TabText { get; set; }
    Control Content { get; }
    int ToolbarHeight { get; }
  }

  public class TabDocument : Form, ITabDocument
  {
    private TabPage host = null;
    private String tabText = "";
    private String toolTipText = "";

    public TabDocument()
    {
      FormBorderStyle = FormBorderStyle.None;
      TopLevel = false;
    }

    new virtual public void Show()
    {
      base.Show();
    }

    new public void Activate()
    {
      Show();

      base.Activate();
      (host.Parent as TabControl).SelectedTab = host;
    }

    public void SetHost(TabPage host)
    {
      if (this.host != host)
      {
        if (this.host != null)
        {
          FlatTabControl tabControl = this.host.Parent as FlatTabControl;
          if (tabControl != null)
          {
            // Remove the tab page this document was docked to if it gets moved to another one.
            this.host.Controls.Clear();
            tabControl.TabPages.Remove(this.host);
            tabControl.OnTabClosed(new TabClosedEventArgs(this.host));
          }
        }

        this.host = host;
        if (host != null)
        {
          host.Text = tabText;
          host.ToolTipText = toolTipText;
        }
      }
    }

    protected override void Dispose(bool disposing)
    {
      host = null;
      base.Dispose(disposing);
    }

    /// <summary>
    /// Called when the document is closed already. Just remove its tab page.
    /// No need to trigger tab closing and closed events.
    /// </summary>
    override protected void OnFormClosed(FormClosedEventArgs e)
    {
      if (host != null && host.Parent != null)
      {
        host.Controls.Clear();
        (host.Parent as FlatTabControl).TabPages.Remove(host);
      }
      base.OnFormClosed(e);
    }

    public virtual String TabText
    {
      get { return tabText; }
      set
      {
        tabText = value;
        if (host != null)
          host.Text = value;
      }
    }

    public virtual Control Content
    {
      get { return this; }
    }

    public int ToolbarHeight
    {
      get {
        if (Controls.Count > 0 && Controls[0] is Panel)
        {
          // If this tab document has a menu and/or toolbar then it is placed on a panel
          // in the root of this document.
          Panel container = Controls[0] as Panel;
          if (container.Controls.Count > 0 && (container.Controls[0] is ToolStrip))
            return container.Height;
        }
        return 0;
      }
    }

    public virtual String ToolTipText
    {
      get { return toolTipText; }
      set
      {
        toolTipText = value;
        if (host != null)
          host.ToolTipText = value;
      }
    }
  }

  #region Event argument classes

  public class TabClosingEventArgs : EventArgs
  {
    public TabPage page;
    public bool canClose;

    public TabClosingEventArgs(TabPage page, bool canClose)
    {
      this.page = page;
      this.canClose = canClose;
    }
  }

  public class TabClosedEventArgs : EventArgs
  {
    public TabPage page;

    public TabClosedEventArgs(TabPage page)
    {
      this.page = page;
    }
  }

  public class TabMovedEventArgs : EventArgs
  {
    public int FromIndex, ToIndex;

    public TabMovedEventArgs(int from, int to)
    {
      FromIndex = from;
      ToIndex = to;
    }
  }

  public class TabMovingEventArgs : EventArgs
  {
    public int FromIndex, ToIndex;
    public bool Cancel;

    public TabMovingEventArgs(int from, int to)
    {
      FromIndex = from;
      ToIndex = to;
      Cancel = false;
    }
  }

  #endregion

}
