{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is: JvGrdCpt.PAS, released on 2002-07-04.

The Initial Developers of the Original Code are: Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 1997, 1998 Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 2001,2002 SGB Software
All Rights Reserved.

You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.delphi-jedi.org

Known Issues:
-----------------------------------------------------------------------------}
// $Id$

unit JvGradientCaption;

{$I jvcl.inc}

interface

uses
  {$IFDEF UNITVERSIONING}
  JclUnitVersioning,
  {$ENDIF UNITVERSIONING}
  Windows, Messages, Classes, Graphics, Controls, Forms, Menus,
  JvWndProcHook, JvJCLUtils, JvJVCLUtils;

type
  THideDirection = (hdLeftToRight, hdRightToLeft);

  TJvCaption = class;
  TJvCaptionList = class;

  {$IFDEF RTL230_UP}
  [ComponentPlatformsAttribute(pidWin32 or pidWin64 or pidOSX32)]
  {$ENDIF RTL230_UP}
  TJvGradientCaption = class(TComponent)
  private
    FActive: Boolean;
    FWindowActive: Boolean;
    FSaveRgn: HRGN;
    FRgnChanged: Boolean;
    FWinHook: TJvWindowHook;
    FStartColor: TColor;
    FEndColor: TColor;
    FFillDirection : TFillDirection;
    FCaptions: TJvCaptionList;
    FFont: TFont;
    FDefaultFont: Boolean;
    FPopupMenu: TPopupMenu;
    FClicked: Boolean;
    FHideDirection: THideDirection;
    FGradientInactive: Boolean;
    FGradientActive: Boolean;
    FFontInactiveColor: TColor;
    FFormCaption: string;
    FGradientSteps: Integer;
    FOnActivate: TNotifyEvent;
    FOnDeactivate: TNotifyEvent;
    procedure SetHook;
    procedure ReleaseHook;
    procedure CheckToggleHook;
    function GetActive: Boolean;
    procedure SetActive(Value: Boolean);
    procedure SetStartColor(Value: TColor);
    procedure DrawGradientCaption(DC: HDC);
    procedure CalculateGradientParams(var R: TRect; var Icons: TBorderIcons);
    function GetForm: TForm;
    function GetFormCaption: string;
    procedure SetFormCaption(const Value: string);
    procedure BeforeMessage(Sender: TObject; var Msg: TMessage; var Handled: Boolean);
    procedure AfterMessage(Sender: TObject; var Msg: TMessage; var Handled: Boolean);
    function CheckMenuPopup(X, Y: Integer): Boolean;
    procedure SetFont(Value: TFont);
    procedure FontChanged(Sender: TObject);
    procedure SetDefaultFont(Value: Boolean);
    procedure SetFontDefault;
    function IsFontStored: Boolean;
    function GetTextWidth: Integer;
    procedure SetCaptions(Value: TJvCaptionList);
    procedure SetGradientActive(Value: Boolean);
    procedure SetGradientInactive(Value: Boolean);
    procedure SetGradientSteps(Value: Integer);
    procedure SetFontInactiveColor(Value: TColor);
    procedure SetHideDirection(Value: THideDirection);
    procedure SetPopupMenu(Value: TPopupMenu);
    procedure SetEndColor(Value: TColor);
    procedure SetFillDirection(Value: TFillDirection);
  protected
    procedure Loaded; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    function IsRightToLeft: Boolean;
    property Form: TForm read GetForm;
    property TextWidth: Integer read GetTextWidth;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure MoveCaption(FromIndex, ToIndex: Integer);
    procedure Update;
    procedure Clear;
  published
    property Active: Boolean read GetActive write SetActive default True;
    property Captions: TJvCaptionList read FCaptions write SetCaptions;
    property DefaultFont: Boolean read FDefaultFont write SetDefaultFont default True;
    property FormCaption: string read GetFormCaption write SetFormCaption;
    property FontInactiveColor: TColor read FFontInactiveColor
      write SetFontInactiveColor default clInactiveCaptionText;
    property Font: TFont read FFont write SetFont stored IsFontStored;
    property GradientActive: Boolean read FGradientActive
      write SetGradientActive default True;
    property GradientInactive: Boolean read FGradientInactive
      write SetGradientInactive default False;
    property GradientSteps: Integer read FGradientSteps write SetGradientSteps default 64;
    property HideDirection: THideDirection read FHideDirection
      write SetHideDirection default hdLeftToRight;
    property PopupMenu: TPopupMenu read FPopupMenu write SetPopupMenu;
    property StartColor: TColor read FStartColor write SetStartColor
      default clWindowText;
    property EndColor: TColor read FEndColor write SetEndColor
      default clActiveCaption;
    property  FillDirection : TFillDirection read FFillDirection write SetFillDirection
      default fdLeftToRight;
    property OnActivate: TNotifyEvent read FOnActivate write FOnActivate;
    property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate;
  end;

  TJvCaptionList = class(TCollection)
  private
    FParent: TJvGradientCaption;
    function GetCaption(Index: Integer): TJvCaption;
    procedure SetCaption(Index: Integer; Value: TJvCaption);
  protected
    function GetOwner: TPersistent; override;
    procedure Update(Item: TCollectionItem); override;
  public
    constructor Create(AParent: TJvGradientCaption);
    function Add: TJvCaption;
    procedure RestoreDefaults;
    property Parent: TJvGradientCaption read FParent;
    property Items[Index: Integer]: TJvCaption read GetCaption write SetCaption; default;
  end;

  TJvCaption = class(TCollectionItem)
  private
    FCaption: string;
    FFont: TFont;
    FParentFont: Boolean;
    FVisible: Boolean;
    FGlueNext: Boolean;
    FInactiveColor: TColor;
    procedure SetCaption(const Value: string);
    procedure SetFont(Value: TFont);
    procedure SetParentFont(Value: Boolean);
    procedure FontChanged(Sender: TObject);
    function IsFontStored: Boolean;
    function GetTextWidth: Integer;
    procedure SetVisible(Value: Boolean);
    procedure SetInactiveColor(Value: TColor);
    procedure SetGlueNext(Value: Boolean);
  protected
    function GetParentCaption: TJvGradientCaption;
    property TextWidth: Integer read GetTextWidth;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    procedure RestoreDefaults; virtual;
    property GradientCaption: TJvGradientCaption read GetParentCaption;
  published
    property Caption: string read FCaption write SetCaption;
    property Font: TFont read FFont write SetFont stored IsFontStored;
    property ParentFont: Boolean read FParentFont write SetParentFont default True;
    property InactiveColor: TColor read FInactiveColor write SetInactiveColor
      default clInactiveCaptionText;
    property GlueNext: Boolean read FGlueNext write SetGlueNext default False;
    property Visible: Boolean read FVisible write SetVisible default True;
  end;

function GradientFormCaption(AForm: TCustomForm; AStartColor, AEndColor: TColor):
  TJvGradientCaption;

{$IFDEF UNITVERSIONING}
const
  UnitVersioning: TUnitVersionInfo = (
    RCSfile: '$URL$';
    Revision: '$Revision$';
    Date: '$Date$';
    LogPath: 'JVCL\run'
  );
{$ENDIF UNITVERSIONING}

implementation

uses
  SysUtils,
  JvConsts, JvCaptionButton;

function GradientFormCaption(AForm: TCustomForm; AStartColor, AEndColor: TColor): TJvGradientCaption;
begin
  Result := TJvGradientCaption.Create(AForm);
  with Result do
  try
    FStartColor := AStartColor;
    FEndColor := AEndColor;
    FormCaption := AForm.Caption;
    Update;
  except
    Free;
    raise;
  end;
end;

function InternalGetTextWidth(Font: TFont; const Caption: string): Integer;
var
  Canvas: TCanvas;
  PS: TPaintStruct;
begin
  BeginPaint(Application.Handle, PS);
  try
    Canvas := TCanvas.Create;
    try
      Canvas.Handle := PS.hdc;
      Canvas.Font := Font;
      Result := Canvas.TextWidth(Caption);
    finally
      Canvas.Free;
    end;
  finally
    EndPaint(Application.Handle, PS);
  end;
end;

//=== { TJvCaptionList } =====================================================

constructor TJvCaptionList.Create(AParent: TJvGradientCaption);
begin
  inherited Create(TJvCaption);
  FParent := AParent;
end;

function TJvCaptionList.Add: TJvCaption;
begin
  Result := TJvCaption(inherited Add);
end;

function TJvCaptionList.GetCaption(Index: Integer): TJvCaption;
begin
  Result := TJvCaption(inherited Items[Index]);
end;

function TJvCaptionList.GetOwner: TPersistent;
begin
  Result := FParent;
end;

procedure TJvCaptionList.RestoreDefaults;
var
  I: Integer;
begin
  BeginUpdate;
  try
    for I := 0 to Count - 1 do
      Items[I].RestoreDefaults;
  finally
    EndUpdate;
  end;
end;

procedure TJvCaptionList.SetCaption(Index: Integer; Value: TJvCaption);
begin
  Items[Index].Assign(Value);
end;

procedure TJvCaptionList.Update(Item: TCollectionItem);
begin
  if (FParent <> nil) and not (csLoading in FParent.ComponentState) then
    if FParent.Active then
      FParent.Update;
end;

//=== { TJvCaption } =========================================================

constructor TJvCaption.Create(Collection: TCollection);
var
  Parent: TJvGradientCaption;
begin
  Parent := nil;
  if Assigned(Collection) and (Collection is TJvCaptionList) then
    Parent := TJvCaptionList(Collection).Parent;
  try
    inherited Create(Collection);
    FFont := TFont.Create;
    if Assigned(Parent) then
    begin
      FFont.Assign(Parent.Font);
      FFont.Color := Parent.Font.Color;
    end
    else
      FFont.Color := clCaptionText;
    FFont.OnChange := FontChanged;
    FCaption := '';
    FParentFont := True;
    FVisible := True;
    FGlueNext := False;
    FInactiveColor := clInactiveCaptionText;
  finally
    if Assigned(Parent) then
      Changed(False);
  end;
end;

destructor TJvCaption.Destroy;
begin
  FFont.Free;
  FFont := nil;
  inherited Destroy;
end;

procedure TJvCaption.Assign(Source: TPersistent);
begin
  if Source is TJvCaption then
  begin
    if Assigned(Collection) then
      Collection.BeginUpdate;
    try
      RestoreDefaults;
      Caption := TJvCaption(Source).Caption;
      ParentFont := TJvCaption(Source).ParentFont;
      if not ParentFont then
        Font.Assign(TJvCaption(Source).Font);
      InactiveColor := TJvCaption(Source).InactiveColor;
      GlueNext := TJvCaption(Source).GlueNext;
      Visible := TJvCaption(Source).Visible;
    finally
      if Assigned(Collection) then
        Collection.EndUpdate;
    end;
  end
  else
    inherited Assign(Source);
end;

procedure TJvCaption.RestoreDefaults;
begin
  FInactiveColor := clInactiveCaptionText;
  FVisible := True;
  ParentFont := True;
end;

function TJvCaption.GetParentCaption: TJvGradientCaption;
begin
  if Assigned(Collection) and (Collection is TJvCaptionList) then
    Result := TJvCaptionList(Collection).Parent
  else
    Result := nil;
end;

procedure TJvCaption.SetCaption(const Value: string);
begin
  FCaption := Value;
  Changed(False);
end;

procedure TJvCaption.FontChanged(Sender: TObject);
begin
  FParentFont := False;
  Changed(False);
end;

procedure TJvCaption.SetFont(Value: TFont);
begin
  FFont.Assign(Value);
end;

procedure TJvCaption.SetParentFont(Value: Boolean);
begin
  if Value and (GradientCaption <> nil) then
  begin
    FFont.OnChange := nil;
    try
      FFont.Assign(GradientCaption.Font);
    finally
      FFont.OnChange := FontChanged;
    end;
  end;
  FParentFont := Value;
  Changed(False);
end;

function TJvCaption.IsFontStored: Boolean;
begin
  Result := not ParentFont;
end;

function TJvCaption.GetTextWidth: Integer;
begin
  Result := InternalGetTextWidth(Font, Caption);
end;

procedure TJvCaption.SetVisible(Value: Boolean);
begin
  if FVisible <> Value then
  begin
    FVisible := Value;
    Changed(False);
  end;
end;

procedure TJvCaption.SetInactiveColor(Value: TColor);
begin
  if FInactiveColor <> Value then
  begin
    FInactiveColor := Value;
    if (GradientCaption = nil) or not GradientCaption.FWindowActive then
      Changed(False);
  end;
end;

procedure TJvCaption.SetGlueNext(Value: Boolean);
begin
  if FGlueNext <> Value then
  begin
    FGlueNext := Value;
    Changed(False);
  end;
end;

//=== { TJvGradientCaption } ================================================

constructor TJvGradientCaption.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FGradientSteps := 64;
  FGradientActive := True;
  FActive := True;
  FCaptions := TJvCaptionList.Create(Self);
  FWinHook := TJvWindowHook.Create(Self);
  FWinHook.BeforeMessage := BeforeMessage;
  FWinHook.AfterMessage := AfterMessage;
  FStartColor := clWindowText;
  FEndColor := clActiveCaption;// doubt: should it be clGradientActiveCaption?
  FFillDirection := fdLeftToRight;
  FFontInactiveColor := clInactiveCaptionText;
  FFormCaption := '';
  FFont := TFont.Create;
  SetFontDefault;
end;

destructor TJvGradientCaption.Destroy;
begin
  FOnDeactivate := nil;
  FOnActivate := nil;
  if not (csDesigning in ComponentState) then
    ReleaseHook;
  FCaptions.Free;
  FCaptions := nil;
  FFont.Free;
  FFont := nil;
  inherited Destroy;
end;

procedure TJvGradientCaption.Loaded;
var
  Loading: Boolean;
begin
  Loading := csLoading in ComponentState;
  inherited Loaded;
  if not (csDesigning in ComponentState) then
  begin
    if Loading and (Owner is TCustomForm) then
      Update;
  end;
end;

procedure TJvGradientCaption.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if (AComponent = PopupMenu) and (Operation = opRemove) then
    PopupMenu := nil;
end;

procedure TJvGradientCaption.SetPopupMenu(Value: TPopupMenu);
begin
  ReplaceComponentReference(Self, Value, TComponent(FPopupMenu));
end;

procedure TJvGradientCaption.SetCaptions(Value: TJvCaptionList);
begin
  Captions.Assign(Value);
end;

procedure TJvGradientCaption.SetDefaultFont(Value: Boolean);
begin
  if FDefaultFont <> Value then
  begin
    if Value then
      SetFontDefault;
    FDefaultFont := Value;
    if Active then
      Update;
  end;
end;

procedure TJvGradientCaption.SetFontDefault;
var
  NCMetrics: TNonClientMetrics;
begin
  with FFont do
  begin
    OnChange := nil;
    try
      {$IFDEF RTL210_UP}
      NCMetrics.cbSize := TNonClientMetrics.SizeOf;
      {$ELSE}
      NCMetrics.cbSize := SizeOf(NCMetrics);
      {$ENDIF RTL210_UP}
      if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, NCMetrics.cbSize, @NCMetrics, 0) then
      begin
        if (Owner is TForm) and
          ((Owner as TForm).BorderStyle in [bsToolWindow, bsSizeToolWin]) then
          Handle := CreateFontIndirect(NCMetrics.lfSmCaptionFont)
        else
          Handle := CreateFontIndirect(NCMetrics.lfCaptionFont);
      end
      else
      begin
        Name := 'MS Sans Serif';
        Size := 8;
        Style := [fsBold];
      end;
      Color := clCaptionText;
      Charset := DEFAULT_CHARSET;
    finally
      OnChange := FontChanged;
    end;
  end;
  FDefaultFont := True;
end;

function TJvGradientCaption.IsFontStored: Boolean;
begin
  Result := not DefaultFont;
end;

function TJvGradientCaption.GetForm: TForm;
begin
  if Owner is TCustomForm then
    Result := TForm(Owner as TCustomForm)
  else
    Result := nil;
end;

function TJvGradientCaption.GetFormCaption: string;
begin
  if (Form <> nil) and (csDesigning in ComponentState) then
    FFormCaption := Form.Caption;
  Result := FFormCaption;
end;

procedure TJvGradientCaption.SetFormCaption(const Value: string);
begin
  if FFormCaption <> Value then
  begin
    FFormCaption := Value;
    if (Form <> nil) and (csDesigning in ComponentState) then
      Form.Caption := FFormCaption;
    if Active then
      Update;
  end;
end;

procedure TJvGradientCaption.SetHook;
begin
  if not (csDesigning in ComponentState) and (Owner <> nil) and
    (Owner is TCustomForm) then
    FWinHook.Control := Form;
end;

procedure TJvGradientCaption.ReleaseHook;
begin
  FWinHook.Control := nil;
end;

procedure TJvGradientCaption.CheckToggleHook;
begin
  if Active then
    SetHook
  else
    ReleaseHook;
end;

function TJvGradientCaption.CheckMenuPopup(X, Y: Integer): Boolean;
begin
  Result := False;
  if not (csDesigning in ComponentState) and Assigned(FPopupMenu) and
    FPopupMenu.AutoPopup then
  begin
    FPopupMenu.PopupComponent := Self;
    if Form <> nil then
    begin
      Form.SendCancelMode(nil);
      FPopupMenu.Popup(X, Y);
      Result := True;
    end;
  end;
end;

procedure TJvGradientCaption.BeforeMessage(Sender: TObject; var Msg: TMessage;
  var Handled: Boolean);
var
  DrawRgn: HRGN;
  R: TRect;
  Icons: TBorderIcons;
begin
  if Active then
  begin
    case Msg.Msg of
      WM_NCACTIVATE:
        FWindowActive := (Msg.WParam <> 0);
      WM_NCRBUTTONDOWN:
        if Assigned(FPopupMenu) and FPopupMenu.AutoPopup then
        begin
          FClicked := True;
          Msg.Result := 0;
          Handled := True;
        end;
      WM_NCRBUTTONUP:
        with TWMMouse(Msg) do
          if FClicked then
          begin
            FClicked := False;
            if CheckMenuPopup(XPos, YPos) then
            begin
              Result := 0;
              Handled := True;
            end;
          end;
      WM_NCPAINT:
        begin
          FSaveRgn := Msg.WParam;
          FRgnChanged := False;
          CalculateGradientParams(R, Icons);
          if RectInRegion(FSaveRgn, R) then
          begin
            DrawRgn := CreateRectRgn(R.Left, R.Top, R.Right, R.Bottom);
            try
              Msg.WParam := CreateRectRgn(0, 0, 1, 1);
              FRgnChanged := True;
              CombineRgn(Msg.WParam, FSaveRgn, DrawRgn, RGN_DIFF);
            finally
              DeleteObject(DrawRgn);
            end;
          end;
        end;
    end;
  end;
end;

procedure TJvGradientCaption.AfterMessage(Sender: TObject; var Msg: TMessage;
  var Handled: Boolean);
var
  DC: HDC;
  S: string;
begin
  if Active then
  begin
    case Msg.Msg of
      WM_NCACTIVATE:
        begin
          DC := GetWindowDC(Form.Handle);
          try
            DrawGradientCaption(DC);
          finally
            ReleaseDC(Form.Handle, DC);
          end;
        end;
      WM_NCPAINT:
        begin
          if FRgnChanged then
          begin
            DeleteObject(Msg.WParam);
            Msg.WParam := FSaveRgn;
            FRgnChanged := False;
          end;
          DC := GetWindowDC(Form.Handle);
          try
            DrawGradientCaption(DC);
          finally
            ReleaseDC(Form.Handle, DC);
          end;
        end;
      WM_GETTEXT:
        { Delphi doesn't send WM_SETTEXT to form's window procedure,
          so we need to handle WM_GETTEXT to redraw non-client area
          when form's caption changed }
        if csDesigning in ComponentState then
        begin
          SetString(S, PChar(Msg.LParam), Msg.Result);
          if AnsiCompareStr(S, FFormCaption) <> 0 then
          begin
            FormCaption := S;
            PostMessage(Form.Handle, WM_NCPAINT, 0, 0);
          end;
        end;
    end;
  end;
end;

procedure TJvGradientCaption.SetStartColor(Value: TColor);
begin
  if FStartColor <> Value then
  begin
    FStartColor := Value;
    if Active then
      Update;
  end;
end;

procedure TJvGradientCaption.SetEndColor(Value: TColor);
begin
  if FEndColor <> Value then
  begin
    FEndColor := Value;
    if Active then
      Update;
  end;
end;


function TJvGradientCaption.GetActive: Boolean;
begin
  Result := FActive;
  if not (csDesigning in ComponentState) then
    Result := Result and (Owner is TCustomForm);
end;

procedure TJvGradientCaption.SetActive(Value: Boolean);
begin
  if FActive <> Value then
  begin
    FActive := Value;
    FClicked := False;
    Update;
    if [csDestroying, csReading] * ComponentState = [] then
    begin
      if FActive then
      begin
        if Assigned(FOnActivate) then
          FOnActivate(Self);
      end
      else
      begin
        if Assigned(FOnDeactivate) then
          FOnDeactivate(Self);
      end;
    end;
  end;
end;

procedure TJvGradientCaption.Clear;
begin
  if FCaptions <> nil then
    FCaptions.Clear;
end;

procedure TJvGradientCaption.MoveCaption(FromIndex, ToIndex: Integer);
begin
  Captions[FromIndex].Index := ToIndex;
end;

procedure TJvGradientCaption.Update;
var
  Rgn: HRGN;
begin
  if not (csDesigning in ComponentState) and (Owner is TCustomForm) and
    not (csLoading in ComponentState) then
  begin
    CheckToggleHook;
    FWindowActive := False;
    if (Form <> nil) and Form.HandleAllocated and Form.Visible then
    begin
      if Active then
        FWindowActive := (GetActiveWindow = Form.Handle) and IsForegroundTask;
      with Form do
        Rgn := CreateRectRgn(Left, Top, Left + Width, Top + Height);
      try
        SendMessage(Form.Handle, WM_NCPAINT, Rgn, 0);
      finally
        DeleteObject(Rgn);
      end;
    end;
  end;
end;

procedure TJvGradientCaption.CalculateGradientParams(var R: TRect;
  var Icons: TBorderIcons);
var
  I: TBorderIcon;
  J: Integer;
  BtnCount: Integer;
  CaptionButton: TJvCaptionButton;
begin
  GetWindowRect(Form.Handle, R);
  Icons := Form.BorderIcons;
  case Form.BorderStyle of
    bsDialog: Icons := Icons * [biSystemMenu, biHelp];
    bsToolWindow, bsSizeToolWin: Icons := Icons * [biSystemMenu];
  else
    begin
      if not (biSystemMenu in Icons) then
        Icons := Icons - [biMaximize, biMinimize];
      if Icons * [biMaximize, biMinimize] <> [] then
        Icons := Icons - [biHelp];
    end;
  end;
  BtnCount := 0;
  for I := Low(TBorderIcon) to High(TBorderIcon) do
    if I in Icons then
      Inc(BtnCount);
  if (biMinimize in Icons) and not (biMaximize in Icons) then
    Inc(BtnCount)
  else
  if not (biMinimize in Icons) and (biMaximize in Icons) then
    Inc(BtnCount);
  case Form.BorderStyle of
    bsToolWindow, bsSingle, bsDialog:
      InflateRect(R, -GetSystemMetrics(SM_CXFIXEDFRAME),
        -GetSystemMetrics(SM_CYFIXEDFRAME));
    bsSizeable, bsSizeToolWin:
      InflateRect(R, -GetSystemMetrics(SM_CXSIZEFRAME),
        -GetSystemMetrics(SM_CYSIZEFRAME));
  end;
  if Form.BorderStyle in [bsToolWindow, bsSizeToolWin] then
  begin
    R.Bottom := R.Top + GetSystemMetrics(SM_CYSMCAPTION) - 1;
    Dec(R.Right, BtnCount * GetSystemMetrics(SM_CXSMSIZE));
  end
  else
  begin
    R.Bottom := R.Top + GetSystemMetrics(SM_CYCAPTION) - 1;
    Dec(R.Right, BtnCount * GetSystemMetrics(SM_CXSIZE));
  end;

  // Mantis 3857: take JvCaptionButtons into account
  for J := 0 to Form.ComponentCount - 1 do
  begin
    if Form.Components[J] is TJvCaptionButton then
    begin
      CaptionButton := Form.Components[J] as TJvCaptionButton;
      if CaptionButton.Visible then
      begin
        if CaptionButton.ButtonWidth = 0 then
          Dec(R.Right, CaptionButton.DefaultButtonWidth)
        else
          Dec(R.Right, CaptionButton.ButtonWidth);
        Dec(R.Right, CaptionButton.Spacing);
      end;
    end;
  end;
end;

function TJvGradientCaption.IsRightToLeft: Boolean;
var
  F: TForm;
begin
  F := Form;
  if F <> nil then
    Result := F.IsRightToLeft
  else
    Result := Application.IsRightToLeft;
end;

procedure TJvGradientCaption.DrawGradientCaption(DC: HDC);
var
  R, DrawRect: TRect;
  Icons: TBorderIcons;
  Ico: HIcon;
  Image: TBitmap;
  S: string;
  IconCreated, DrawNext: Boolean;
  I, J, SumWidth: Integer;

  procedure SetCaptionFont(Index: Integer);
  begin
    if (Index < 0) or Captions[Index].ParentFont then
      Image.Canvas.Font.Assign(Self.Font)
    else
      Image.Canvas.Font.Assign(Captions[Index].Font);
    if not FWindowActive then
    begin
      if Index < 0 then
        Image.Canvas.Font.Color := FFontInactiveColor
      else
        Image.Canvas.Font.Color := Captions[Index].InactiveColor;
    end;
  end;

  function DrawStr(GluePrev, GlueNext: Boolean; PrevIndex: Integer): Boolean;
  const
    Points = '...';
  var
    Text: string;
    Flags: Longint;
  begin
    if Length(S) > 0 then
    begin
      Text := MinimizeText(S, Image.Canvas, R.Right - R.Left);
      if GlueNext and (Text = S) then
      begin
        if Image.Canvas.TextWidth(Text + '.') >= R.Right - R.Left then
        begin
          if GluePrev then
            Text := Points
          else
            Text := Text + Points;
        end;
      end;
      if (Text <> Points) or GluePrev then
      begin
        if (Text = Points) and GluePrev then
        begin
          SetCaptionFont(-1);
          if PrevIndex > 0 then
          begin
            if FWindowActive then
              Image.Canvas.Font.Color := Captions[PrevIndex].Font.Color
            else
              Image.Canvas.Font.Color := Captions[PrevIndex].InactiveColor;
          end;
        end;
        Flags := DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX;
        if IsRightToLeft then
          Flags := Flags or DT_RIGHT or DT_RTLREADING
        else
          Flags := Flags or DT_LEFT;
        DrawText(Image.Canvas, Text, -1, R, Flags);
        if IsRightToLeft then
          Dec(R.Right, Image.Canvas.TextWidth(Text))
        else
          Inc(R.Left, Image.Canvas.TextWidth(Text));
      end;
      Result := (Text = S);
    end
    else
      Result := True;
  end;

begin
  if Form.BorderStyle = bsNone then
    Exit;
  Image := TBitmap.Create;
  try
    CalculateGradientParams(R, Icons);
    GetWindowRect(Form.Handle, DrawRect);
    OffsetRect(R, -DrawRect.Left, -DrawRect.Top);
    DrawRect := R;
    Image.Width := RectWidth(R);
    Image.Height := RectHeight(R);
    R := Rect(-Image.Width div 4, 0, Image.Width, Image.Height);
    if (FWindowActive and GradientActive) or
      (not FWindowActive and GradientInactive) then
    begin
      GradientFillRect(Image.Canvas, R, FStartColor, FEndColor, FFillDirection, FGradientSteps);
    end
    else
    begin
      Image.Canvas.Brush.Color := FEndColor;
      Image.Canvas.FillRect(R);
    end;
    R.Left := 0;
    if (biSystemMenu in Icons) and (Form.BorderStyle in [bsSizeable, bsSingle]) then
    begin
      IconCreated := False;
      if Form.Icon.Handle <> 0 then
        Ico := Form.Icon.Handle
      else
      if Application.Icon.Handle <> 0 then
      begin
        Ico := LoadImage(HInstance, 'MAINICON', IMAGE_ICON,
          GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0);
        IconCreated := Ico <> 0;
        if not IconCreated then
          Ico := Application.Icon.Handle;
      end
      else
        Ico := LoadIcon(0, IDI_APPLICATION);
      DrawIconEx(Image.Canvas.Handle, R.Left + 1 + (R.Bottom + R.Top -
        GetSystemMetrics(SM_CXSMICON)) div 2, (R.Bottom + R.Top -
        GetSystemMetrics(SM_CYSMICON)) div 2, Ico,
        GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
        0, 0, DI_NORMAL);
      if IconCreated then
        DestroyIcon(Ico);
      Inc(R.Left, R.Bottom - R.Top);
    end;
    if (FFormCaption <> '') or ((Captions <> nil) and (Captions.Count > 0)) then
    begin
      SumWidth := 2;
      SetBkMode(Image.Canvas.Handle, TRANSPARENT);
      Inc(R.Left, 2);
      if FHideDirection = hdLeftToRight then
      begin
        for I := 0 to Captions.Count - 1 do
          if Captions[I].Visible then
            SumWidth := SumWidth + Captions[I].TextWidth;
        SumWidth := SumWidth + TextWidth;
        J := 0;
        while (SumWidth > (R.Right - R.Left)) and (J < Captions.Count) do
        begin
          SumWidth := SumWidth - Captions[J].TextWidth;
          while (J < Captions.Count - 1) and Captions[J].GlueNext do
          begin
            SumWidth := SumWidth - Captions[J + 1].TextWidth;
            Inc(J);
          end;
          Inc(J);
        end;
        for I := J to Captions.Count do
        begin
          if I < Captions.Count then
          begin
            if Captions[I].Visible then
            begin
              S := Captions[I].Caption;
              SetCaptionFont(I);
            end
            else
              S := '';
          end
          else
          begin
            S := FFormCaption;
            SetCaptionFont(-1);
          end;
          DrawStr(I = Captions.Count, False, -1);
        end;
      end
      else
      begin
        DrawNext := True;
        J := 0;
        if Captions <> nil then
        begin
          while (SumWidth < (R.Right - R.Left)) and (J < Captions.Count) do
          begin
            if Captions[J].Visible then
            begin
              SumWidth := SumWidth + Captions[J].TextWidth;
              while Captions[J].GlueNext and (J < Captions.Count - 1) do
              begin
                SumWidth := SumWidth + Captions[J + 1].TextWidth;
                Inc(J);
              end;
            end;
            Inc(J);
          end;
          for I := 0 to J - 1 do
          begin
            if Captions[I].Visible and DrawNext then
            begin
              S := Captions[I].Caption;
              if S <> '' then
              begin
                SetCaptionFont(I);
                DrawNext := DrawStr(((I > 0) and Captions[I - 1].GlueNext) or
                  (I = 0), Captions[I].GlueNext, I - 1) and
                  (Captions[I].GlueNext or (R.Right > R.Left));
              end;
            end;
          end;
        end;
        if (R.Right > R.Left) and DrawNext and (FFormCaption <> '') then
        begin
          S := FFormCaption;
          SetCaptionFont(-1);
          DrawStr(False, False, -1);
        end;
      end;
    end;
    BitBlt(DC, DrawRect.Left, DrawRect.Top, Image.Width, Image.Height,
      Image.Canvas.Handle, 0, 0, SRCCOPY);
  finally
    Image.Free;
  end;
end;

procedure TJvGradientCaption.SetFont(Value: TFont);
begin
  FFont.Assign(Value);
end;

procedure TJvGradientCaption.FontChanged(Sender: TObject);
var
  I: Integer;
begin
  FDefaultFont := False;
  if Captions <> nil then
  begin
    Captions.BeginUpdate;
    try
      for I := 0 to Captions.Count - 1 do
        if Captions[I].ParentFont then
          Captions[I].SetParentFont(True);
    finally
      Captions.EndUpdate;
    end;
  end
  else
  if Active then
    Update;
end;

function TJvGradientCaption.GetTextWidth: Integer;
begin
  Result := InternalGetTextWidth(Font, FormCaption);
end;

procedure TJvGradientCaption.SetGradientSteps(Value: Integer);
begin
  if FGradientSteps <> Value then
  begin
    FGradientSteps := Value mod 256; // auto resets to 0 at 256
    if Active and ((FWindowActive and GradientActive) or
      (not FWindowActive and GradientInactive)) then
      Update;
  end;
end;

procedure TJvGradientCaption.SetGradientActive(Value: Boolean);
begin
  if FGradientActive <> Value then
  begin
    FGradientActive := Value;
    if Active and FWindowActive then
      Update;
  end;
end;

procedure TJvGradientCaption.SetGradientInactive(Value: Boolean);
begin
  if FGradientInactive <> Value then
  begin
    FGradientInactive := Value;
    if Active and not FWindowActive then
      Update;
  end;
end;

procedure TJvGradientCaption.SetFontInactiveColor(Value: TColor);
begin
  if FFontInactiveColor <> Value then
  begin
    FFontInactiveColor := Value;
    if Active and not FWindowActive then
      Update;
  end;
end;

procedure TJvGradientCaption.SetHideDirection(Value: THideDirection);
begin
  if FHideDirection <> Value then
  begin
    FHideDirection := Value;
    if Active then
      Update;
  end;
end;

procedure TJvGradientCaption.SetFillDirection(Value: TFillDirection);
begin
  if FFillDirection <> Value then
  begin
    FFillDirection := Value;
    if Active then
      Update;
  end;
end;

{$IFDEF UNITVERSIONING}

initialization
  RegisterUnitVersion(HInstance, UnitVersioning);

finalization
  UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}

end.
