{==============================================================================

  Custom Controls
  Copyright (C) 2000-2001 by Evgeny Kryukov
  All rights reserved

  All contents of this file and all other files included in this archive
  are Copyright (C) 2001 Evgeny Kryukov. Use and/or distribution of
  them requires acceptance of the License Agreement.

  See License.txt for licence information

===============================================================================}

unit KsTabs;

{$I KsCommonVer.Inc}
{$T-,W-,X+,P+}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, KsBitmap, KsControls, KsUtils;

const
  CScrollDelayInterval = 300;
  CScrollSpeedInterval = 100;
  CMaxTabRowCount = 100;

type

  TTabRow = record
    LastTab: integer;
    ExternalTabWidth: integer;
    ExternalRest: integer;
  end;

  TTabRows = array[0..CMaxTabRowCount - 1] of TTabRow;

  TTabPosition = (tpTop, tpBottom, tpLeft, tpRight);

  TKsCustomTabControl = class;

  TTabChangingEvent = procedure(Sender: TObject; var AllowChange: Boolean) of object;
  TDrawTabEvent = procedure(Control: TKsCustomTabControl; TabIndex: Integer;
    const Rect: TRect; Active: Boolean) of object;

{ TKsCustomTabSheet }

  TKsCustomTabSheet = class(TCustomControl)
  private
    FDrawCount: integer;
    FTabControl: TKsCustomTabControl;
    FOnHide: TNotifyEvent;
    FOnShow: TNotifyEvent;
    function GetTabIndex: Integer;
    procedure SetTabControl(ATabControl: TKsCustomTabControl);
    procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
    procedure WMEraseBkgnd(var Msg: TWmEraseBkgnd); message WM_ERASEBKGND;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
    procedure DoHide; dynamic;
    procedure DoShow; dynamic;
    procedure ReadState(Reader: TReader); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property TabControl: TKsCustomTabControl read FTabControl write SetTabControl;
    property TabIndex: Integer read GetTabIndex;
  published
    property Caption;
    property BorderWidth;
    property DragMode;
    property Enabled;
    property Font;
    property Height stored False;
    property Left stored False;
    property Constraints;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ShowHint;
    property Top stored False;
    property Visible stored False;
    property Width stored False;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnHide: TNotifyEvent read FOnHide write FOnHide;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnResize;
    property OnShow: TNotifyEvent read FOnShow write FOnShow;
    property OnStartDrag;
  end;

{ TKsCustomTabControl }

  TKsCustomTabControl = class(TKsCustomControl)
  private
    FRepeatTimer: TTimer;
    FTabs: TStrings;
    FPages: TList;
    FActivePage: TKsCustomTabSheet;

    FTabRows: TTabRows;
    FTabRowCount: integer;

    FMouseOnLeftBtn: boolean;
    FMouseOnRightBtn: boolean;
    FMouseOnTabIndex: integer;

    FMDownOnLeftBtn: boolean;
    FMDownOnRightBtn: boolean;

    FUpdating: boolean;
    FMultiLine: Boolean;
    FTabPosition: TTabPosition;

    FFirstVisibleTab: integer;
    FTabIndex: integer;
    FRaggedRight: Boolean;
    FScrollOpposite: Boolean;
    FHotTrack: Boolean;

    FTabSize: TPoint;
    FOnChange: TNotifyEvent;
    FOnChanging: TTabChangingEvent;
    FOwnerDraw: Boolean;
    FOnDrawTab: TDrawTabEvent;
    FUsePages: boolean;

    procedure UpdateTabRows;
    function IsTabOnOpositeSide(ATab: integer): boolean;
    procedure PartPress(FirstTime: boolean);
    procedure TimerExpired(Sender: TObject);
    function IsTabLastInRow(ATabIndex: integer): boolean;

    procedure InsertPage(Index: integer; Page: TKsCustomTabSheet);
    procedure AddPage(Page: TKsCustomTabSheet);
    procedure RemovePage(Page: TKsCustomTabSheet);
    procedure SetActivePage(Page: TKsCustomTabSheet);
    procedure SetActivePageIndex(const Value: Integer);

    procedure CMEnabledChanged(var Msg: TMessage); message CM_ENABLEDCHANGED;
    procedure CMDesignHitTest(var Msg: TCMDesignHitTest); message CM_DESIGNHITTEST;
    procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
    procedure WMGetDlgCode(var Msg: TWMGetDlgCode); message WM_GETDLGCODE;

    function GetLastVisibleTab(FullVisible: boolean): integer; //is used in not multiline mode

    function GetTabExtendedWidth(ATabIndex: integer): integer; //in Multiline and not RaggedRight mode

    function GetTabRow(ATab: integer): integer;
    function GetTabRowHeight(ARow: integer): integer;

    function GetOppositeTabLineHeight: integer;
    function GetTabLineHeight: integer;
    function GetLeftBtnPressed: boolean;
    function GetRightBtnPressed: boolean;

    procedure SetTabIndex(const Value: integer);
    procedure SetRaggedRight(const Value: Boolean);
    procedure SetTabs(const Value: TStrings);
    procedure SetScrollOpposite(const Value: Boolean);
    procedure SetHotTrack(const Value: Boolean);
    procedure SetTabHeight(const Value: integer);
    procedure SetTabWidth(const Value: integer);
    procedure SetOwnerDraw(const Value: Boolean);
    procedure SetMultiLine(const Value: Boolean);
    procedure SetTabPosition(const Value: TTabPosition);

    procedure Change; dynamic;
    function CanChange: Boolean; dynamic;
    procedure DoDrawTab(TabIndex: Integer; const Rect: TRect;
      Active: Boolean); dynamic;
    function GetPage(Index: Integer): TKsCustomTabSheet;
    function GetPageCount: Integer;
    procedure SetUsePages(const Value: boolean);
    function GetActivePageIndex: integer;
    function GetTabSizeX: integer;
    function GetTabSizeY: integer;
  protected
    procedure AdjustClientRect(var Rect: TRect); override;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;

    function GetTabRowCount: integer;
    function GetTabCount: integer;

    function GetTabRect(ATabIndex: integer): TRect;
    function GetBorderRect: TRect;
    function GetLeftScrlBtnRect: TRect;
    function GetRightScrlBtnRect: TRect;
    function GetScrlBtnsVisible: boolean;

    function GetLeftMargin: integer; virtual;
    function GetTopMargin: integer; virtual;
    function GetRightMargin: integer; virtual;
    function GetBottomMargin: integer; virtual;
    function GetTabWidth(ATabIndex: integer): integer; virtual;
    function GetTabHeight: integer; virtual;
    function GetScrollBtnSize: TPoint; virtual;

    procedure DrawTab(ATabIndex: integer); virtual;
    procedure DrawBorder; virtual;
    procedure DrawLeftScrlBtn; virtual;
    procedure DrawRightScrlBtn; virtual;

    function PtInTab(x, y: integer): integer;
    function PtInLeftBtn(x, y: integer): boolean; virtual;
    function PtInRightBtn(x, y: integer): boolean; virtual;

    procedure DrawTabs(Active: boolean);

    procedure PaintBuffer; override;

    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; x, y: integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; x, y: integer); override;
    procedure MouseMove(Shift: TShiftState; x, y: integer); override;
    procedure KeyDown(var Key: word; Shift: TShiftState); override;
    procedure KeyUp(var Key: word; Shift: TShiftState); override;

    procedure ScrollTabs(Delta: Integer);
    function IndexOfTabAt(X, Y: Integer): Integer;
    function TabRect(Index: Integer): TRect;
    function RowCount: Integer;

    procedure TabsChanged; virtual;

    property MouseOnLeftBtn: boolean read FMouseOnLeftBtn;
    property LeftBtnPressed: boolean read GetLeftBtnPressed;

    property MouseOnRightBtn: boolean read FMouseOnRightBtn;
    property RightBtnPressed: boolean read GetRightBtnPressed;

    property MouseOnTabIndex: integer read FMouseOnTabIndex;

    procedure DoEnter; override;
    procedure DoExit; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Loaded; override;

    property ActivePageIndex: integer read GetActivePageIndex; 
    property PageCount: Integer read GetPageCount;
    property Pages[Index: Integer]: TKsCustomTabSheet read GetPage;
  published
    property Align;
    property Anchors;
    property BiDiMode;
    property Constraints;
    property DockSite;
    property DragCursor;
    property DragKind;
    property DragMode;
    property Enabled;
    property Font;
    property ParentBiDiMode;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;
    property HotTrack: Boolean read FHotTrack write SetHotTrack default False;
    property MultiLine: Boolean read FMultiLine write SetMultiLine default False;
    property OwnerDraw: Boolean read FOwnerDraw write SetOwnerDraw default False;
    property Tabs: TStrings read FTabs write SetTabs;
    property TabPosition: TTabPosition read FTabPosition write SetTabPosition default tpTop;
    property TabIndex: integer read FTabIndex write SetTabIndex default -1;
    property RaggedRight: Boolean read FRaggedRight write SetRaggedRight default False;
    property ScrollOpposite: Boolean read FScrollOpposite write SetScrollOpposite default False;
    property TabHeight: integer read GetTabSizeY write SetTabHeight default 0;
    property TabWidth: integer read GetTabSizeX write SetTabWidth default 0;
    property UsePages: boolean read FUsePages write SetUsePages;

    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property OnChanging: TTabChangingEvent read FOnChanging write FOnChanging;
    property OnDrawTab: TDrawTabEvent read FOnDrawTab write FOnDrawTab;
    property OnDockDrop;
    property OnDockOver;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDock;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnGetSiteInfo;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnResize;
    property OnStartDock;
    property OnStartDrag;
    property OnUnDock;
  end;

implementation {===============================================================}

uses
  Consts, ComStrs;

type

  TKsTabStringList = class(TStringList)
  private
    FTabControl: TKsCustomTabControl;
  protected
    procedure Put(Index: Integer; const S: string); override;
    procedure PutObject(Index: Integer; AObject: TObject); override;
    procedure SetUpdateState(Updating: Boolean); override;
  public
    procedure Clear; override;
    procedure Delete(Index: Integer); override;
    procedure Insert(Index: Integer; const S: string); override;
    function Add(const S: string): Integer; override;
    procedure Assign(Source: TPersistent); override;
  end;

{ TKsTabStringList }

function TKsTabStringList.Add(const S: string): Integer;
begin
  if IndexOf(S) >= 0 then
    raise Exception.Create('A tab named '+S+' already exists.');

  Result := inherited Add(S);
  FTabControl.TabsChanged;
end;

procedure TKsTabStringList.Assign(Source: TPersistent);
var
  i: integer;
begin
  if Source is TStrings then
  begin
    BeginUpdate;
    try
      inherited Clear;
      for i := 0 to TStrings(Source).Count - 1 do
        inherited Add(TStrings(Source).Strings[i]);
    finally
      FTabControl.TabsChanged;
      EndUpdate;
    end;
    Exit;
  end;
  inherited;
end;

procedure TKsTabStringList.Clear;
begin
  inherited Clear;
  FTabControl.TabsChanged;
end;

procedure TKsTabStringList.Delete(Index: Integer);
begin
  inherited Delete(Index);
  FTabControl.TabsChanged;
end;

procedure TKsTabStringList.Insert(Index: Integer; const S: string);
begin
  if IndexOf(S) >= 0 then
    raise Exception.Create('A tab named '+S+' already exists.');

  inherited Insert(Index, S);
  FTabControl.TabsChanged;
end;

procedure TKsTabStringList.Put(Index: Integer; const S: string);
begin
  inherited Put(Index, S);
  FTabControl.TabsChanged;
end;

procedure TKsTabStringList.PutObject(Index: Integer; AObject: TObject);
begin
  inherited PutObject(Index, AObject);
  FTabControl.TabsChanged;
end;

procedure TKsTabStringList.SetUpdateState(Updating: Boolean);
begin
  inherited SetUpdateState(Updating);
  FTabControl.FUpdating := Updating;
  if not Updating then
  begin
    FTabControl.Invalidate;
    FTabControl.TabsChanged;
  end;
end;

{ Routines }

procedure DrawLeftTopCorner(x, y: integer; Buffer: TKsBitmap);
begin
  with Buffer do
  begin
    MoveTo(x, y + 2);
    LineTo(x + 3, y - 1, KColor(clBtnHighlight));
  end;
end;

procedure DrawRightTopCorner(x, y: integer; Buffer: TKsBitmap);
begin
  with Buffer do
  begin
    MoveTo(x - 3, y);
    LineTo(x - 2, y + 1, KColor(clBtnHighlight));

    LineTo(x, y + 3, KColor(cl3DDkShadow));

    MoveTo(x - 2, y + 2);
    LineTo(x - 1, y + 2, KColor(clBtnShadow));
  end;
end;

procedure DrawRightBottomCorner(x, y: integer; Buffer: TKsBitmap);
begin
  with Buffer do
  begin
    MoveTo(x - 1, y - 3);
    LineTo(x - 4, y, KColor(cl3DDkShadow));

    MoveTo(x - 2, y - 3);
    LineTo(x - 4, y - 1, KColor(clBtnShadow));
  end;
end;

procedure DrawLeftBottomCorner(x, y: integer; Buffer: TKsBitmap);
begin
  with Buffer do
  begin
    MoveTo(x, y - 3);
    LineTo(x + 2, y - 1, KColor(clBtnHighlight));

    LineTo(x + 2, y - 2, KColor(cl3DDkShadow));

    LineTo(x + 2, y - 3, KColor(clBtnShadow));
  end;
end;

procedure DrawTopLine(Bx, Ex, y: integer; Buffer: TKsBitmap);
begin
  with Buffer do
  begin
    MoveTo(Bx, y);
    LineTo(Ex, y, KColor(clBtnHighlight));
  end;
end;

procedure DrawLeftLine(x, By, Ey: integer; Buffer: TKsBitmap);
begin
  with Buffer do
  begin
    MoveTo(x, By);
    LineTo(x, Ey, KColor(clBtnHighlight));
  end;
end;

procedure DrawRightLine(x, By, Ey: integer; Buffer: TKsBitmap);
begin
  with Buffer do
  begin
    MoveTo(x - 1, By);
    LineTo(x - 1, Ey, KColor(cl3DDkShadow));

    MoveTo(x - 2, By);
    LineTo(x - 2, Ey, KColor(clBtnShadow));
  end;
end;

procedure DrawBottomLine(Bx, Ex, y: integer; Buffer: TKsBitmap);
begin
  with Buffer do
  begin
    MoveTo(Bx, y - 1);
    LineTo(Ex, y - 1, KColor(cl3DDkShadow));

    MoveTo(Bx, y - 2);
    LineTo(Ex, y - 2, KColor(clBtnShadow));
  end;
end;

{ TKsCustomTabSheet }

constructor TKsCustomTabSheet.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Align := alClient;
  ControlStyle := ControlStyle + [csAcceptsControls, csNoDesignVisible];
  Visible := False;
end;

destructor TKsCustomTabSheet.Destroy;
begin
  if FTabControl <> nil then
    FTabControl.RemovePage(Self);
    
  inherited Destroy;
end;

procedure TKsCustomTabSheet.DoHide;
begin
  if Assigned(FOnHide) then FOnHide(Self);
end;

procedure TKsCustomTabSheet.DoShow;
begin
  if Assigned(FOnShow) then FOnShow(Self);
end;

function TKsCustomTabSheet.GetTabIndex: Integer;
begin
  if FTabControl <> nil then
    Result := FTabControl.FPages.IndexOf(Self)
  else
    Result := -1;
end;

procedure TKsCustomTabSheet.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params.WindowClass do
    style := style and not (CS_HREDRAW or CS_VREDRAW);
end;

procedure TKsCustomTabSheet.WMEraseBkgnd(var Msg: TWmEraseBkgnd);
begin
  if (csDesigning in ComponentState) then inherited ;

  if (Msg.DC <> 0) then
  begin
    if (Msg.Unused or BufferFlag <> 0) then
      PaintOwnerBackground(Self, Msg.DC);

    if (Msg.Unused or NoChildPaint = 0) then
      PaintControls(Msg.DC, nil);
  end;

  Msg.Result := 1;
end;

procedure TKsCustomTabSheet.WMPaint(var Message: TWMPaint);
var
  DC: HDC;
  PS: TPaintStruct;
begin
  DC := BeginPaint(Handle, PS);
  try
    if (FTabControl <> nil) and (FTabControl.FActivePage = Self) then
      PaintControls(DC, nil);
  finally
    EndPaint(Handle, PS);
  end;
end;

procedure TKsCustomTabSheet.ReadState(Reader: TReader);
begin
  inherited ReadState(Reader);
  if Reader.Parent is TKsCustomTabControl then
    TabControl := TKsCustomTabControl(Reader.Parent);
end;

procedure TKsCustomTabSheet.SetTabControl(ATabControl: TKsCustomTabControl);
begin
  if FTabControl <> ATabControl then
  begin
    if FTabControl <> nil then FTabControl.RemovePage(Self);
    FTabControl := ATabControl;
    if FTabControl <> nil then FTabControl.AddPage(Self);
  end;
end;

procedure TKsCustomTabSheet.CMShowingChanged(var Message: TMessage);
begin
  inherited;
  if Showing then
  begin
    try
      DoShow
    except
      Application.HandleException(Self);
    end;
  end else if not Showing then
  begin
    try
      DoHide;
    except
      Application.HandleException(Self);
    end;
  end;
end;

{ TKsCustomTabControl =========================================================}

constructor TKsCustomTabControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := [csAcceptsControls, csDoubleClicks];

  FTabIndex := -1;

  FTabs := TKsTabStringList.Create;
  TKsTabStringList(FTabs).FTabControl := Self;

  FPages := TList.Create;

  with FTabSize do
  begin
    y := 0;
    x := 0;
  end;

  FMouseOnLeftBtn := false;
  FMouseOnRightBtn := false;
  FMouseOnTabIndex := -1;

  FFirstVisibleTab := 0;

  FTabPosition := tpTop;
  FUpdating := false;
  FScrollOpposite := false;
  FHotTrack := False;
  FMultiLine := False;
  FOwnerDraw := False;
  FTabPosition := tpTop;
  FTabIndex := -1;
  FRaggedRight := False;

  Width := 289;
  Height := 193;
  TabStop := True;

  Realign;
end;

destructor TKsCustomTabControl.Destroy;
var
  i: integer;
begin
  for i := 0 to FPages.Count - 1 do
    TKsCustomTabSheet(FPages[I]).FTabControl := nil;
  FPages.Free;

  FTabs.Free;
  FRepeatTimer.Free;
  inherited Destroy;
end;

procedure TKsCustomTabControl.Loaded;
begin
  inherited;

  if FUsePages then
    SetActivePageIndex(FTabIndex);
end;

procedure TKsCustomTabControl.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  I: Integer;
begin
  if FUsePages then
    for I := 0 to FPages.Count - 1 do Proc(TComponent(FPages[I]))
  else
    inherited ;
end;

procedure TKsCustomTabControl.AddPage(Page: TKsCustomTabSheet);
begin
  if FPages.Count > 0 then
    InsertPage(FPages.Count, Page)
  else
    InsertPage(0, Page);
end;

procedure TKsCustomTabControl.InsertPage(Index: integer; Page: TKsCustomTabSheet);
var
  i, C: integer;
  S: string;
  Unique: boolean;
begin
  if Page.Caption = '' then
  begin
    FTabs.Add('New Page ' + IntToStr(FTabs.Count - 1));
    Page.Caption := FTabs[FTabs.Count-1];
  end;

  FPages.Insert(Index, Page);

  Page.FTabControl := Self;
  Page.Parent := Self;
  Page.Visible := false;

  if (Page.Name = '') and (csDesigning in ComponentState) and (Owner is TForm) then
  begin
    { Make Unique name }
    C := 1;
    Unique := false;
    while not Unique do
    begin
      Unique := true;
      
      S := Page.ClassName + IntToStr(C);
      for i := 0 to (Owner as TForm).ComponentCount - 1 do
        if (Owner as TForm).Components[i].Name = S then
          Unique := false;

      Inc(C);
    end;
    Page.Name := S;
  end;

  Invalidate;
end;

procedure TKsCustomTabControl.RemovePage(Page: TKsCustomTabSheet);
begin
  if FPages.IndexOf(Page) < 0 then Exit;

  if FTabs.IndexOf(Page.Caption) >= 0 then
    FTabs.Delete(Page.GetTabIndex);
  FPages.Remove(Page);

  Page.FTabControl := nil;
  Page.Caption := '';

  if FActivePage = Page then
  begin
    if FTabs.Count > 0 then
    begin
      FTabIndex := 0;
      SetActivePageIndex(0);
    end
    else
    begin
      FTabIndex := -1;
      FActivePage := nil;
    end;
  end;
  Page.Free;

  Invalidate;
end;

procedure TKsCustomTabControl.SetActivePage(Page: TKsCustomTabSheet);
begin
  if FActivePage <> Page then
  begin
    if FActivePage <> nil then
      FActivePage.Visible := false;

    FActivePage := Page;
    FActivePage.Visible := true;
    FActivePage.BringToFront;
    FTabIndex := FActivePage.TabIndex;
    Invalidate;
  end;
end;

procedure TKsCustomTabControl.SetActivePageIndex(const Value: Integer);
begin
  if (Value >= 0) and (Value < FPages.Count) then
    SetActivePage(FPages[Value]);
end;

procedure TKsCustomTabControl.AdjustClientRect(var Rect: TRect);
var
  LBorderRect: TRect;
begin
  inherited;
  LBorderRect := GetBorderRect;
  with Rect do
  begin
    Left := LBorderRect.Left + GetLeftMargin + 1;
    Top := LBorderRect.Top + GetTopMargin + 1;
    Right := LBorderRect.Right - GetRightMargin - 1;
    Bottom := LBorderRect.Bottom - GetBottomMargin - 1;
  end;
end;

procedure TKsCustomTabControl.DrawTab(ATabIndex: integer);
var
  TextRect, LRect: TRect;
  LTabPosition: TTabPosition;
begin
  Buffer.Font.Assign(Font);
  with Buffer do
  begin
    LRect := GetTabRect(ATabIndex);
    if ATabIndex = TabIndex then
    begin
      case FTabPosition of
        tpLeft: begin
          InflateRect(LRect, 2, 2);
          Dec(LRect.Right, 2);
        end;
        tpTop: begin
          InflateRect(LRect, 2, 2);
          Dec(LRect.Bottom, 2);
        end;
        tpRight: begin
          InflateRect(LRect, 2, 2);
          Inc(LRect.Left, 2);
        end;
        tpBottom: begin
          InflateRect(LRect, 2, 2);
          Inc(LRect.Top, 2);
        end;
      end;
    end;

    if IsTabOnOpositeSide(ATabIndex) then
      case TabPosition of
        tpTop: LTabPosition := tpBottom;
        tpLeft: LTabPosition := tpRight;
        tpRight: LTabPosition := tpLeft;
      else
        { tpBottom }
        LTabPosition := tpTop;
      end
    else
      LTabPosition := TabPosition;

    with LRect do
    begin
      case LTabPosition of
        tpLeft:
          begin
            FillRoundRect(Rect(Left, Top + 1, Right, Bottom - 1), 4, KColor(clBtnFace));
            FillRect(Rect(Left + 2, Top + 1, Right, Bottom), KColor(clBtnFace));

            if Focused and (ATabIndex = TabIndex) then
            begin
              DrawFocusRect(Rect(Left + GetLeftMargin + 1, Top + GetTopMargin + 1,
                Right - GetLeftMargin - 1, Bottom - GetTopMargin - 1), KColor(clBtnFace));
            end;

            if OwnerDraw then
              DoDrawTab(ATabIndex, LRect, ATabIndex = TabIndex)
            else
            begin
              if HotTrack and (ATabIndex = MouseOnTabIndex) then
                Font.Color := clHighlight;

              TextRect := LRect;
              if TabIndex = ATabIndex then InflateRect(TextRect, -1, -1);
              TextRect.Right := TextRect.Left + GetTabHeight;
              DrawVerticalText(Tabs[ATabIndex], TextRect, DT_CENTER or DT_VCENTER or DT_SINGLELINE, false);
            end;

            DrawTopLine(Left + 3, Right - 1, Top, Buffer);
            DrawLeftLine(Left, Top + 3, Bottom - 3, Buffer);
            DrawBottomLine(Left + 3, Right - 1, Bottom, Buffer);
            DrawLeftTopCorner(Left, Top, Buffer);
            DrawLeftBottomCorner(Left, Bottom, Buffer);
          end;
        tpTop:
          begin
            FillRoundRect(Rect(Left + 1, Top + 1, Right - 1, Bottom), 4, KColor(clBtnFace));
            FillRect(Rect(Left + 1, Top + 2, Right - 1, Bottom), KColor(clBtnFace));

            if Focused and (ATabIndex = TabIndex) then
            begin
              DrawFocusRect(Rect(Left + GetLeftMargin + 1, Top + GetTopMargin + 1,
                Right - GetLeftMargin - 1, Bottom - GetTopMargin - 1), KColor(clBtnFace));
            end;

            if OwnerDraw then
              DoDrawTab(ATabIndex, LRect, ATabIndex = TabIndex)
            else
            begin
              if HotTrack and (ATabIndex = MouseOnTabIndex) then
                Font.Color := clHighlight;

              if TabIndex <> ATabIndex then Inc(LRect.Top);

              TextRect := LRect;
              if TabIndex = ATabIndex then InflateRect(TextRect, -1, -1);
              TextRect.Bottom := TextRect.Top + GetTabHeight;
              DrawText(Tabs[ATabIndex], TextRect, DrawTextBiDiModeFlags(DT_CENTER or DT_VCENTER or DT_SINGLELINE));
            end;
            DrawTopLine(Left + 3, Right - 3, Top, Buffer);
            DrawLeftLine(Left, Top + 3, Bottom - 1, Buffer);
            DrawRightLine(Right, Top + 3, Bottom - 1, Buffer);
            DrawLeftTopCorner(Left, Top, Buffer);
            DrawRightTopCorner(Right, Top, Buffer);
          end;
        tpRight:
          begin
            FillRoundRect(Rect(Left, Top + 1, Right, Bottom - 1), 4, KColor(clBtnFace));
            FillRect(Rect(Left, Top + 1, Right - 2, Bottom - 1), KColor(clBtnFace));

            if Focused and (ATabIndex = TabIndex) then
            begin
              DrawFocusRect(Rect(Left + GetLeftMargin + 1, Top + GetTopMargin + 1,
                Right - GetLeftMargin - 1, Bottom - GetTopMargin - 1), KColor(clBtnFace));
            end;

            if OwnerDraw then
              DoDrawTab(ATabIndex, LRect, ATabIndex = TabIndex)
            else
            begin
              if HotTrack and (ATabIndex = MouseOnTabIndex) then
                Font.Color := clHighlight;

              TextRect := LRect;
              if TabIndex = ATabIndex then InflateRect(TextRect, -1, -1);
              TextRect.Left := TextRect.Right - GetTabHeight;
              DrawVerticalText(Tabs[ATabIndex], TextRect, DT_CENTER or DT_VCENTER or DT_SINGLELINE, true);
            end;

            DrawTopLine(Left, Right - 3, Top, Buffer);
            DrawRightLine(Right, Top + 3, Bottom - 3, Buffer);
            DrawBottomLine(Left + 1, Right - 3, Bottom, Buffer);
            DrawRightTopCorner(Right, Top, Buffer);
            DrawRightBottomCorner(Right, Bottom, Buffer);
          end;
        tpBottom:
          begin
            FillRoundRect(Rect(Left + 1, Top, Right - 1, Bottom), 4, KColor(clBtnFace));
            FillRect(Rect(Left + 1, Top + 2, Right - 1, Bottom), KColor(clBtnFace));

            if Focused and (ATabIndex = TabIndex) then
            begin
              DrawFocusRect(Rect(Left + GetLeftMargin + 1, Top + GetTopMargin + 1,
                Right - GetLeftMargin - 1, Bottom - GetTopMargin - 1), KColor(clBtnFace));
            end;

            if OwnerDraw then
              DoDrawTab(ATabIndex, LRect, ATabIndex = TabIndex)
            else
            begin
              if HotTrack and (ATabIndex = MouseOnTabIndex) then
                Font.Color := clHighlight;

              TextRect := LRect;
              if TabIndex = ATabIndex then InflateRect(TextRect, -1, -1);
              TextRect.Top := TextRect.Bottom - GetTabHeight;
              DrawText(Tabs[ATabIndex], TextRect, DrawTextBiDiModeFlags(DT_CENTER or DT_VCENTER or DT_SINGLELINE));
            end;

            DrawLeftLine(Left, Top, Bottom - 3, Buffer);
            DrawRightLine(Right, Top + 1, Bottom - 3, Buffer);
            DrawBottomLine(Left + 3, Right - 3, Bottom, Buffer);
            DrawLeftBottomCorner(Left, Bottom, Buffer);
            DrawRightBottomCorner(Right, Bottom, Buffer);
          end;
      end;
    end;
  end;
end;

procedure TKsCustomTabControl.DrawTabs(Active: boolean);
var
  i: integer;
  LLastVisibleTab: integer;
  CurRow, LRowCount: integer;
  RowBegTab, RowEndTab: integer;
  CurTab: integer;
begin
  UpdateTabRows;

  if GetTabCount <= 0 then Exit;

  if Active then
  begin
    DrawTab(TabIndex);
    Exit;
  end;

  if MultiLine then
  begin
    LRowCount := GetTabRowCount;
    if LRowCount <= 0 then
      Exit;

    if not ScrollOpposite then
      for CurRow := GetTabRow(TabIndex) - 1 downto 0 do
      begin
        if CurRow > 0 then
          RowBegTab := FTabRows[CurRow - 1].LastTab + 1
        else
          RowBegTab := 0;
        RowEndTab := FTabRows[CurRow].LastTab;
        for CurTab := RowBegTab to RowEndTab do
          DrawTab(CurTab);
      end;

    for CurRow := LRowCount - 1 downto GetTabRow(TabIndex) do
    begin
      if CurRow > 0 then
        RowBegTab := FTabRows[CurRow - 1].LastTab + 1
      else
        RowBegTab := 0;
      RowEndTab := FTabRows[CurRow].LastTab;
      for CurTab := RowBegTab to RowEndTab do
        DrawTab(CurTab);
    end;

    if ScrollOpposite then
      for CurRow := 0 to GetTabRow(TabIndex) - 1 do
      begin
        if CurRow > 0 then
          RowBegTab := FTabRows[CurRow - 1].LastTab + 1
        else
          RowBegTab := 0;
        RowEndTab := FTabRows[CurRow].LastTab;
        for CurTab := RowBegTab to RowEndTab do
          DrawTab(CurTab);
      end;

  end
  else
  begin
    i := FFirstVisibleTab;
    LLastVisibleTab := GetLastVisibleTab(false);
    while (i <= GetTabCount - 1) and (i <= LLastVisibleTab) do
    begin
      if i <> TabIndex then
        DrawTab(i);
      i := i + 1;
    end;
  end;
end;

function TKsCustomTabControl.GetBorderRect: TRect;
begin
  with Result do
  begin
    Left := 0;
    Top := 0;
    Right := Width;
    Bottom := Height;
    case TabPosition of
      tpLeft:
        begin
          Left := Left + GetTabLineHeight;
          Right := Right - GetOppositeTabLineHeight;
        end;
      tpTop:
        begin
          Top := Top + GetTabLineHeight;
          Bottom := Bottom - GetOppositeTabLineHeight;
        end;
      tpRight:
        begin
          Right := Right - GetTabLineHeight;
          Left := Left + GetOppositeTabLineHeight;
        end;
      tpBottom:
        begin
          Bottom := Bottom - GetTabLineHeight;
          Top := Top + GetOppositeTabLineHeight;
        end;
    end;
  end;
end;

function TKsCustomTabControl.GetBottomMargin: integer;
begin
  Result := 2;
end;

function TKsCustomTabControl.GetLastVisibleTab(FullVisible: boolean): integer;
var
  LTabAreaWidth,
    SumWidth: integer;
  TmpRect: TRect;
begin
  Result := -1;

  if GetTabCount = 0 then Exit;

  case TabPosition of
    tpTop, tpBottom: LTabAreaWidth := Width - GetLeftMargin - GetRightMargin;
  else
    LTabAreaWidth := Height - GetTopMargin - GetBottomMargin;
  end;

  SumWidth := GetTabWidth(FFirstVisibleTab);
  for Result := FFirstVisibleTab to GetTabCount - 2 do
    if (SumWidth + GetTabWidth(Result + 1) <= LTabAreaWidth)
      and (Result <= GetTabCount - 2) then
      SumWidth := SumWidth + GetTabWidth(Result + 1)
    else
      Break;

  if Result + 1 <= GetTabCount - 1 then
    Result := Result + 1;

  if FullVisible then
  begin
    while (Result > 0) and ((GetTabRect(Result).Right > Width) or
      (IntersectRect(TmpRect, GetTabRect(Result), GetLeftScrlBtnRect) or
      IntersectRect(TmpRect, GetTabRect(Result), GetRightScrlBtnRect))) do
      Result := Result - 1;
  end;
end;

function TKsCustomTabControl.GetLeftMargin: integer;
begin
  Result := 2;
end;

function TKsCustomTabControl.GetRightMargin: integer;
begin
  Result := 2;
end;

function TKsCustomTabControl.GetTabCount: integer;
begin
  Result := Tabs.Count;
end;

function TKsCustomTabControl.GetTabHeight: integer;
begin
  if TabHeight <= 0 then
    Result := Round(Font.Size * 1.7) + 4
  else
    Result := TabHeight;
end;

function TKsCustomTabControl.GetTabRect(ATabIndex: integer): TRect;
var
  i: integer;
  CurShift: integer;
  TabRow,
    FirstTabInRow: integer;
  LRowHeight,
    LTabLineHeight: integer;
  LTabPosition: TTabPosition;
begin
  UpdateTabRows;

  CurShift := 0;

  if MultiLine then
  begin
    TabRow := GetTabRow(ATabIndex);

    if TabRow = 0 then
      FirstTabInRow := 0
    else
      FirstTabInRow := FTabRows[TabRow - 1].LastTab + 1;

    for i := FirstTabInRow to ATabIndex - 1 do
      CurShift := CurShift + GetTabExtendedWidth(i);

  end
  else
  begin
    if ATabIndex >= FFirstVisibleTab then
      for i := FFirstVisibleTab to ATabIndex - 1 do
        CurShift := CurShift + GetTabExtendedWidth(i)
    else
      for i := FFirstVisibleTab - 1 downto ATabIndex do
        CurShift := CurShift - GetTabExtendedWidth(i);
  end;

  if IsTabOnOpositeSide(ATabIndex) then
  begin
    case TabPosition of
      tpTop: LTabPosition := tpBottom;
      tpLeft: LTabPosition := tpRight;
      tpRight: LTabPosition := tpLeft;
    else // tpBottom
      LTabPosition := tpTop;
    end;
    LRowHeight := GetTabRowHeight(GetTabRow(ATabIndex));
    LTabLineHeight := GetOppositeTabLineHeight;
  end
  else
  begin
    LTabPosition := TabPosition;
    LRowHeight := GetTabRowHeight(GetTabRow(ATabIndex));
    LTabLineHeight := GetTabLineHeight;
  end;

  with Result do
  begin
{    if ATabIndex <> TabIndex then }
      case LTabPosition of
        tpLeft:
          begin
            Left := LTabLineHeight - LRowHeight;
            Right := LTabLineHeight + GetLeftMargin;
            Top := CurShift + GetTopMargin;
            Bottom := Top + GetTabExtendedWidth(ATabIndex);
          end;
        tpTop:
          begin
            Left := CurShift + GetLeftMargin;
            Right := Left + GetTabExtendedWidth(ATabIndex);
            Top := LTabLineHeight - LRowHeight;
            Bottom := LTabLineHeight + GetTopMargin;
          end;
        tpRight:
          begin
            Right := Width - (LTabLineHeight - LRowHeight);
            Left := Width - LTabLineHeight - GetRightMargin;
            Top := CurShift + GetTopMargin;
            Bottom := Top + GetTabExtendedWidth(ATabIndex);
          end;
        tpBottom:
          begin
            Left := CurShift + GetLeftMargin;
            Right := Left + GetTabExtendedWidth(ATabIndex);
            Bottom := Height - (LTabLineHeight - LRowHeight);
            Top := Height - LTabLineHeight - GetBottomMargin;
          end;
      end
(*    else
      case LTabPosition of
        tpLeft:
          begin
            Left := LTabLineHeight - LRowHeight - GetLeftMargin;
            Right := LTabLineHeight + GetLeftMargin;
            Top := CurShift;
            Bottom := Top + GetTabExtendedWidth(ATabIndex) + GetTopMargin * 2;
          end;
        tpTop:
          begin
            Left := CurShift;
            Right := Left + GetTabExtendedWidth(ATabIndex) + GetLeftMargin * 2;
            Top := LTabLineHeight - LRowHeight - GetTopMargin {*2};
            Bottom := LTabLineHeight + GetTopMargin;
          end;
        tpRight:
          begin
            Right := Width - (LTabLineHeight - LRowHeight) + GetRightMargin;
            Left := Width - LTabLineHeight - GetRightMargin;
            Top := CurShift;
            Bottom := Top + GetTabExtendedWidth(ATabIndex) + GetTopMargin * 2;
          end;
        tpBottom:
          begin
            Left := CurShift;
            Right := Left + GetTabExtendedWidth(ATabIndex) + GetLeftMargin * 2;

            Bottom := Height - (LTabLineHeight - LRowHeight) + GetBottomMargin;
            Top := Height - LTabLineHeight - GetBottomMargin;
          end;
      end *)

  end;
end;

function TKsCustomTabControl.GetTabRowCount: integer;
begin
  if not Multiline then
    Result := 1
  else
    Result := FTabRowCount;
end;

function TKsCustomTabControl.GetTabWidth(ATabIndex: integer): integer;
begin
  if ATabIndex < 0 then Exit;
  
  if FTabSize.x = 0 then
  begin

    Buffer.Font.Assign(Font);
    Result := Buffer.TextWidth(Tabs[ATabIndex]) + 12;

    case TabPosition of
      tpLeft:
        if Result < (GetTabHeight + GetLeftMargin) * 2 then
          Result := (GetTabHeight + GetLeftMargin) * 2;
      tpTop:
        if Result < (GetTabHeight + GetTopMargin) * 2 then
          Result := (GetTabHeight + GetTopMargin) * 2;
      tpRight:
        if Result < (GetTabHeight + GetRightMargin) * 2 then
          Result := (GetTabHeight + GetRightMargin) * 2;
      tpBottom:
        if Result < (GetTabHeight + GetBottomMargin) * 2 then
          Result := (GetTabHeight + GetBottomMargin) * 2;
    end;
  end
  else
    Result := FTabSize.x;
end;

function TKsCustomTabControl.GetTopMargin: integer;
begin
  Result := 2;
end;

function TKsCustomTabControl.GetTabLineHeight: integer;
begin
  if GetTabCount > 0 then
  begin
    if not ScrollOpposite then
      Result := GetTabHeight * GetTabRowCount
    else
      Result := GetTabHeight * (GetTabRowCount - GetTabRow(TabIndex));
    case TabPosition of
      tpLeft: Result := Result + GetLeftMargin;
      tpTop: Result := Result + GetTopMargin;
      tpRight: Result := Result + GetRightMargin;
      tpBottom: Result := Result + GetBottomMargin;
    end;
  end
  else
    Result := 0;
end;

procedure TKsCustomTabControl.PaintBuffer;
var
  R: TRect;
begin
  if Tabs = nil then Exit;
  if Buffer = nil then Exit;

  DrawTabs(false);
  DrawBorder;

  DrawTabs(true);

  if GetScrlBtnsVisible then
  begin
    DrawLeftScrlBtn;
    DrawRightScrlBtn;
  end;
end;

procedure TKsCustomTabControl.DrawBorder;
var
  LRect: TRect;
begin
  LRect := GetBorderRect;
  with Buffer do
  begin
    FillRect(LRect, KColor(clBtnFace));

    DrawEdge(LRect, KColor(clBtnHighlight), KColor(cl3DDkShadow));
    InflateRect(LRect, -1, -1);
    DrawEdge(LRect, KColor(clBtnFace), KColor(clBtnShadow));
  end;
end;

procedure TKsCustomTabControl.TabsChanged;
var
  i, j: integer;
  Page: TKsCustomTabSheet;
  Exist: boolean;
begin
  if FTabs.Count <= 0 then
  begin
    FTabIndex := -1;
  end
  else
    if FTabIndex >= FTabs.Count-1 then
      FTabIndex := FTabs.Count-1
    else
      if FTabIndex = -1 then
        FTabIndex := 0;


  if (FUsePages) and not (csLoading in ComponentState) then
  begin
    { Update Pages }
    if FTabs.Count < FPages.Count then
    begin
      { Remove all }
      if FTabs.Count = 0 then
      begin
        while FPages.Count > 0 do
          RemovePage(Pages[0]);

        FTabIndex := -1;
        FActivePage := nil;
      end;
    end;

    if FTabs.Count < FPages.Count then
    begin
      { Remove oldest pages }
      i := 0;
      while i < FPages.Count do
      begin
        if FTabs.IndexOf(Pages[i].Caption) < 0 then
          RemovePage(Pages[i]);

        Inc(i);
      end;
    end;

    if FTabs.Count > FPages.Count then
    begin
      { Add newst pages }
      for i := 0 to FTabs.Count - 1 do
      begin
        Exist := false;
        for j := 0 to PageCount - 1 do
          if Pages[j].Caption = FTabs[i] then
          begin
            Exist := true;
            Break;
          end;

        if not Exist then
        begin
          Page := TKsCustomTabSheet.Create(Owner);
          Page.Caption := FTabs[i];
          InsertPage(i, Page);

          SetActivePageIndex(i);
        end;
      end;
    end;
  end;

  if (not FUsePages) and not (csLoading in ComponentState) then
  begin
    { Remove all pages }
    if FPages.Count > 0 then
    begin
      { Remove all }
      while FPages.Count > 0 do
      begin
        Page := Pages[0];

        FPages.Remove(Page);
        Page.FTabControl := nil;
        Page.Free;
      end;
    end;
  end;

  if not FUpdating then
  begin
    Invalidate;
    Realign;
  end;
end;

function TKsCustomTabControl.PtInTab(x, y: integer): integer;
var
  i, LLastVisibleTab: integer;
  LRect: TRect;
  CurRow, LRowCount: integer;
  RowBegTab, RowEndTab: integer;
  CurTab: integer;
begin
  Result := -1;

  if GetTabCount <= 0 then Exit;

  if (((TabPosition = tpTop) and PtInRect(Rect(Width - GetScrollBtnSize.x * 2,
    0,
    Width,
    GetTabLineHeight), Point(x, y)))
    or ((TabPosition = tpBottom) and PtInRect(Rect(Width - GetScrollBtnSize.x * 2,
    Height - GetTabLineHeight,
    Width,
    Height), Point(x, y))))
    and GetScrlBtnsVisible then
      Exit;

  if PtInRect(GetTabRect(TabIndex), Point(x, y)) then
  begin
    Result := TabIndex;
    Exit;
  end;

  if MultiLine then
  begin
    LRowCount := GetTabRowCount;
    if LRowCount <= 0 then
      Exit;
   
    if ScrollOpposite then
      for CurRow := GetTabRow(TabIndex) - 1 downto 0 do
      begin
        if CurRow > 0 then
          RowBegTab := FTabRows[CurRow - 1].LastTab + 1
        else
          RowBegTab := 0;
        RowEndTab := FTabRows[CurRow].LastTab;
        for CurTab := RowBegTab to RowEndTab do
          if PtInRect(GetTabRect(CurTab), Point(x, y)) then
          begin
            Result := CurTab;
            Exit;
          end;
      end;

    for CurRow := GetTabRow(TabIndex) to LRowCount - 1 do
    begin
      if CurRow > 0 then
        RowBegTab := FTabRows[CurRow - 1].LastTab + 1
      else
        RowBegTab := 0;
      RowEndTab := FTabRows[CurRow].LastTab;
      for CurTab := RowBegTab to RowEndTab do
        if PtInRect(GetTabRect(CurTab), Point(x, y)) then
        begin
          Result := CurTab;
          Exit;
        end;
    end;

    if not ScrollOpposite then
      for CurRow := 0 to GetTabRow(TabIndex) - 1 do
      begin
        if CurRow > 0 then
          RowBegTab := FTabRows[CurRow - 1].LastTab + 1
        else
          RowBegTab := 0;
        RowEndTab := FTabRows[CurRow].LastTab;
        for CurTab := RowBegTab to RowEndTab do
          if PtInRect(GetTabRect(CurTab), Point(x, y)) then
          begin
            Result := CurTab;
            Exit;
          end;
      end;


  end
  else
  begin
    i := FFirstVisibleTab;
    LLastVisibleTab := GetLastVisibleTab(false);
    while (i <= GetTabCount - 1) and (i <= LLastVisibleTab) do
    begin
      LRect := GetTabRect(i);
      with LRect do
        case TabPosition of
          tpLeft: Right := Right + GetLeftMargin;
          tpTop: Bottom := Bottom - GetTopMargin;
          tpRight: Left := Left + GetRightMargin;
          tpBottom: Top := Top + GetBottomMargin;
        end;

      if PtInRect(LRect, Point(x, y)) then
      begin
        Result := i;
        Exit;
      end
      else
        Inc(i);
    end;
  end;
end;

procedure TKsCustomTabControl.MouseDown(Button: TMouseButton;
  Shift: TShiftState; x, y: integer);
var
  A: integer;
  OldLeftBtnPressed,
    OldRightBtnPressed: boolean;
begin
  inherited MouseDown(Button, Shift, x, y);

  if Button <> mbLeft then Exit;

  OldLeftBtnPressed := LeftBtnPressed;
  OldRightBtnPressed := RightBtnPressed;

  if PtInLeftBtn(x, y) then
    FMDownOnLeftBtn := true;

  if PtInRightBtn(x, y) then
    FMDownOnRightBtn := true;

  if ((OldLeftBtnPressed = false) and (LeftBtnPressed = true)) or
    ((OldRightBtnPressed = false) and (RightBtnPressed = true)) then
  begin
    Invalidate;
    PartPress(True);
  end
  else
  begin
    A := PtInTab(x, y);
    if A <> -1 then
    begin
      if not (csDesigning in ComponentState) then
        SetFocus;
      TabIndex := A;
    end;
  end;
end;

procedure TKsCustomTabControl.ScrollTabs(Delta: Integer);
var
  WasTrimmmed: boolean;
begin
  if Delta = 0 then Exit;

  if GetTabCount > 0 then
  begin
    FFirstVisibleTab := FFirstVisibleTab + Delta;
    if FFirstVisibleTab < 0 then
      FFirstVisibleTab := 0
    else
    begin
      if FFirstVisibleTab > GetTabCount - 1 then
        FFirstVisibleTab := GetTabCount - 1;

      WasTrimmmed := false;
      while (FFirstVisibleTab > 0) and
        (GetLastVisibleTab(true) = GetTabCount - 1) do
      begin
        FFirstVisibleTab := FFirstVisibleTab - 1;
        WasTrimmmed := true;
      end;
      if WasTrimmmed then
        FFirstVisibleTab := FFirstVisibleTab + 1;
    end;
  end
  else
    FFirstVisibleTab := -1;

  Invalidate;
end;

function TKsCustomTabControl.GetScrollBtnSize: TPoint;
begin
  with Result do
  begin
    x := Font.Size * 2;
    y := x;
  end;
end;

function TKsCustomTabControl.GetLeftScrlBtnRect: TRect;
var
  BtnSize: TPoint;
begin
  BtnSize := GetScrollBtnSize;
  with Result do
  begin
    Right := Width - BtnSize.x;
    Left := Right - BtnSize.x;
    if TabPosition = tpTop then
      Top := GetTabLineHeight - BtnSize.y - GetTopMargin {-1}
    else
      Top := Height - GetTabLineHeight + GetBottomMargin;
    Bottom := Top + BtnSize.y;
  end;
end;

function TKsCustomTabControl.GetRightScrlBtnRect: TRect;
var
  BtnSize: TPoint;
begin
  BtnSize := GetScrollBtnSize;
  with Result do
  begin
    Right := Width;
    Left := Right - BtnSize.x;
    if TabPosition = tpTop then
      Top := GetTabLineHeight - BtnSize.y - GetTopMargin {-1}
    else
      Top := Height - GetTabLineHeight + GetBottomMargin;
    Bottom := Top + BtnSize.y;
  end;
end;

procedure TKsCustomTabControl.DrawLeftScrlBtn;
var
  TmpRect: TRect;
  Flags: UINT;
begin
  TmpRect := GetLeftScrlBtnRect;

  Flags := DFCS_SCROLLLEFT;

  if LeftBtnPressed then
    Flags := Flags or DFCS_PUSHED;

  DrawFrameControl(Buffer.DC, TmpRect, DFC_SCROLL, Flags);
end;

procedure TKsCustomTabControl.DrawRightScrlBtn;
var
  TmpRect: TRect;
  Flags: UINT;
begin
  TmpRect := GetRightScrlBtnRect;

  Flags := DFCS_SCROLLRIGHT;

  if RightBtnPressed then
    Flags := Flags or DFCS_PUSHED;

  DrawFrameControl(Buffer.DC, TmpRect, DFC_SCROLL, Flags);
end;

function TKsCustomTabControl.GetScrlBtnsVisible: boolean;
begin
  if ((TabPosition = tpTop) or (TabPosition = tpBottom)) and (GetTabCount > 1) then
    Result := (not Multiline) and ((FFirstVisibleTab > 0) or (GetTabRect(GetTabCount - 1).Right > Width))
  else
    Result := false;
end;

function TKsCustomTabControl.GetLeftBtnPressed: boolean;
begin
  Result := FMDownOnLeftBtn and MouseOnLeftBtn;
end;

function TKsCustomTabControl.GetRightBtnPressed: boolean;
begin
  Result := FMDownOnRightBtn and MouseOnRightBtn;
end;

procedure TKsCustomTabControl.KeyDown(var Key: word; Shift: TShiftState);
begin
  inherited KeyDown(Key, Shift);

  case Key of
    VK_LEFT, VK_UP: TabIndex := TabIndex - 1;
    VK_RIGHT, VK_DOWN: TabIndex := TabIndex + 1;
  end;
end;

procedure TKsCustomTabControl.KeyUp(var Key: word; Shift: TShiftState);
begin
  inherited;

end;

procedure TKsCustomTabControl.MouseMove(Shift: TShiftState; x, y: integer);
var
  OldMouseOnLeftBtn,
    OldMouseOnRightBtn: boolean;
  OldMouseOnTabIndex: integer;
begin
  inherited;

  OldMouseOnLeftBtn := FMouseOnLeftBtn;
  FMouseOnLeftBtn := PtInLeftBtn(x, y) and GetScrlBtnsVisible;

  OldMouseOnRightBtn := FMouseOnRightBtn;
  FMouseOnRightBtn := PtInRightBtn(x, y) and GetScrlBtnsVisible;

  OldMouseOnTabIndex := MouseOnTabIndex;
  FMouseOnTabIndex := PtInTab(x, y);

  if (OldMouseOnLeftBtn <> FMouseOnLeftBtn) or
    (OldMouseOnRightBtn <> FMouseOnRightBtn) or
    (OldMouseOnTabIndex <> MouseOnTabIndex) then
    Invalidate;
end;

procedure TKsCustomTabControl.MouseUp(Button: TMouseButton;
  Shift: TShiftState; x, y: integer);
begin
  inherited;

  if Button <> mbLeft then Exit;

  FMDownOnLeftBtn := false;
  FMDownOnRightBtn := false;

  if FRepeatTimer <> nil then
    FRepeatTimer.Enabled := False;

  Invalidate;
end;

procedure TKsCustomTabControl.PartPress(FirstTime: boolean);
begin
  if FRepeatTimer = nil then
  begin
    FRepeatTimer := TTimer.Create(Self);
    FRepeatTimer.OnTimer := TimerExpired;
  end;
  if FirstTime then
  begin
    FRepeatTimer.OnTimer(FRepeatTimer);
    FRepeatTimer.Interval := CScrollDelayInterval;
  end
  else
    FRepeatTimer.Interval := CScrollSpeedInterval;

  FRepeatTimer.Enabled := true;
end;

function TKsCustomTabControl.PtInLeftBtn(x, y: integer): boolean;
begin
  Result := GetScrlBtnsVisible and PtInRect(GetLeftScrlBtnRect, Point(x, y));
end;

function TKsCustomTabControl.PtInRightBtn(x, y: integer): boolean;
begin
  Result := GetScrlBtnsVisible and PtInRect(GetRightScrlBtnRect, Point(x, y));
end;

procedure TKsCustomTabControl.TimerExpired(Sender: TObject);
begin
  FRepeatTimer.Interval := CScrollSpeedInterval;
  try
    if LeftBtnPressed then
    begin
      ScrollTabs(-1);
    end
    else
      if RightBtnPressed then
    begin
      ScrollTabs(1);
    end;
  except
    FRepeatTimer.Enabled := False;
  end;
end;

procedure TKsCustomTabControl.DoEnter;
begin
  inherited;
  Invalidate;
end;

procedure TKsCustomTabControl.DoExit;
begin
  inherited;
  Invalidate;
end;

procedure TKsCustomTabControl.UpdateTabRows;
var
  CurTab: integer;
  CurWidth: integer;
  LTabLineWidth: integer;
  CurLine: integer;
  RowBegTab: integer;
begin
  if GetTabCount <= 0 then
    Exit;

  if TabPosition in [tpTop, tpBottom] then
    LTabLineWidth := Width - GetLeftMargin - GetRightMargin
  else
    LTabLineWidth := Height - GetLeftMargin - GetRightMargin;

  FTabRowCount := 0;
  CurWidth := GetTabWidth(0);
  CurLine := 0;
  RowBegTab := 0;

  for CurTab := 1 to GetTabCount - 1 do
    if (CurWidth + GetTabWidth(CurTab) >= LTabLineWidth) then
    begin
      FTabRows[CurLine].LastTab := CurTab - 1;
      if CurTab - RowBegTab <> 0 then
        FTabRows[CurLine].ExternalTabWidth :=
          (LTabLineWidth - CurWidth) div (CurTab - RowBegTab)
      else
        FTabRows[CurLine].ExternalTabWidth := 0;
      FTabRows[CurLine].ExternalRest := LTabLineWidth -
        CurWidth - FTabRows[CurLine].ExternalTabWidth * (CurTab - RowBegTab);
      CurWidth := GetTabWidth(CurTab);
      CurLine := CurLine + 1;
      RowBegTab := CurTab;
    end
    else
      CurWidth := CurWidth + GetTabWidth(CurTab);

  if CurWidth > 0 then
  begin
    FTabRows[CurLine].LastTab := GetTabCount - 1;

    if GetTabCount - 1 - RowBegTab + 1 <> 0 then
      FTabRows[CurLine].ExternalTabWidth :=
        (LTabLineWidth - CurWidth) div (GetTabCount - 1 - RowBegTab + 1)
    else
      FTabRows[CurLine].ExternalTabWidth := 0;

    FTabRows[CurLine].ExternalRest := LTabLineWidth -
      CurWidth - FTabRows[CurLine].ExternalTabWidth * (GetTabCount - RowBegTab);
  end;
  FTabRowCount := CurLine + 1;
end;

function TKsCustomTabControl.GetTabRow(ATab: integer): integer;
var
  i: integer;
  LTabRowCount: integer;
begin
  LTabRowCount := GetTabRowCount;
  if LTabRowCount = 0 then
  begin
    Result := -1;
    Exit;
  end;

  Result := 0;
  for i := 0 to LTabRowCount - 2 do
    if (ATab > FTabRows[i].LastTab) and (ATab <= FTabRows[i + 1].LastTab) then
    begin
      Result := i + 1;
      Exit;
    end;
end;

function TKsCustomTabControl.GetTabRowHeight(ARow: integer): integer;
var
  LActiveRow: integer;
begin
  LActiveRow := GetTabRow(TabIndex);
  if not ScrollOpposite then
  begin

    if ARow >= LActiveRow then
      Result := ARow - LActiveRow + 1
    else
      Result := ARow + GetTabRowCount - LActiveRow + 1;
  end
  else
    if ARow >= LActiveRow then
    Result := ARow - LActiveRow + 1
  else
    Result := LActiveRow - ARow;

  Result := Result * GetTabHeight;
end;

function TKsCustomTabControl.GetTabExtendedWidth(ATabIndex: integer): integer;
var
  LRow: integer;
begin
  if RaggedRight or (FTabSize.x <> 0) or not MultiLine then
    Result := GetTabWidth(ATabIndex)
  else
  begin
    LRow := GetTabRow(ATabIndex);
    Result := GetTabWidth(ATabIndex) + FTabRows[LRow].ExternalTabWidth;
    if FTabRows[LRow].LastTab = ATabIndex then
      Result := Result + FTabRows[LRow].ExternalRest;
  end;
end;

function TKsCustomTabControl.IsTabLastInRow(ATabIndex: integer): boolean;
var
  CurRow: integer;
begin
  Result := false;
  for CurRow := 0 to GetTabRowCount - 1 do
    if FTabRows[CurRow].LastTab = ATabIndex then
    begin
      Result := true;
      Break;
    end;
end;

function TKsCustomTabControl.GetOppositeTabLineHeight: integer;
begin
  if ScrollOpposite then
    Result := GetTabRow(TabIndex) * GetTabHeight
  else
    Result := 0;
end;

function TKsCustomTabControl.IsTabOnOpositeSide(ATab: integer): boolean;
begin
  Result := Multiline and ScrollOpposite and (GetTabRow(ATab) < GetTabRow(TabIndex));
end;

procedure TKsCustomTabControl.Change;
begin
  if Assigned(FOnChange) then
    FOnChange(Self);

  if FUsePages then
  begin
    { Select Page }
    SetActivePage(FPages[TabIndex]);
  end;
end;

function TKsCustomTabControl.CanChange: Boolean;
begin
  Result := True;
  if Assigned(FOnChanging) then
    FOnChanging(Self, Result);
end;

procedure TKsCustomTabControl.DoDrawTab(TabIndex: Integer; const Rect: TRect;
  Active: Boolean);
begin
  if Assigned(FOnDrawTab) then
    FOnDrawTab(Self, TabIndex, Rect, Active);
end;

function TKsCustomTabControl.IndexOfTabAt(X, Y: Integer): Integer;
begin
  Result := PtInTab(x, y);
end;

function TKsCustomTabControl.RowCount: Integer;
begin
  if GetTabCount = 0 then
    Result := 0
  else
    Result := GetTabRowCount;
end;

function TKsCustomTabControl.TabRect(Index: Integer): TRect;
begin
  GetTabRect(Index);
end;

procedure TKsCustomTabControl.WMGetDlgCode(var Msg: TWMGetDlgCode);
begin
  inherited;
  Msg.Result := dlgc_WantArrows;
end;

procedure TKsCustomTabControl.CMDesignHitTest(var Msg: TCMDesignHitTest);
var
  Shift: TShiftState;
begin
  Shift := KeysToShiftState(Msg.Keys);

  if ssLeft in Shift then
  begin
    MouseDown(mbLeft, Shift, Msg.XPos, Msg.YPos);
    Msg.Result := 0;
  end;
end;

procedure TKsCustomTabControl.CMEnabledChanged(var Msg: TMessage);
begin
  inherited;
  Invalidate;
end;

procedure TKsCustomTabControl.CMMouseLeave(var Msg: TMessage);
begin
  if FMouseOnLeftBtn or
    FMouseOnRightBtn or
    (FMouseOnTabIndex > -1)
    then
  begin
    FMouseOnLeftBtn := false;
    FMouseOnRightBtn := false;
    FMouseOnTabIndex := -1;
    Invalidate;
  end;
end;

procedure TKsCustomTabControl.SetTabPosition(const Value: TTabPosition);
begin
  if FTabPosition <> Value then
  begin
    FTabPosition := Value;
    if (TabPosition in [tpLeft, tpRight]) and not MultiLine then
      MultiLine := true;

    Realign;
    Invalidate;
  end;
end;

procedure TKsCustomTabControl.SetTabs(const Value: TStrings);
begin
  FTabs.Assign(Value);
end;

procedure TKsCustomTabControl.SetTabIndex(const Value: integer);
var
  TmpRect: TRect;
  OldIndex: integer;
begin
  if (FTabIndex <> Value) and (Value >= 0)
    and (Value <= GetTabCount - 1) then
  begin
    OldIndex := FTabIndex;
    FTabIndex := Value;

    if not CanChange then
    begin
      FTabIndex := OldIndex;
      Exit;
    end;

    while GetScrlBtnsVisible and
      (IntersectRect(TmpRect, GetTabRect(FTabIndex), GetLeftScrlBtnRect) or
      IntersectRect(TmpRect, GetTabRect(FTabIndex), GetRightScrlBtnRect)) do
      FFirstVisibleTab := FFirstVisibleTab + 1;
    while FFirstVisibleTab > FTabIndex do
      FFirstVisibleTab := FFirstVisibleTab - 1;

    Invalidate;
    Realign;
    Change;
  end;
end;

procedure TKsCustomTabControl.SetRaggedRight(const Value: Boolean);
begin
  if FRaggedRight <> Value then
  begin
    FRaggedRight := Value;
    Invalidate;
  end;
end;

procedure TKsCustomTabControl.SetScrollOpposite(const Value: Boolean);
begin
  if FScrollOpposite <> Value then
  begin
    FScrollOpposite := Value;
    if Value then
      FMultiLine := Value;

    Realign;
    Invalidate;
  end;
end;

procedure TKsCustomTabControl.SetHotTrack(const Value: Boolean);
begin
  if FHotTrack <> Value then
  begin
    FHotTrack := Value;
    Invalidate;
  end;
end;

procedure TKsCustomTabControl.SetTabHeight(const Value: integer);
begin
  if FTabSize.y <> Value then
  begin
    FTabSize.y := Value;
    Invalidate;
    Realign;
  end;
end;

procedure TKsCustomTabControl.SetTabWidth(const Value: integer);
begin
  if FTabSize.x <> Value then
  begin
    FTabSize.x := Value;
    Invalidate;
    Realign;
  end;
end;

procedure TKsCustomTabControl.SetOwnerDraw(const Value: Boolean);
begin
  if FOwnerDraw <> Value then
  begin
    FOwnerDraw := Value;
    Invalidate;
  end;
end;

procedure TKsCustomTabControl.SetMultiLine(const Value: Boolean);
begin
  if (TabPosition in [tpLeft, tpRight]) and (Value = false) then
    raise Exception.Create('MultiLine must be True when TabPosition is tpLeft or tpRight')
  else
    if FMultiLine <> Value then
    begin
      FMultiLine := Value;
      Realign;
      Invalidate;
    end;
end;

function TKsCustomTabControl.GetPage(Index: Integer): TKsCustomTabSheet;
begin
  Result := FPages[Index];
end;

function TKsCustomTabControl.GetPageCount: Integer;
begin
  Result := FPages.Count;
end;

procedure TKsCustomTabControl.SetUsePages(const Value: boolean);
begin
  if FUsePages <> Value then
  begin
    FUsePages := Value;

    if not (csLoading in ComponentState) then
    begin
      if FUsePages then
      begin
        { Remove all controls }
        while ControlCount > 0 do
          Controls[0].Free;
      end;

      TabsChanged;
    end;
  end;
end;

function TKsCustomTabControl.GetActivePageIndex: integer;
begin
  if FUsePages and (FActivePage <> nil) then
    Result := FActivePage.TabIndex
  else
    Result := -1;
end;

function TKsCustomTabControl.GetTabSizeX: integer;
begin
  Result := FTabSize.x;
end;

function TKsCustomTabControl.GetTabSizeY: integer;
begin
  Result := FTabSize.y;
end;

initialization
  RegisterClass(TKsCustomTabSheet);
end.

