  {-----------------------------------------------------------------------------
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: JvDesktopAlert.PAS, released on 2004-03-23.

The Initial Developer of the Original Code is Peter Thornqvist <peter3 at sourceforge dot net>
Portions created by Peter Thornqvist are Copyright (C) 2004 Peter Thornqvist.
All Rights Reserved.

Contributor(s):
Hans-Eric Grnlund (stack logic)
Olivier Sannier (animation styles logic)

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

Known Issues:
-----------------------------------------------------------------------------}
// $Id: JvDesktopAlert.pas 10691 2006-06-09 14:12:23Z ahuser $

unit JvDesktopAlert;

{$I jvcl.inc}

interface

uses
  {$IFDEF UNITVERSIONING}
  JclUnitVersioning,
  {$ENDIF UNITVERSIONING}
  Windows, Classes, Controls, Graphics, Forms, ExtCtrls, Menus, ImgList,
  JvComponentBase, JvBaseDlg, JvDesktopAlertForm;

const
  JvDefaultFrameColor = TColor($00943000);
  JvDefaultWindowFromColor = TColor($00FFE7CE);
  JvDefaultWindowToColor = TColor($00E7A67B);
  JvDefaultCaptionFromColor = TColor($00D68652);
  JvDefaultCaptionToColor = TColor($00944110);

type
  // The possible animation styles as an enumeration
  TJvAlertStyle = (asFade, asCenterGrow);

  // The different status a style handler can have
  TJvStyleHandlerStatus = (hsIdle, hsStartAnim, hsEndAnim, hsDisplay);

  TJvCustomDesktopAlertStyleHandler = class;

  TJvDesktopAlertStack = class;
  TJvDesktopAlert = class;
  TJvDesktopAlertChangePersistent = class(TPersistent)
  private
    FOnChange: TNotifyEvent;
  protected
    procedure Change;
  public
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

  TJvDesktopAlertColors = class(TJvDesktopAlertChangePersistent)
  private
    FWindowFrom: TColor;
    FCaptionTo: TColor;
    FWindowTo: TColor;
    FFrame: TColor;
    FCaptionFrom: TColor;
    procedure SetCaptionFrom(const Value: TColor);
    procedure SetCaptionTo(const Value: TColor);
    procedure SetFrame(const Value: TColor);
    procedure SetWindowFrom(const Value: TColor);
    procedure SetWindowTo(const Value: TColor);
  public
    constructor Create;
    procedure Assign(Source: TPersistent); override;
  published
    property Frame: TColor read FFrame write SetFrame default JvDefaultFrameColor;
    property WindowFrom: TColor read FWindowFrom write SetWindowFrom default JvDefaultWindowFromColor;
    property WindowTo: TColor read FWindowTo write SetWindowTo default JvDefaultWindowToColor;
    property CaptionFrom: TColor read FCaptionFrom write SetCaptionFrom default JvDefaultCaptionFromColor;
    property CaptionTo: TColor read FCaptionTo write SetCaptionTo default JvDefaultCaptionToColor;
  end;

  TJvDesktopAlertPosition =
   (dapTopLeft, dapTopRight, dapBottomLeft, dapBottomRight, dapCustom,
    dapDesktopCenter, dapMainFormCenter, dapOwnerFormCenter, dapActiveFormCenter,
    dapMainFormTopLeft, dapMainFormTopRight, dapMainFormBottomLeft, dapMainFormBottomRight,
    dapOwnerFormTopLeft, dapOwnerFormTopRight, dapOwnerFormBottomLeft, dapOwnerFormBottomRight,
    dapActiveFormTopLeft, dapActiveFormTopRight, dapActiveFormBottomLeft, dapActiveFormBottomRight);

  TJvDesktopAlertLocation = class(TJvDesktopAlertChangePersistent)
  private
    FTop: Integer;
    FLeft: Integer;
    FPosition: TJvDesktopAlertPosition;
    FAlwaysResetPosition: Boolean;
    FHeight: Integer;
    FWidth: Integer;
    procedure SetTop(const Value: Integer);
    procedure SetLeft(const Value: Integer);
    procedure SetPosition(const Value: TJvDesktopAlertPosition);
    procedure SetHeight(const Value: Integer);
    procedure SetWidth(const Value: Integer);
  public
    constructor Create;
  published
    property Position: TJvDesktopAlertPosition read FPosition write SetPosition default dapBottomRight;
    property Top: Integer read FTop write SetTop;
    property Left: Integer read FLeft write SetLeft;
    property Width: Integer read FWidth write SetWidth;
    property Height: Integer read FHeight write SetHeight;
    property AlwaysResetPosition: Boolean read FAlwaysResetPosition write FAlwaysResetPosition default True;
  end;

  TJvDesktopAlertButtonItem = class(TCollectionItem)
  private
    FImageIndex: Integer;
    FOnClick: TNotifyEvent;
    FTag: Integer;
  public
    procedure Assign(Source: TPersistent); override;
  published
    property ImageIndex: Integer read FImageIndex write FImageIndex;
    property Tag: Integer read FTag write FTag;
    property OnClick: TNotifyEvent read FOnClick write FOnClick;
  end;

  TJvDesktopAlertButtons = class(TOwnedCollection)
  private
    function GetItem(Index: Integer): TJvDesktopAlertButtonItem;
    procedure SetItem(Index: Integer; const Value: TJvDesktopAlertButtonItem);
  public
    constructor Create(AOwner: TPersistent);
    function Add: TJvDesktopAlertButtonItem;
    property Items[Index: Integer]: TJvDesktopAlertButtonItem read GetItem write SetItem; default;
    procedure Assign(Source: TPersistent); override;
  end;

  TJvDesktopAlertOption = (daoCanClick, daoCanMove, daoCanMoveAnywhere, daoCanClose);
  TJvDesktopAlertOptions = set of TJvDesktopAlertOption;

  TJvDesktopAlert = class(TJvCommonDialogP)
  private
    FStacker: TJvDesktopAlertStack;
    FImages: TCustomImageList;
    FButtons: TJvDesktopAlertButtons;
    FColors: TJvDesktopAlertColors;
    FLocation: TJvDesktopAlertLocation;
    FOptions: TJvDesktopAlertOptions;
    FOnClose: TNotifyEvent;
    FOnMouseEnter: TNotifyEvent;
    FOnMessageClick: TNotifyEvent;
    FOnShow: TNotifyEvent;
    FOnMouseLeave: TNotifyEvent;
    FData: TObject;
    FAutoFocus: Boolean;
    FAutoFree: Boolean;
    FAlertStyle: TJvAlertStyle;
    FStyleHandler: TJvCustomDesktopAlertStyleHandler;
    FOnShown: TNotifyEvent;
    FOnShowing: TNotifyEvent;
    function GetStacker: TJvDesktopAlertStack;
    procedure SetButtons(const Value: TJvDesktopAlertButtons);
    procedure SetColors(const Value: TJvDesktopAlertColors);
    procedure SetDropDownMenu(const Value: TPopupMenu);
    procedure SetFont(const Value: TFont);
    procedure SetHeaderFont(const Value: TFont);
    procedure SetImage(const Value: TPicture);
    procedure SetImages(const Value: TCustomImageList);
    procedure SetPopupMenu(const Value: TPopupMenu);
    procedure InternalOnShowing(Sender: TObject);
    procedure InternalOnShow(Sender: TObject);
    procedure InternalOnShown(Sender: TObject);
    procedure InternalOnClose(Sender: TObject; var Action: TCloseAction);
    procedure InternalMouseEnter(Sender: TObject);
    procedure InternalMouseLeave(Sender: TObject);
    procedure InternalMessageClick(Sender: TObject);
    procedure InternalOnMove(Sender: TObject);
    function GetAlertStack: TJvDesktopAlertStack;
    procedure SetAlertStack(const Value: TJvDesktopAlertStack);
    function GetFont: TFont;
    function GetHeaderFont: TFont;
    function GetImage: TPicture;
    function GetDropDownMenu: TPopupMenu;
    function GetHeaderText: string;
    function GetMessageText: string;
    function GetPopupMenu: TPopupMenu;
    procedure SetHeaderText(const Value: string);
    procedure SetLocation(const Value: TJvDesktopAlertLocation);
    procedure SetMessageText(const Value: string);
    procedure DoLocationChange(Sender: TObject);
    function GetParentFont: Boolean;
    function GetShowHint: Boolean;
    function GetHint: string;
    procedure SetHint(const Value: string);
    procedure SetParentFont(const Value: Boolean);
    procedure SetShowHint(const Value: Boolean);
    procedure SetOptions(const Value: TJvDesktopAlertOptions);
    function GetCloseButtonClick: TNotifyEvent;
    procedure SetCloseButtonClick(const Value: TNotifyEvent);
    procedure SetAlertStyle(const Value: TJvAlertStyle);
    procedure SetStyleHandler(const Value: TJvCustomDesktopAlertStyleHandler);
    function GetBiDiMode: TBidiMode;
    procedure SetBiDiMode(const Value: TBidiMode);
  protected
    FFormButtons: array of TControl;
    FDesktopForm: TJvFormDesktopAlert;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function Showing: Boolean;
    procedure Close(Immediate: Boolean);
    function Execute: Boolean; override;
    property Form: TJvFormDesktopAlert read FDesktopForm;
    property Data: TObject read FData write FData;
    property StyleHandler: TJvCustomDesktopAlertStyleHandler read FStyleHandler write SetStyleHandler;
  published
    property AlertStack: TJvDesktopAlertStack read GetAlertStack write SetAlertStack;
    property AlertStyle: TJvAlertStyle read FAlertStyle write SetAlertStyle default asFade;
    property AutoFocus: Boolean read FAutoFocus write FAutoFocus default False;
    property AutoFree: Boolean read FAutoFree write FAutoFree default False;
    property BiDiMode: TBidiMode read GetBiDiMode write SetBiDiMode default bdLeftToRight;
    property HeaderText: string read GetHeaderText write SetHeaderText;
    property MessageText: string read GetMessageText write SetMessageText;

    property HeaderFont: TFont read GetHeaderFont write SetHeaderFont;
    property Hint: string read GetHint write SetHint;
    property ShowHint: Boolean read GetShowHint write SetShowHint;
    property Font: TFont read GetFont write SetFont;
    property ParentFont: Boolean read GetParentFont write SetParentFont;
    property Options: TJvDesktopAlertOptions read FOptions write SetOptions default [daoCanClick..daoCanClose];
    property Colors: TJvDesktopAlertColors read FColors write SetColors;
    property Buttons: TJvDesktopAlertButtons read FButtons write SetButtons;
    property Location: TJvDesktopAlertLocation read FLocation write SetLocation;
    property Image: TPicture read GetImage write SetImage;
    property Images: TCustomImageList read FImages write SetImages;
    property DropDownMenu: TPopupMenu read GetDropDownMenu write SetDropDownMenu;
    property PopupMenu: TPopupMenu read GetPopupMenu write SetPopupMenu;

    // This property is equivalent to StyleHandler, it is just renamed to look better in the inspector
    property StyleOptions: TJvCustomDesktopAlertStyleHandler read FStyleHandler write SetStyleHandler;

    property OnShowing: TNotifyEvent read FOnShowing write FOnShowing;
    property OnShow: TNotifyEvent read FOnShow write FOnShow;
    property OnShown: TNotifyEvent read FOnShown write FOnShown;
    property OnCloseButtonClick: TNotifyEvent read GetCloseButtonClick write SetCloseButtonClick;
    property OnClose: TNotifyEvent read FOnClose write FOnClose;
    property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter;
    property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
    property OnMessageClick: TNotifyEvent read FOnMessageClick write FOnMessageClick;
  end;

  TJvDesktopAlertStack = class(TJvComponent)
  private
    FItems: TList;
    FPosition: TJvDesktopAlertPosition;
    function GetCount: Integer;
    function GetItems(Index: Integer): TJvFormDesktopAlert;
    procedure SetPosition(const Value: TJvDesktopAlertPosition);
  protected
    procedure UpdatePositions; virtual;
  public
    procedure Add(AForm: TCustomForm); virtual;
    procedure Remove(AForm: TCustomForm); virtual;

    property Items[Index: Integer]: TJvFormDesktopAlert read GetItems;
    property Count: Integer read GetCount;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    // all forms must have the same position property
    property Position: TJvDesktopAlertPosition read FPosition write SetPosition default dapBottomRight;
  end;

  // Common ancestor of all the alert styles for a TJvFormDesktopAlert
  TJvCustomDesktopAlertStyleHandler = class(TPersistent)
  private
    FAnimTimer: TTimer;
    FOwnerForm: TJvFormDesktopAlert;
    FStartSteps: Cardinal;
    FEndSteps: Cardinal;
    FEndInterval: Cardinal;
    FStartInterval: Cardinal;
    FDisplayDuration: Cardinal;
    FCurrentStep: Cardinal;
    FStatus: TJvStyleHandlerStatus;
    procedure SetDisplayDuration(const Value: Cardinal);
    procedure SetOwnerForm(const Value: TJvFormDesktopAlert);
    function GetActive: Boolean;
  protected
    procedure SetEndInterval(const Value: Cardinal); virtual;
    procedure SetEndSteps(const Value: Cardinal); virtual;
    procedure SetStartInterval(const Value: Cardinal); virtual;
    procedure SetStartSteps(const Value: Cardinal); virtual;
    // This procedure will be called for each step of the starting animation
    // It will be called StartSteps time, every StartInterval milliseconds
    // The implementation here only ensures that once the number of steps
    // is reached, the timer is stopped
    procedure StartAnimTimer(Sender: TObject); virtual;
    // This procedure will be called for each step of the ending animation
    // It will be called EndSteps time, every EndInterval milliseconds
    // The implementation here only ensures that once the number of steps
    // is reached, the timer is stopped and then calls DoDisplay
    procedure EndAnimTimer(Sender: TObject); virtual;
    // This procedure will be called once after DisplayDuration
    // (if it is > 0) when the start animation is finished.
    procedure DisplayTimer(Sender: TObject); virtual;
    // This procedure is called just before the start animation timer
    // is enabled. Use it to setup initial values required for the
    // animation
    // As implemented in this base class, the owner form is shown
    procedure PrepareStartAnimation; virtual;
    // This procedure is called just after the start animation has finished
    // Use it to set the final values of the animation
    procedure FinalizeStartAnimation; virtual; abstract;
    // This procedure is called just before the end animation timer
    // is enabled. Use it to setup initial values required for the
    // animation
    procedure PrepareEndAnimation; virtual; abstract;
    // This procedure is called just after the end animation has finished
    // Use it to set the final values of the animation
    // As implemented in this base class, this closes the owner form.
    // Note: It is required to close the form or the end animation
    // will keep being repeated
    procedure FinalizeEndAnimation; virtual;
    // The timer used for all animations and waits
    property AnimTimer: TTimer read FAnimTimer;
  public
    constructor Create(OwnerForm: TJvFormDesktopAlert); virtual;
    destructor Destroy; override;
    // Sets up the timer to call StartAnimTimer on the correct interval
    // then show the owner form.
    // If StartSteps is not greater than 0, the animation will not start
    // and the form will not be shown.
    procedure DoStartAnimation; virtual;
    // Sets up the timer to call EndAnimTimer on the correct interval
    // If EndSteps is not greater than 0, the animation will not start
    procedure DoEndAnimation; virtual;
    // Sets up the timer to call DisplayTimer after the correct delay
    // If DisplayDuration is equal to 0, the timer is not enabled and
    // DisplayTimer will never be called
    procedure DoDisplay; virtual;
    // Aborts the current animation, if any. Will call the proper Finalize
    // function as applicable. The middle wait is NOT aborted by a call
    // to this function
    procedure AbortAnimation; virtual;
    // The owner form, the form to which the style is associated.
    // This value MUST NOT be nil when any of the DoXXXX function is called
    property OwnerForm: TJvFormDesktopAlert read FOwnerForm write SetOwnerForm;
    // The current step in the animation (starts at 0, use Active to know
    // if an animation or wait is in progress).
    property CurrentStep: Cardinal read FCurrentStep;
    // Returns AnimTimer.Enabled
    property Active: Boolean read GetActive;
    // Returns the status of the handler
    property Status: TJvStyleHandlerStatus read FStatus;
  published
    // The duration between each step of the start animation
    property StartInterval: Cardinal read FStartInterval write SetStartInterval;
    // The number of steps in the start animation
    property StartSteps: Cardinal read FStartSteps write SetStartSteps;
    // The duration between each step of the end animation
    property EndInterval: Cardinal read FEndInterval write SetEndInterval;
    // The number of steps in the end animation
    property EndSteps: Cardinal read FEndSteps write SetEndSteps;
    // The duration of the middle wait (between the end of the start
    // animation and the beginning of the end animation)
    property DisplayDuration: Cardinal read FDisplayDuration write SetDisplayDuration;
  end;

  // This style will make the form fade in and fade out.
  // NOTE: This is only supported by Delphi or C++ Builder 6 and above
  // NOTE: Even if the compiler supports it, this only works if the
  //       operating system is Windows 2000 or Windows XP
  TJvFadeAlertStyleHandler = class (TJvCustomDesktopAlertStyleHandler)
  private
    FMinAlphaBlendValue: Byte;
    FCurrentAlphaBlendValue: Byte;
    FMaxAlphaBlendValue: Byte;
    procedure SetMinAlphaBlendValue(const Value: Byte);
    procedure SetMaxAlphaBlendValue(const Value: Byte);
  protected
    procedure StartAnimTimer(Sender: TObject); override;
    procedure EndAnimTimer(Sender: TObject); override;
    // Applies the current alpha blend value to the owner form
    procedure DoAlphaBlend(Value: Byte);
    procedure PrepareStartAnimation; override;
    procedure FinalizeStartAnimation; override;
    procedure PrepareEndAnimation; override;
    procedure FinalizeEndAnimation; override;
  public
    constructor Create(OwnerForm: TJvFormDesktopAlert); override;
    procedure AbortAnimation; override;
  published
    property MinAlphaBlendValue: Byte read FMinAlphaBlendValue write SetMinAlphaBlendValue default 0;
    property MaxAlphaBlendValue: Byte read FMaxAlphaBlendValue write SetMaxAlphaBlendValue default 255;
    property CurrentAlphaBlendValue: Byte read FCurrentAlphaBlendValue;
    property StartInterval default 25;
    property StartSteps default 10;
    property EndInterval default 50;
    property EndSteps default 10;
    property DisplayDuration default 1400;
  end;

  TJvCenterGrowAlertStyleHandler = class(TJvCustomDesktopAlertStyleHandler)
  private
    FMaxGrowthPercentage: Double;
    FMinGrowthPercentage: Double;
    procedure SetMaxGrowthPercentage(const Value: Double);
    procedure SetMinGrowthPercentage(const Value: Double);
  protected
    procedure StartAnimTimer(Sender: TObject); override;
    procedure EndAnimTimer(Sender: TObject); override;
    // Applies the current region growth percentage value to the owner form
    procedure DoGrowRegion(Percentage: Double);
    procedure PrepareStartAnimation; override;
    procedure FinalizeStartAnimation; override;
    procedure PrepareEndAnimation; override;
    procedure FinalizeEndAnimation; override;
  public
    constructor Create(OwnerForm: TJvFormDesktopAlert); override;
    procedure AbortAnimation; override;
  published
    property StartInterval default 25;
    property StartSteps default 10;
    property EndInterval default 50;
    property EndSteps default 10;
    property DisplayDuration default 1400;
    property MinGrowthPercentage: Double read FMinGrowthPercentage write SetMinGrowthPercentage;
    property MaxGrowthPercentage: Double read FMaxGrowthPercentage write SetMaxGrowthPercentage;
  end;

function CreateHandlerForStyle(Style: TJvAlertStyle; OwnerForm: TJvFormDesktopAlert): TJvCustomDesktopAlertStyleHandler;

{$IFDEF UNITVERSIONING}
const
  UnitVersioning: TUnitVersionInfo = (
    RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvDesktopAlert.pas $';
    Revision: '$Revision: 10691 $';
    Date: '$Date: 2006-06-09 16:12:23 +0200 (ven., 09 juin 2006) $';
    LogPath: 'JVCL\run'
  );
{$ENDIF UNITVERSIONING}

implementation

uses
  SysUtils, Messages,
  JvJVCLUtils, JvTypes;

var
  GStacker: TJvDesktopAlertStack = nil;

function CreateHandlerForStyle(Style: TJvAlertStyle; OwnerForm: TJvFormDesktopAlert): TJvCustomDesktopAlertStyleHandler;
begin
  case Style of
    asFade:
      Result := TJvFadeAlertStyleHandler.Create(OwnerForm);
    asCenterGrow:
      Result := TJvCenterGrowAlertStyleHandler.Create(OwnerForm);
    else
      raise Exception.Create('');
  end;
end;

function GlobalStacker: TJvDesktopAlertStack;
begin
  if GStacker = nil then
    GStacker := TJvDesktopAlertStack.Create(nil);
  Result := GStacker;
end;

//=== { TJvDesktopAlertChangePersistent } ====================================

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

//=== { TJvDesktopAlertColors } ==============================================

constructor TJvDesktopAlertColors.Create;
begin
  inherited Create;
  FFrame := JvDefaultFrameColor;
  FWindowFrom := JvDefaultWindowFromColor;
  FWindowTo := JvDefaultWindowToColor;
  FCaptionFrom := JvDefaultCaptionFromColor;
  FCaptionTo := JvDefaultCaptionToColor;
end;

procedure TJvDesktopAlertColors.Assign(Source: TPersistent);
begin
  if Source is TJvDesktopAlertColors then
  begin
    if Source <> Self then
    begin
      FFrame := TJvDesktopAlertColors(Source).Frame;
      FWindowFrom := TJvDesktopAlertColors(Source).WindowFrom;
      FWindowTo := TJvDesktopAlertColors(Source).WindowTo;
      FCaptionFrom := TJvDesktopAlertColors(Source).CaptionFrom;
      FCaptionTo := TJvDesktopAlertColors(Source).CaptionTo;
      Change;
    end;
  end
  else
    inherited Assign(Source);
end;

procedure TJvDesktopAlertColors.SetCaptionFrom(const Value: TColor);
begin
  if FCaptionFrom <> Value then
  begin
    FCaptionFrom := Value;
    Change;
  end;
end;

procedure TJvDesktopAlertColors.SetCaptionTo(const Value: TColor);
begin
  if FCaptionTo <> Value then
  begin
    FCaptionTo := Value;
    Change;
  end;
end;

procedure TJvDesktopAlertColors.SetFrame(const Value: TColor);
begin
  if FFrame <> Value then
  begin
    FFrame := Value;
    Change;
  end;
end;

procedure TJvDesktopAlertColors.SetWindowFrom(const Value: TColor);
begin
  if FWindowFrom <> Value then
  begin
    FWindowFrom := Value;
    Change;
  end;
end;

procedure TJvDesktopAlertColors.SetWindowTo(const Value: TColor);
begin
  if FWindowTo <> Value then
  begin
    FWindowTo := Value;
    Change;
  end;
end;

//=== { TJvDesktopAlertLocation } ============================================

constructor TJvDesktopAlertLocation.Create;
begin
  inherited Create;
  FPosition := dapBottomRight;
  FAlwaysResetPosition := True;
end;

procedure TJvDesktopAlertLocation.SetHeight(const Value: Integer);
begin
  if FHeight <> Value then
  begin
    FHeight := Value;
    Change;
  end;
end;

procedure TJvDesktopAlertLocation.SetLeft(const Value: Integer);
begin
  if FLeft <> Value then
  begin
    FLeft := Value;
    Change;
  end;
end;

procedure TJvDesktopAlertLocation.SetPosition(const Value: TJvDesktopAlertPosition);
begin
//  if FPosition <> Value then
  begin
    FPosition := Value;
    Change;
  end;
end;

procedure TJvDesktopAlertLocation.SetTop(const Value: Integer);
begin
  if FTop <> Value then
  begin
    FTop := Value;
    Change;
  end;
end;

procedure TJvDesktopAlertLocation.SetWidth(const Value: Integer);
begin
  if FWidth <> Value then
  begin
    FWidth := Value;
    Change;
  end;
end;

//=== { TJvDesktopAlertButtonItem } ==========================================

procedure TJvDesktopAlertButtonItem.Assign(Source: TPersistent);
begin
  if Source is TJvDesktopAlertButtonItem then
  begin
    if Source <> Self then
    begin
      ImageIndex := TJvDesktopAlertButtonItem(Source).ImageIndex;
      OnClick := TJvDesktopAlertButtonItem(Source).OnClick;
      Tag := TJvDesktopAlertButtonItem(Source).Tag;
    end;
  end
  else
    inherited Assign(Source);
end;

//=== { TJvDesktopAlertButtons } =============================================

constructor TJvDesktopAlertButtons.Create(AOwner: TPersistent);
begin
  inherited Create(AOwner, TJvDesktopAlertButtonItem);
end;

function TJvDesktopAlertButtons.Add: TJvDesktopAlertButtonItem;
begin
  Result := TJvDesktopAlertButtonItem(inherited Add);
end;

procedure TJvDesktopAlertButtons.Assign(Source: TPersistent);
var
  I: Integer;
begin
  if Source is TJvDesktopAlertButtons then
  begin
    if Source <> Self then
    begin
      Clear;
      for I := 0 to TJvDesktopAlertButtons(Source).Count - 1 do
        Add.Assign(TJvDesktopAlertButtons(Source)[I]);
    end;
  end
  else
    inherited Assign(Source);
end;

function TJvDesktopAlertButtons.GetItem(Index: Integer): TJvDesktopAlertButtonItem;
begin
  Result := TJvDesktopAlertButtonItem(inherited Items[Index]);
end;

procedure TJvDesktopAlertButtons.SetItem(Index: Integer; const Value: TJvDesktopAlertButtonItem);
begin
  inherited Items[Index] := Value;
end;


//=== { TJvDesktopAlert } ====================================================

constructor TJvDesktopAlert.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FColors := TJvDesktopAlertColors.Create;
  FButtons := TJvDesktopAlertButtons.Create(Self);
  FLocation := TJvDesktopAlertLocation.Create;
  FLocation.OnChange := DoLocationChange;
  FDesktopForm := TJvFormDesktopAlert.Create(Self);
  AlertStyle := asFade;
  FOptions := [daoCanClick..daoCanClose];
end;

destructor TJvDesktopAlert.Destroy;
begin
  // when AutoFreeing, Delphi doesn't like the component having an owner, so remove the Owner here
  if FAutoFree and (Owner <> nil) and not (csDesigning in ComponentState) then
    Owner.RemoveComponent(Self);
  if (FDesktopForm <> nil) then
  begin
    if FDesktopForm.Showing then
      FDesktopForm.Close;
    FDesktopForm.OnClose := nil;
    GetStacker.Remove(FDesktopForm);
    FDesktopForm.Release;
    FDesktopForm := nil;
  end;
  FreeAndNil(FColors);
  FreeAndNil(FButtons);
  FreeAndNil(FLocation);
  FreeAndNil(FStyleHandler);
  inherited Destroy;
end;

procedure TJvDesktopAlert.Close(Immediate: Boolean);
begin
  if Showing then
  begin
    if Immediate then
      FDesktopForm.Close
    else
      FStyleHandler.DoEndAnimation;
  end;
end;

procedure TJvDesktopAlert.DoLocationChange(Sender: TObject);
begin
  if GetStacker.Position <> Location.Position then
  begin
    if GetStacker = GlobalStacker then
      GetStacker.Position := Location.Position
    else
      Location.Position := GetStacker.Position;
  end;
end;

function TJvDesktopAlert.Execute: Boolean;
var
  ARect: TRect;
  I, X, Y: Integer;
  FActiveWindow, FActiveFocus: HWND;
  Position: TJvDesktopAlertPosition;

  procedure CenterForm(AForm: TCustomForm; ARect: TRect);
  begin
    AForm.Top := ARect.Top + ((ARect.Bottom - ARect.Top) - AForm.Height) div 2;
    AForm.Left := ARect.Left + ((ARect.Right - ARect.Left) - AForm.Width) div 2;
  end;

begin
  Assert(FDesktopForm <> nil);
  if FDesktopForm.Visible then
    FDesktopForm.Close;

  ARect := ScreenWorkArea;
  if (Application <> nil) and (Application.MainForm <> nil) and
     (Location.Position in [dapMainFormTopLeft, dapMainFormTopRight, dapMainFormBottomLeft, dapMainFormBottomRight]) then
    ARect := Application.MainForm.BoundsRect
  else
  if (Screen.ActiveForm <> nil) and
     (Location.Position in [dapActiveFormTopLeft, dapActiveFormTopRight, dapActiveFormBottomLeft, dapActiveFormBottomRight]) then
    ARect := Screen.ActiveForm.BoundsRect
  else
  if (Owner is TCustomForm) and
     (Location.Position in [dapOwnerFormTopLeft, dapOwnerFormTopRight, dapOwnerFormBottomLeft, dapOwnerFormBottomRight]) then
    ARect := TCustomForm(Owner).BoundsRect;

  Position := Location.Position;
  case Position of
    dapMainFormTopLeft, dapActiveFormTopLeft, dapOwnerFormTopLeft:
      Position := dapTopLeft;
    dapMainFormTopRight, dapActiveFormTopRight, dapOwnerFormTopRight:
      Position := dapTopRight;
    dapMainFormBottomLeft, dapActiveFormBottomLeft, dapOwnerFormBottomLeft:
      Position := dapBottomLeft;
    dapMainFormBottomRight, dapActiveFormBottomRight, dapOwnerFormBottomRight:
      Position := dapBottomRight;
  end;

  if Location.Width <> 0 then
    FDesktopForm.Width := Location.Width
  else
    FDesktopForm.Width := cDefaultAlertFormWidth;
  if Location.Height <> 0 then
    FDesktopForm.Height := Location.Height
  else
    FDesktopForm.Height := cDefaultAlertFormHeight;
  case Position of
    dapTopLeft:
      begin
        FDesktopForm.Top := ARect.Top;
        FDesktopForm.Left := ARect.Left;
      end;
    dapTopRight:
      begin
        FDesktopForm.Top := ARect.Top;
        FDesktopForm.Left := ARect.Right - FDesktopForm.Width;
      end;
    dapBottomLeft:
      begin
        FDesktopForm.Top := ARect.Bottom - FDesktopForm.Height;
        FDesktopForm.Left := ARect.Left;
      end;
    dapBottomRight:
      begin
        FDesktopForm.Top := ARect.Bottom - FDesktopForm.Height;
        FDesktopForm.Left := ARect.Right - FDesktopForm.Width;
      end;
    dapCustom:
      begin
        FDesktopForm.Top := Location.Top;
        FDesktopForm.Left := Location.Left;
      end;
    dapDesktopCenter, dapMainFormCenter, dapOwnerFormCenter, dapActiveFormCenter:
      begin
        CenterForm(FDesktopForm, ARect);
        if (Location.Position = dapActiveFormCenter) and (Screen.ActiveForm <> nil) then
          CenterForm(FDesktopForm, Screen.ActiveForm.BoundsRect)
        else
        if (Location.Position = dapMainFormCenter) and (Application <> nil) and (Application.MainForm <> nil) then
          CenterForm(FDesktopForm, Application.MainForm.BoundsRect)
        else
        if (Location.Position = dapOwnerFormCenter) and (Owner is TCustomForm) then
          CenterForm(FDesktopForm, TCustomForm(Owner).BoundsRect);
      end;
  end;

  FDesktopForm.OnShowing := InternalOnShowing;
  FDesktopForm.OnShow    := InternalOnShow;
  FDesktopForm.OnShown   := InternalOnShown;
  FDesktopForm.OnClose := InternalOnClose;
  FDesktopForm.OnMouseEnter := InternalMouseEnter;
  FDesktopForm.OnMouseLeave := InternalMouseLeave;
  FDesktopForm.OnUserMove := InternalOnMove;
  FDesktopForm.lblText.OnClick := InternalMessageClick;
  FDesktopForm.Moveable := (daoCanMove in Options);
  FDesktopForm.MoveAnywhere := (daoCanMoveAnywhere in Options);
  FDesktopForm.Closeable := (daoCanClose in Options);
  FDesktopForm.ClickableMessage := daoCanClick in Options;
  if not Assigned(FDesktopForm.tbClose.OnClick) then
    FDesktopForm.tbClose.OnClick := FDesktopForm.acCloseExecute;

  FDesktopForm.tbDropDown.DropDownMenu := DropDownMenu;
  FDesktopForm.imIcon.Picture := Image;

  FDesktopForm.Font := Font;
  FDesktopForm.lblHeader.Caption := HeaderText;
  FDesktopForm.lblHeader.Font := HeaderFont;
  FDesktopForm.lblText.Caption := MessageText;
  FDesktopForm.WindowColorFrom := Colors.WindowFrom;
  FDesktopForm.WindowColorTo := Colors.WindowTo;
  FDesktopForm.CaptionColorFrom := Colors.CaptionFrom;
  FDesktopForm.CaptionColorTo := Colors.CaptionTo;
  FDesktopForm.FrameColor := Colors.Frame;

  for I := 0 to Length(FFormButtons) - 1 do
    FFormButtons[I].Free;
  SetLength(FFormButtons, Buttons.Count);
  X := 2;
  Y := FDesktopForm.Height - 23;
  for I := 0 to Length(FFormButtons) - 1 do
  begin
    FFormButtons[I] := TJvDesktopAlertButton.Create(FDesktopForm);
    with TJvDesktopAlertButton(FFormButtons[I]) do
    begin
      SetBounds(X, Y, 21, 21);
      ToolType := abtImage;
      Images := Self.Images;
      ImageIndex := Buttons[I].ImageIndex;
      Tag := Buttons[I].Tag;
      InternalClick := Buttons[I].OnClick;
      OnClick := FDesktopForm.DoButtonClick;
      Parent := FDesktopForm;
      Inc(X, 22);
    end;
  end;
  Location.Position := GetStacker.Position;
  if not AutoFocus then
  begin
    FActiveFocus := GetFocus;
    FActiveWindow := GetActiveWindow;
  end
  else
  begin
    FActiveWindow := NullHandle;
    FActiveFocus := NullHandle;
  end;
  FDesktopForm.AllowFocus := AutoFocus;
  FDesktopForm.ShowNoActivate;
  Result := True;
  if not AutoFocus and (FActiveFocus <> GetFocus) then
  begin
    if (FActiveFocus <> NullHandle) then
      SetFocus(FActiveFocus)
    else
    if (FActiveWindow <> NullHandle) then
      SetActiveWindow(FActiveWindow);
  end;
  GetStacker.Add(FDesktopForm);
end;

function TJvDesktopAlert.GetAlertStack: TJvDesktopAlertStack;
begin
  if FStacker = GlobalStacker then
    Result := nil
  else
    Result := FStacker;
end;

function TJvDesktopAlert.GetDropDownMenu: TPopupMenu;
begin
  Result := FDesktopForm.tbDropDown.DropDownMenu;
end;

function TJvDesktopAlert.GetFont: TFont;
begin
  Result := FDesktopForm.lblText.Font;
end;

function TJvDesktopAlert.GetHeaderFont: TFont;
begin
  Result := FDesktopForm.lblHeader.Font;
end;

function TJvDesktopAlert.GetHeaderText: string;
begin
  Result := FDesktopForm.lblHeader.Caption;
end;

function TJvDesktopAlert.GetImage: TPicture;
begin
  Result := FDesktopForm.imIcon.Picture;
end;

function TJvDesktopAlert.GetMessageText: string;
begin
  Result := FDesktopForm.lblText.Caption;
end;

function TJvDesktopAlert.GetParentFont: Boolean;
begin
  Result := FDesktopForm.ParentFont;
end;

function TJvDesktopAlert.GetPopupMenu: TPopupMenu;
begin
  Result := FDesktopForm.PopupMenu;
end;

function TJvDesktopAlert.GetShowHint: Boolean;
begin
  Result := FDesktopForm.ShowHint;
end;

function TJvDesktopAlert.GetStacker: TJvDesktopAlertStack;
begin
  if FStacker = nil then
    Result := GlobalStacker
  else
    Result := FStacker;
end;

function TJvDesktopAlert.GetHint: string;
begin
  Result := FDesktopForm.Hint;
end;

procedure TJvDesktopAlert.InternalMessageClick(Sender: TObject);
var
  FEndInterval:Cardinal;
begin
  if Assigned(FOnMessageClick) and (daoCanClick in Options) then
  begin
    FEndInterval := StyleHandler.EndInterval;
    try
      StyleHandler.EndInterval := 0;
      FOnMessageClick(Self);  // (p3) should this be Sender instead?
    finally
      StyleHandler.EndInterval := FEndInterval;
    end;
    if not Form.MouseInControl then
      StyleHandler.DoEndAnimation;
  end;
end;

procedure TJvDesktopAlert.InternalMouseEnter(Sender: TObject);
begin
  if Assigned(FOnMouseEnter) then
    FOnMouseEnter(Self);
end;

procedure TJvDesktopAlert.InternalMouseLeave(Sender: TObject);
begin
  if Assigned(FOnMouseLeave) then
    FOnMouseLeave(Self);
end;

procedure TJvDesktopAlert.InternalOnClose(Sender: TObject;
  var Action: TCloseAction);
begin
  if (csDestroying in ComponentState) then
    Exit;
  if Location.Position = dapCustom then
  begin
    Location.Top := FDesktopForm.Top;
    Location.Left := FDesktopForm.Left;
  end;
  if Assigned(FOnClose) then
    FOnClose(Self);
  GetStacker.Remove(FDesktopForm);
  if AutoFree and (FDesktopForm <> nil) and not (csDesigning in ComponentState) then
  begin
    FDesktopForm.OnClose := nil;
    // post a message to the form so we have time to finish off all event handlers and
    // timers before the form and component are freed
    PostMessage(FDesktopForm.Handle, JVDESKTOPALERT_AUTOFREE, WPARAM(FDesktopForm), LPARAM(Self));
    FDesktopForm := nil;
  end;
end;

procedure TJvDesktopAlert.InternalOnMove(Sender: TObject);
begin
  if not (csDesigning in ComponentState) and not Location.AlwaysResetPosition and
    (Location.Position <> dapCustom) then
  begin
    GetStacker.Remove(FDesktopForm);
    Location.Position := dapCustom;
  end;
end;

procedure TJvDesktopAlert.InternalOnShow(Sender: TObject);
begin
  if Assigned(FOnShow) then
    FOnShow(Self);
end;

procedure TJvDesktopAlert.InternalOnShowing(Sender: TObject);
begin
  if Assigned(FOnShowing) then
    FOnShowing(Self);
end;

procedure TJvDesktopAlert.InternalOnShown(Sender: TObject);
begin
  if Assigned(FOnShown) then
    FOnShown(Self);
end;

procedure TJvDesktopAlert.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if Operation = opRemove then
  begin
    if AComponent = FStacker then
      AlertStack := nil;
  end;
end;

procedure TJvDesktopAlert.SetAlertStack(const Value: TJvDesktopAlertStack);
begin
  if FStacker <> Value then
  begin
    FStacker := Value;
    if FStacker <> nil then
    begin
      Location.Position := FStacker.Position;
      FStacker.FreeNotification(Self);
    end;
  end;
end;

procedure TJvDesktopAlert.SetButtons(const Value: TJvDesktopAlertButtons);
begin
  FButtons.Assign(Value);
end;

procedure TJvDesktopAlert.SetColors(const Value: TJvDesktopAlertColors);
begin
  FColors.Assign(Value);
end;

procedure TJvDesktopAlert.SetDropDownMenu(const Value: TPopupMenu);
begin
  FDesktopForm.tbDropDown.DropDownMenu := Value;
end;

procedure TJvDesktopAlert.SetFont(const Value: TFont);
begin
  FDesktopForm.lblText.Font := Value;
end;

procedure TJvDesktopAlert.SetHeaderFont(const Value: TFont);
begin
  FDesktopForm.lblHeader.Font := Value;
end;

procedure TJvDesktopAlert.SetHeaderText(const Value: string);
begin
  FDesktopForm.lblHeader.Caption := Value;
end;

procedure TJvDesktopAlert.SetHint(const Value: string);
begin
  FDesktopForm.Hint := Value;
end;

procedure TJvDesktopAlert.SetImage(const Value: TPicture);
begin
  FDesktopForm.imIcon.Picture := Value;
end;

procedure TJvDesktopAlert.SetImages(const Value: TCustomImageList);
begin
  if FImages <> Value then
  begin
    FImages := Value;
    if FImages <> nil then
      FImages.FreeNotification(Self);
  end;
end;

procedure TJvDesktopAlert.SetLocation(const Value: TJvDesktopAlertLocation);
begin
  //
end;

procedure TJvDesktopAlert.SetMessageText(const Value: string);
begin
  FDesktopForm.lblText.Caption := Value;
  FDesktopForm.lblText.Update;
end;

procedure TJvDesktopAlert.SetParentFont(const Value: Boolean);
begin
  FDesktopForm.ParentFont := Value;
end;

procedure TJvDesktopAlert.SetPopupMenu(const Value: TPopupMenu);
begin
  FDesktopForm.PopupMenu := Value;
end;

procedure TJvDesktopAlert.SetShowHint(const Value: Boolean);
begin
  FDesktopForm.ShowHint := Value;
end;

function TJvDesktopAlert.Showing: Boolean;
begin
  Result := (FDesktopForm <> nil) and FDesktopForm.Showing;
end;

procedure TJvDesktopAlert.SetOptions(const Value: TJvDesktopAlertOptions);
begin
  if FOptions <> Value then
  begin
    FOptions := Value;
    if not (daoCanMove in FOptions) then
      Exclude(FOptions, daoCanMoveAnywhere);
  end;
end;

function TJvDesktopAlert.GetCloseButtonClick: TNotifyEvent;
begin
  Result := FDesktopForm.tbClose.OnClick;
end;

procedure TJvDesktopAlert.SetCloseButtonClick(const Value: TNotifyEvent);
begin
  FDesktopForm.tbClose.OnClick := Value;
end;

procedure TJvDesktopAlert.SetAlertStyle(const Value: TJvAlertStyle);
begin
  if (FAlertStyle <> Value) or (FStyleHandler = nil) then
  begin
    FAlertStyle := Value;
    FStyleHandler.Free;
    FStyleHandler := CreateHandlerForStyle(AlertStyle, FDesktopForm);
  end;
end;

//=== { TJvDesktopAlertStack } ===============================================

constructor TJvDesktopAlertStack.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FItems := TList.Create;
  FPosition := dapBottomRight;
end;

destructor TJvDesktopAlertStack.Destroy;
begin
  FItems.Free;
  inherited Destroy;
end;

procedure TJvDesktopAlertStack.Add(AForm: TCustomForm);
begin
  FItems.Add(AForm);
  UpdatePositions;
end;

function TJvDesktopAlertStack.GetCount: Integer;
begin
  Result := FItems.Count;
end;

function TJvDesktopAlertStack.GetItems(Index: Integer): TJvFormDesktopAlert;
begin
  Result := TJvFormDesktopAlert(FItems[Index]);
  Assert((Result = nil) or (Result is TJvFormDesktopAlert));
end;

procedure TJvDesktopAlertStack.Remove(AForm: TCustomForm);
var
  Index, PrevNilSlot: Integer;
  Form: TJvFormDesktopAlert;
begin
  if (AForm <> nil) and (AForm is TJvFormDesktopAlert) then
  begin
    // The basic trick here is to push piling forms down in the list, while keeping the
    // static ones (i.e. a form that has the mouse pointer over it) in place.
    Index := FItems.IndexOf(AForm);
    if Index >= 0 then
    begin
      FItems[Index] := nil;

      Inc(Index);
      while Index < FItems.Count do
      begin
        Form := Items[Index];
        if Assigned(Form) and (not Form.MouseInControl) then
        begin
          PrevNilSlot := Pred(Index);
          while FItems[PrevNilSlot] <> nil do
            Dec(PrevNilSlot);
          FItems[PrevNilSlot] := FItems[Index];
          FItems[Index] := nil;
        end;

        Inc(Index);
      end;

      while (Pred(FItems.Count) >= 0) and (FItems[Pred(FItems.Count)] = nil) do
        FItems.Delete(Pred(FItems.Count));

      UpdatePositions;
    end;
  end;
end;

procedure TJvDesktopAlertStack.SetPosition(const Value: TJvDesktopAlertPosition);
begin
  if FPosition <> Value then
  begin
//    if Value = dapCustom then raise
//      Exception.Create('TJvDesktopAlertStack does not handle dapCustom alerts!');
//    FItems.Clear;
    FPosition := Value;
  end;
end;

procedure TJvDesktopAlertStack.UpdatePositions;
var
  C, I: Integer;
  Form: TJvFormDesktopAlert;
  X, Y: Integer;
  R: TRect;
begin
  C := Count;
  if C > 0 then
  begin
    R := ScreenWorkArea;
    case Position of
      dapBottomRight:
        begin
          Y := R.Bottom;
          for I := 0 to Pred(C) do
          begin
            Form := Items[I];
            if Assigned(Form) and Form.Visible then
            begin
              X := R.Right - Form.Width;
              Dec(Y, Form.Height);
              Form.SetNewOrigin(X, Y);
            end;
          end;
        end;
      dapBottomLeft:
        begin
          X := R.Left;
          Y := R.Bottom;
          for I := 0 to Pred(C) do
          begin
            Form := Items[I];
            if Assigned(Form) and Form.Visible then
            begin
              Dec(Y, Form.Height);
              Form.SetNewOrigin(X, Y);
            end;
          end;
        end;
      dapTopRight:
        begin
          Y := R.Top;
          for I := 0 to Pred(C) do
          begin
            Form := Items[I];
            if Assigned(Form) and Form.Visible then
            begin
              X := R.Right - Form.Width;
              Form.SetNewOrigin(X, Y);
              Inc(Y, Form.Height);
            end;
          end;
        end;
      dapTopLeft:
        begin
          Y := R.Top;
          X := R.Left;
          for I := 0 to Pred(C) do
          begin
            Form := Items[I];
            if Assigned(Form) and Form.Visible then
            begin
              Form.SetNewOrigin(X, Y);
              Inc(Y, Form.Height);
            end;
          end;
        end;
    end;
  end;
end;

//=== { TJvCustomDesktopAlertStyle } =========================================

constructor TJvCustomDesktopAlertStyleHandler.Create(OwnerForm: TJvFormDesktopAlert);
begin
  inherited Create;
  FAnimTimer := TTimer.Create(nil);
  FAnimTimer.Enabled := False;
  FOwnerForm := OwnerForm;
end;

destructor TJvCustomDesktopAlertStyleHandler.Destroy;
begin
  FAnimTimer.OnTimer := nil;
  FreeAndNil(FAnimTimer);
  inherited Destroy;
end;

procedure TJvCustomDesktopAlertStyleHandler.AbortAnimation;
begin
  AnimTimer.Enabled := False;
  if Status = hsStartAnim then
    FinalizeStartAnimation
  else
  if Status = hsEndAnim then
    FinalizeEndAnimation;
end;

procedure TJvCustomDesktopAlertStyleHandler.DoEndAnimation;
begin
  if EndSteps > 0 then
  begin
    AnimTimer.Enabled := False;
    AnimTimer.OnTimer := EndAnimTimer;
    AnimTimer.Interval := EndInterval;
    FCurrentStep := 0;
    PrepareEndAnimation;
    FStatus := hsEndAnim;
    AnimTimer.Enabled := True;
  end;
end;

procedure TJvCustomDesktopAlertStyleHandler.DoDisplay;
begin
  if DisplayDuration > 0 then
  begin
    AnimTimer.Enabled := False;
    AnimTimer.OnTimer := DisplayTimer;
    AnimTimer.Interval := DisplayDuration;
    FCurrentStep := 0;
    FStatus := hsDisplay;
    AnimTimer.Enabled := True;
  end;
end;

procedure TJvCustomDesktopAlertStyleHandler.DoStartAnimation;
begin
  if StartSteps > 0 then
  begin
    AnimTimer.Enabled := False;
    AnimTimer.OnTimer := StartAnimTimer;
    AnimTimer.Interval := StartInterval;
    FCurrentStep := 0;
    PrepareStartAnimation;
    FStatus := hsStartAnim;
    AnimTimer.Enabled := True;
  end;
end;

procedure TJvCustomDesktopAlertStyleHandler.EndAnimTimer(Sender: TObject);
begin
  Inc(FCurrentStep);
  if CurrentStep >= EndSteps then
  begin
    AnimTimer.Enabled := False;
    FinalizeEndAnimation;
  end;
end;

procedure TJvCustomDesktopAlertStyleHandler.DisplayTimer(Sender: TObject);
begin
  AnimTimer.Enabled := False;
  FStatus := hsIdle;
end;

function TJvCustomDesktopAlertStyleHandler.GetActive: Boolean;
begin
  Result := AnimTimer.Enabled;
end;

procedure TJvCustomDesktopAlertStyleHandler.SetEndInterval(const Value: Cardinal);
begin
  FEndInterval := Value;
end;

procedure TJvCustomDesktopAlertStyleHandler.SetEndSteps(const Value: Cardinal);
begin
  FEndSteps := Value;
  if FEndSteps < 1 then
    FEndSteps := 1;
end;

procedure TJvCustomDesktopAlertStyleHandler.SetDisplayDuration(
  const Value: Cardinal);
begin
  FDisplayDuration := Value;
end;

procedure TJvCustomDesktopAlertStyleHandler.SetOwnerForm(
  const Value: TJvFormDesktopAlert);
begin
  FOwnerForm := Value;
end;

procedure TJvCustomDesktopAlertStyleHandler.SetStartInterval(
  const Value: Cardinal);
begin
  FStartInterval := Value;
end;

procedure TJvCustomDesktopAlertStyleHandler.SetStartSteps(const Value: Cardinal);
begin
  FStartSteps := Value;
  if FStartSteps < 1 then
    FStartSteps := 1;
end;

procedure TJvCustomDesktopAlertStyleHandler.StartAnimTimer(Sender: TObject);
begin
  Inc(FCurrentStep);
  if CurrentStep >= StartSteps then
  begin
    AnimTimer.Enabled := False;
    FinalizeStartAnimation;
    DoDisplay;
  end;
end;

procedure TJvCustomDesktopAlertStyleHandler.FinalizeEndAnimation;
begin
  if OwnerForm <> nil then
    OwnerForm.Close;
end;

procedure TJvCustomDesktopAlertStyleHandler.PrepareStartAnimation;
begin
  if OwnerForm <> nil then
    ShowWindow(OwnerForm.Handle, SW_SHOWNA);
end;

//=== { TJvFadeAlertStyleHandler } ===========================================

const
  WS_EX_LAYERED = $00080000;
  LWA_ALPHA = $00000002;

type
  TDynamicSetLayeredWindowAttributes =
    function(HWnd: THandle; crKey: COLORREF; bAlpha: Byte; dwFlags: DWORD): Boolean; stdcall;

constructor TJvFadeAlertStyleHandler.Create(OwnerForm: TJvFormDesktopAlert);
begin
  inherited Create(OwnerForm);

  // Set default values
  StartInterval := 25;
  StartSteps := 10;
  EndInterval := 50;
  EndSteps := 10;
  DisplayDuration := 1400;
  MinAlphaBlendValue := 0;
  MaxAlphaBlendValue := 255;
end;

procedure TJvFadeAlertStyleHandler.AbortAnimation;
begin
  if not (Status in [hsDisplay, hsStartAnim]) then
  begin
    AnimTimer.Enabled := False;
    DoAlphaBlend(MaxAlphaBlendValue);
  end;
end;

procedure TJvFadeAlertStyleHandler.DoAlphaBlend(Value: Byte);
var
  DynamicSetLayeredWindowAttributes: TDynamicSetLayeredWindowAttributes;
  CurrentStyle: Cardinal;

  procedure InitProcs;
  const
    sUser32 = 'User32.dll';
  var
    ModH: HMODULE;
  begin
    ModH := GetModuleHandle(sUser32);
    if ModH <> 0 then
      @DynamicSetLayeredWindowAttributes := GetProcAddress(ModH, 'SetLayeredWindowAttributes')
    else
      @DynamicSetLayeredWindowAttributes := nil;
  end;

begin
  if OwnerForm <> nil then
  begin
    InitProcs;
    if OwnerForm.HandleAllocated and Assigned(DynamicSetLayeredWindowAttributes) then
    begin
      CurrentStyle := GetWindowLong(OwnerForm.Handle, GWL_EXSTYLE);
      if (CurrentStyle and WS_EX_LAYERED) = 0 then
        SetWindowLong(OwnerForm.Handle, GWL_EXSTYLE,
          GetWindowLong(OwnerForm.Handle, GWL_EXSTYLE) or WS_EX_LAYERED);
      DynamicSetLayeredWindowAttributes(OwnerForm.Handle, 0, Value, LWA_ALPHA);
    end;
  end;
end;

procedure TJvFadeAlertStyleHandler.EndAnimTimer(Sender: TObject);
begin
  inherited EndAnimTimer(Sender);
  DoAlphaBlend(MaxAlphaBlendValue -
    ((Cardinal(MaxAlphaBlendValue) - MinAlphaBlendValue) * CurrentStep) div EndSteps);
end;

procedure TJvFadeAlertStyleHandler.FinalizeEndAnimation;
begin
  DoAlphaBlend(MinAlphaBlendValue);
  inherited FinalizeEndAnimation;  // Do not forget to call inherited, to hide the form
end;

procedure TJvFadeAlertStyleHandler.FinalizeStartAnimation;
begin
  DoAlphaBlend(MaxAlphaBlendValue);
end;

procedure TJvFadeAlertStyleHandler.PrepareEndAnimation;
begin
  DoAlphaBlend(MaxAlphaBlendValue);
end;

procedure TJvFadeAlertStyleHandler.PrepareStartAnimation;
begin
  DoAlphaBlend(MinAlphaBlendValue);
  inherited PrepareStartAnimation;
end;

procedure TJvFadeAlertStyleHandler.SetMaxAlphaBlendValue(const Value: Byte);
begin
  FMaxAlphaBlendValue := Value;
end;

procedure TJvFadeAlertStyleHandler.SetMinAlphaBlendValue(const Value: Byte);
begin
  FMinAlphaBlendValue := Value;
end;

procedure TJvFadeAlertStyleHandler.StartAnimTimer(Sender: TObject);
begin
  DoAlphaBlend(MinAlphaBlendValue +
    ((Cardinal(MaxAlphaBlendValue) - MinAlphaBlendValue) * CurrentStep) div StartSteps);
  inherited StartAnimTimer(Sender);
end;

procedure TJvDesktopAlert.SetStyleHandler(const Value: TJvCustomDesktopAlertStyleHandler);
begin
  FStyleHandler.Assign(Value);
end;

//=== { TJvCenterGrowAlertStyleHandler } =====================================

constructor TJvCenterGrowAlertStyleHandler.Create(OwnerForm: TJvFormDesktopAlert);
begin
  inherited Create(OwnerForm);

  // Set default values
  StartInterval := 25;
  StartSteps := 10;
  EndInterval := 50;
  EndSteps := 10;
  DisplayDuration := 1400;

  MinGrowthPercentage := 0;
  MaxGrowthPercentage := 100;
end;

procedure TJvCenterGrowAlertStyleHandler.AbortAnimation;
begin
  if not (Status in [hsDisplay, hsStartAnim]) then
  begin
    AnimTimer.Enabled := False;
    DoGrowRegion(MaxGrowthPercentage);
  end;
end;

procedure TJvCenterGrowAlertStyleHandler.DoGrowRegion(Percentage: Double);
var
  RegionRect: TRect;
  Region: HRGN;
  RegionHeight: Integer;
  RegionWidth: Integer;
begin
  if OwnerForm <> nil then
  begin
    RegionHeight := Round(Percentage * OwnerForm.Height / 100.0);
    RegionWidth := Round(Percentage * OwnerForm.Width / 100.0);

    RegionRect.Left := (OwnerForm.Width - RegionWidth) div 2;
    RegionRect.Right := RegionRect.Left + RegionWidth;
    RegionRect.Top := (OwnerForm.Height - RegionHeight) div 2;
    RegionRect.Bottom := RegionRect.Top + RegionHeight;

    Region := CreateRectRgnIndirect(RegionRect);
    SetWindowRgn(OwnerForm.Handle, Region, True);
  end;
end;

procedure TJvCenterGrowAlertStyleHandler.StartAnimTimer(Sender: TObject);
begin
  DoGrowRegion(MinGrowthPercentage +
    ((MaxGrowthPercentage - MinGrowthPercentage) * CurrentStep) / StartSteps);
  inherited StartAnimTimer(Sender);
end;

procedure TJvCenterGrowAlertStyleHandler.EndAnimTimer(Sender: TObject);
begin
  inherited EndAnimTimer(Sender);
  DoGrowRegion(MaxGrowthPercentage -
    ((MaxGrowthPercentage - MinGrowthPercentage) * CurrentStep) / EndSteps);
end;

procedure TJvCenterGrowAlertStyleHandler.FinalizeEndAnimation;
begin
  DoGrowRegion(MinGrowthPercentage);
  inherited FinalizeEndAnimation;
end;

procedure TJvCenterGrowAlertStyleHandler.FinalizeStartAnimation;
begin
  DoGrowRegion(MaxGrowthPercentage);
end;

procedure TJvCenterGrowAlertStyleHandler.PrepareEndAnimation;
begin
  DoGrowRegion(MaxGrowthPercentage);
end;

procedure TJvCenterGrowAlertStyleHandler.PrepareStartAnimation;
begin
  DoGrowRegion(MinGrowthPercentage);
  inherited PrepareStartAnimation;
end;

procedure TJvCenterGrowAlertStyleHandler.SetMaxGrowthPercentage(const Value: Double);
begin
  FMaxGrowthPercentage := Value;
  if FMaxGrowthPercentage < 0.0 then
    FMaxGrowthPercentage := 0.0;
  if FMaxGrowthPercentage > 100.0 then
    FMaxGrowthPercentage := 100.0;
end;

procedure TJvCenterGrowAlertStyleHandler.SetMinGrowthPercentage(const Value: Double);
begin
  FMinGrowthPercentage := Value;
  if FMinGrowthPercentage < 0.0 then
    FMinGrowthPercentage := 0.0;
  if FMinGrowthPercentage > 100.0 then
    FMinGrowthPercentage := 100.0;
end;

function TJvDesktopAlert.GetBiDiMode: TBidiMode;
begin
  Result := FDesktopForm.BiDiMode;
end;

procedure TJvDesktopAlert.SetBiDiMode(const Value: TBidiMode);
begin
  FDesktopForm.BiDiMode := Value;
end;

initialization
  {$IFDEF UNITVERSIONING}
  RegisterUnitVersion(HInstance, UnitVersioning);
  {$ENDIF UNITVERSIONING}

finalization
  FreeAndNil(GStacker);
  {$IFDEF UNITVERSIONING}
  UnregisterUnitVersion(HInstance);
  {$ENDIF UNITVERSIONING}

end.

