{******************************************************************************}
{                       CnPack For Delphi/C++Builder                           }
{                     中国人自己的开放源码第三方开发包                         }
{                   (C)Copyright 2001-2015 CnPack 开发组                       }
{                   ------------------------------------                       }
{                                                                              }
{            本开发包是开源的自由软件，您可以遵照 CnPack 的发布协议来修        }
{        改和重新发布这一程序。                                                }
{                                                                              }
{            发布这一开发包的目的是希望它有用，但没有任何担保。甚至没有        }
{        适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。        }
{                                                                              }
{            您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果        }
{        还没有，可访问我们的网站：                                            }
{                                                                              }
{            网站地址：http://www.cnpack.org                                   }
{            电子邮件：master@cnpack.org                                       }
{                                                                              }
{******************************************************************************}

unit CnHint;
{* |<PRE>
================================================================================
* 软件名称：CnPack 界面组件包
* 单元名称：CnHint 控件单元
* 单元作者：
* 备    注：部分参考自网上佚名代码
* 开发平台：PWinXP + Delphi 7.0
* 兼容测试：PWin9X/2000/XP + Delphi 7.0
* 本 地 化：该单元中的字符串均符合本地化处理方式
* 单元标识：$Id$
* 修改记录：2008.01.15 V1.0
*               创建单元
================================================================================
|</PRE>}

interface

{$I CnPack.inc}

uses
  Windows, Messages, SysUtils, Graphics, Classes, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, Math;

const
  CN_MSG_HINT_NOTIFY = WM_USER + $0357;

type
  TCnHint = class;

  TVAlignment = (vtaTopJustify, vtaBottomJustify, vtaCenter);

  THintStyle = (hsNormal, hsBalloonHint, hsAuto);

  THintPosition = (hpUpLeft, hpUpRight, hpDownLeft, hpDownRight);

  THintBeforeEvent = procedure(AHint: TCnHint; Rect: TRect; Text: string) of object;

  THintOwnerDraw = procedure(AHint: TCnHint; Canvas: TCanvas; Rect: TRect; Text: string) of object;

  THintMeasureRect = procedure(AHint: TCnHint; var Rect: TRect; Text: string) of object;

  TCnHint = class(TComponent)
  {* 控制所有 Hint 风格的控制组件}
  private
    FAlignment: TAlignment;
    FBackColor: TColor;
    FOnBeforeHint: THintBeforeEvent;
    FBorderColor: TColor;
    FHintPosition: THintPosition;
    FFont: TFont;
    FGlyph: TBitmap;
    FHintStyle: THintStyle;
    FOnMeasureRect: THintMeasureRect;
    FOnOwnerDraw: THintOwnerDraw;
    FTitle: string;
    procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
    procedure GlyphChange(Sender: TObject);
    procedure SetFont(const Value: TFont);
    procedure SetGlyph(const Value: TBitmap);
  protected
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Loaded; override;
    procedure UpdateHintWindowFont;
  published
    property Alignment: TAlignment read FAlignment write FAlignment default taLeftJustify;
    property BackColor: TColor read FBackColor write FBackColor default $F0A07D;
    property BorderColor: TColor read FBorderColor write FBorderColor default clWhite;
    property HintPosition: THintPosition read FHintPosition write FHintPosition default hpDownRight;
    property Font: TFont read FFont write SetFont;
    property Glyph: TBitmap read FGlyph write SetGlyph;
    property HintStyle: THintStyle read FHintStyle write FHintStyle;
    property Title: string read FTitle write FTitle;
    property OnBeforeHint: THintBeforeEvent read FOnBeforeHint write FOnBeforeHint;
    property OnMeasureRect: THintMeasureRect read FOnMeasureRect write FOnMeasureRect;
    property OnOwnerDraw: THintOwnerDraw read FOnOwnerDraw write FOnOwnerDraw;
  end;

  TCnInternalHintWindow = class(THintWindow)
  {* 实际用来显示的 HintWindow，不能作为组件注册到组件板上}
  private
    FTimer: TTimer;
    FBitmap: TBitmap;
    FLastActive: Cardinal;
    FHint: TCnHint;
    FHintPosition: THintPosition;
    FModified: Boolean;
    FGlyph: TBitmap;
    FUpdating: Boolean;
    FAlignment: TAlignment;
    FHintStyle: THintStyle;
    FOnCancelHint: TNotifyEvent;
    FFirstLineAsTitle: Boolean;
    procedure GlyphChange(Sender: TObject);
    function FindCnHint: TCnHint;
    function GetTextRect(ACanvas: TCanvas; Text: string; R: TRect;
      HAlign: TAlignment; VAlign: TVAlignment): TRect;
    procedure DrawHintText(Canvas: TCanvas; R: TRect; const AText: string;
      IsBalloonHint: Boolean);
    function GetHintPosition(WorkRect: TRect; AWidth, AHeight: Integer;
      Pos: TPoint; IsBalloonHint: Boolean): THintPosition;
    procedure SetPosition(const Value: THintPosition);
    procedure SetGlyph(const Value: TBitmap);
    procedure HintNotify(var message: TMessage); message CN_MSG_HINT_NOTIFY;
    procedure HintTimer(Sender: TObject);
  protected
    procedure Paint; override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure DoCancelHint; virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure ActivateHint(Rect: TRect; const AHint: string); override;
    procedure ActivateHintFromPos(const HintPos: TPoint; const AHint: string;
      const ATitle: string = ''; HidePause: Integer = 0);
    property HintPosition: THintPosition read FHintPosition write SetPosition;
    property Glyph: TBitmap read FGlyph write SetGlyph;
    property Alignment: TAlignment read FAlignment write FAlignment;
    property HintStyle: THintStyle read FHintStyle write FHintStyle;
    property OnCancelHint: TNotifyEvent read FOnCancelHint write FOnCancelHint;
  end;

  TCnHintWindow = class(TComponent)
  {* 封装一 CnInternalHintWindow 的组件}
  private
    FHintWindow: TCnInternalHintWindow;
    FOnCancelHint: TNotifyEvent;
    procedure SetGlyph(const Value: TBitmap);
    procedure SetPosition(const Value: THintPosition);
    procedure SetAlignment(const Value: TAlignment);
    procedure SetHintStyle(const Value: THintStyle);
    function GetAlignment: TAlignment;
    function GetGlyph: TBitmap;
    function GetHintStyle: THintStyle;
    function GetHintPosition: THintPosition;
  protected
    procedure HintWindowCancelHint(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure ActivateHint(const AHint: string; const ATitle: string);
    procedure ActivateHintFromPos(const HintPos: TPoint; const AHint: string;
      const ATitle: string = ''; HidePause: Integer = 5000);
    procedure ReleaseHandle;
  published
    property HintPosition: THintPosition read GetHintPosition write SetPosition;
    property Glyph: TBitmap read GetGlyph write SetGlyph;
    property Alignment: TAlignment read GetAlignment write SetAlignment;
    property HintStyle: THintStyle read GetHintStyle write SetHintStyle;
    property OnCancelHint: TNotifyEvent read FOnCancelHint write FOnCancelHint;
  end;

implementation

var
  FCnHints: TList = nil;
  FCnHintWindows: TList = nil;

// 获得 Hint 显示区的具体尺寸
function GetHintRect(ACanvas: TCanvas; const Text: string;
  GlyphWidth, GlyphHeight: Integer; IsBalloonHint, FirstLineAsTitle: Boolean): TRect;
var
  Lines: TStrings;
  I, H, W: Integer;
  Len: Integer;
  Empty: Boolean;
  Added: Boolean;
  First: Boolean;
  OldStyles: TFontStyles;
begin
  Lines := TStringList.Create;
  try
    Lines.Text := Text;
    H := 0;
    W := 0;
    Empty := GlyphWidth <= 0;
    Added := False;
    First := True;
    OldStyles := ACanvas.Font.Style;

    for I := 0 to Lines.Count - 1 do // 挨个计算每行文字的高度与宽度
    begin
      if FirstLineAsTitle and (I = 0) then
        ACanvas.Font.Style := ACanvas.Font.Style + [fsBold]
      else
        ACanvas.Font.Style := OldStyles;

      Len := ACanvas.TextWidth(Lines[I]);
      if not Empty then
      begin
        if (H <= GlyphHeight) and First then
        begin
          Inc(Len, GlyphWidth + 6); // 有图片的话，宽度加图片宽度加边缘
          First := False;
        end;
        if not Added and (H + 10 >= GlyphHeight) then
        begin
          Added := True;
          Inc(H, 6);
        end;
      end;
      H := H + ACanvas.TextHeight(Lines[I]);
      if W < Len then
        W := Len;
    end;
    if H < GlyphHeight then
      H := GlyphHeight;

    if IsBalloonHint then // 加上把手尖角
    begin
      Inc(H, 18);
      Inc(W, 12);
      if H < 35 then
        H := 35;
      if W < 50 then
        W := 50;
    end
    else
    begin
      Inc(H, 8);
      Inc(W, 12);
    end;
    Result := Classes.Rect(0, 0, W, H);
  finally
    ACanvas.Font.Style := OldStyles;
    Lines.Free;
  end;
end;

function CreateRegion(Mask: TBitmap; TransparentColor: TColor): HRGN;
var
  Dc: HDC;
  Rgn: HRGN;
  X, Y: Integer;
  P: TPoint;
  Line: Boolean;
  color: TColor;
begin
  Dc := Mask.Canvas.Handle;
  BeginPath(Dc);
  for X := 0 to Mask.Width - 1 do
  begin
    Line := False;
    for Y := 0 to Mask.Height - 1 do
    begin
      Color := Mask.Canvas.Pixels[x, y];
      if Color <> TransparentColor then
      begin
        if not Line then
        begin
          Line := True;
          P.x := X;
          P.y := Y;
        end;
      end;
      if (Color = TransparentColor) or (Y = Mask.Height - 1) then
      begin
        if Line then
        begin
          Line := False;
          MoveToEx(Dc, P.x, P.y, nil);
          LineTo(Dc, P.x, Y);
          LineTo(Dc, P.x + 1, Y);
          LineTo(Dc, P.x + 1, P.y);
          CloseFigure(Dc);
        end;
      end;
    end;
  end;
  EndPath(Dc);
  Rgn := PathToRegion(Dc);
  Result := Rgn;
end;

{ TCnInternalHintWindow }

procedure TCnInternalHintWindow.ActivateHint(Rect: TRect; const AHint: string);
begin
  ActivateHintFromPos(Mouse.CursorPos, AHint);
end;

function GetCursorHeightMargin: Integer;
var
  IconInfo: TIconInfo;
  BitmapInfoSize, BitmapBitsSize, ImageSize: DWORD;
  Bitmap: PBitmapInfoHeader;
  Bits: Pointer;
  BytesPerScanline: Integer;

{$IFDEF WIN64}
  function FindScanline(Source: Pointer; MaxLen: Cardinal; Value: Cardinal): Cardinal;
  var
    I: Integer;
    V: Byte;
    P: PByte;
  begin
    //  Pascal Impl.
    Result := MaxLen;
    V := Byte(Value);
    P := PByte(Source);
    for I := MaxLen downto 0 do
    begin
      if P^ = V then
      begin
        Result := I;
        Exit;
      end;
      Inc(P);
    end;
  end;
{$ELSE}
  function FindScanline(Source: Pointer; MaxLen: Cardinal; Value: Cardinal): Cardinal; assembler;
  asm                // EAX              EDX               ECX
        PUSH    ECX
        MOV     ECX, EDX     // MaxLen  -> ECX
        MOV     EDX, EDI
        MOV     EDI, EAX     // Source  -> EDI
        POP     EAX          // Pattern -> EAX
        REPE    SCASB
        MOV     EAX, ECX
        MOV     EDI, EDX
  end;
{$ENDIF}

begin
  Result := GetSystemMetrics(SM_CYCURSOR);
  if GetIconInfo(GetCursor, IconInfo) then
    try
      GetDIBSizes(IconInfo.hbmMask, BitmapInfoSize, BitmapBitsSize);
      Bitmap := AllocMem(DWORD(BitmapInfoSize) + BitmapBitsSize);
      try
        Bits := Pointer(DWORD(Bitmap) + BitmapInfoSize);

        if GetDIB(IconInfo.hbmMask, 0, Bitmap^, Bits^) and (Bitmap^.biBitCount = 1) then
        begin
          with Bitmap^do
          begin
            BytesPerScanline := ((biWidth * biBitCount + 31) and not 31) div 8;
            ImageSize := biWidth * BytesPerScanline;
            Bits := Pointer(DWORD(Bits) + BitmapBitsSize - ImageSize); 
            Result := FindScanline(Bits, ImageSize, $FF);
            if (Result = 0) and (biHeight >= 2 * biWidth) then
              Result := FindScanline(Pointer(DWORD(Bits) - ImageSize), ImageSize, $00);
            Result := Result div BytesPerScanline;
          end;
          Dec(Result, IconInfo.yHotSpot);
        end;
      finally
        FreeMem(Bitmap, BitmapInfoSize + BitmapBitsSize);
      end;
    finally
      if IconInfo.hbmColor <> 0 then
        DeleteObject(IconInfo.hbmColor);
      if IconInfo.hbmMask <> 0 then
        DeleteObject(IconInfo.hbmMask);
    end;
end;

procedure TCnInternalHintWindow.ActivateHintFromPos(const HintPos: TPoint;
  const AHint: string; const ATitle: string; HidePause: Integer);
var
  PS: array[0..2] of TPoint;
  H: HRGN;
  Pos: TPoint;
  R, SaveRect: TRect;
  Posi: THintPosition;
  intW, intH: Integer;
  AWidth, AHeight: Integer;
  Rgn: HRGN;
  cR, cG, cB: Byte;
  Rect: TRect;
  IsBalloonHint: Boolean;
  AHintStyle: THintStyle;
  WorkArea: TRect;
  PT: PPoint;
  I: Integer;

  procedure GetHintRgn;
  var
    TempR: TRect;
    OffY: Integer;
  begin
    R.Right := R.Right - R.Left;
    R.Left := 0;
    R.Bottom := R.Bottom - R.Top;
    R.Top := 0;
    intW := R.Right;
    intH := R.Bottom;
    Pos := HintPos;

    SystemParametersInfo(SPI_GETWORKAREA, 0, @TempR, 0);
    Posi := GetHintPosition(TempR, intW, intH, Pos, IsBalloonHint);
    if IsBalloonHint then
    begin
      case Posi of
        hpUpLeft:
          begin
            OffY := GetCursorHeightMargin;
            Pos.Y := Pos.Y - 6;
            R.Left := Pos.X - (R.Right - 5);
            R.Top := Pos.Y - R.Bottom - OffY;
            R.Right := R.Right + R.Left;
            R.Bottom := R.Top + R.Bottom;
            SaveRect := R;
            PS[0] := Point(R.Right - 16, R.Bottom - 1);
            PS[1] := Point(R.Right - 16, R.Bottom + 15);
            PS[2] := Point(R.Right - 16 - 17, R.Bottom - 2);
            Rect := Classes.Rect(R.Left, R.Top, R.Right, R.Bottom + 16);
          end;
        hpUpRight:
          begin
            OffY := GetCursorHeightMargin;
            Pos.Y := Pos.Y - 6;
            R.Left := Pos.X - 25;
            R.Top := Pos.Y - R.Bottom - OffY;
            R.Right := R.Right + R.Left;
            R.Bottom := R.Top + R.Bottom;
            SaveRect := R;
            PS[0] := Point(R.Left + 16, R.Bottom - 1);
            PS[1] := Point(R.Left + 16, R.Bottom + 16);
            PS[2] := Point(R.Left + 16 + 17, R.Bottom - 1);
            Rect := Classes.Rect(R.Left, R.Top, R.Right, R.Bottom + 16);
          end;
        hpDownLeft:
          begin
            OffY := GetCursorHeightMargin + 10;
            R.Left := Pos.X - (R.Right - 16);
            R.Top := Pos.Y + OffY;
            R.Right := R.Right + R.Left;
            R.Bottom := R.Top + R.Bottom;
            SaveRect := R;
            PS[0] := Point(R.Right - 16, R.Top);
            PS[1] := Point(R.Right - 16, R.Top - 16);
            PS[2] := Point(R.Right - 16 - 17, R.Top + 1);
            Rect := Classes.Rect(R.Left, R.Top - 16, R.Right, R.Bottom);
          end;
        hpDownRight:
          begin
            OffY := GetCursorHeightMargin + 10;
            R.Left := Pos.X - 16;
            R.Top := Pos.Y + OffY;
            R.Right := R.Right + R.Left;
            R.Bottom := R.Top + R.Bottom;
            SaveRect := R;
            PS[0] := Point(R.Left + 16, R.Top);
            PS[1] := Point(R.Left + 16, R.Top - 16);
            PS[2] := Point(R.Left + 16 + 17, R.Top + 1);
            Rect := Classes.Rect(R.Left, R.Top - 16, R.Right, R.Bottom);
          end;

      end;
    end
    else
    begin
      case Posi of
        hpUpLeft:
          begin
            Pos.Y := Pos.Y - 15;
            R.Left := Pos.X - (R.Right + 10);
            R.Top := Pos.Y - R.Bottom - 0;
            R.Right := R.Right + R.Left;
            R.Bottom := R.Top + R.Bottom;
            SaveRect := R;
            Rect := Classes.Rect(R.Left, R.Top, R.Right, R.Bottom);
          end;
        hpUpRight:
          begin
            Pos.Y := Pos.Y - 15;
            R.Left := Pos.X - 10;
            R.Top := Pos.Y - R.Bottom - 0;
            R.Right := R.Right + R.Left;
            R.Bottom := R.Top + R.Bottom;
            SaveRect := R;
            Rect := Classes.Rect(R.Left, R.Top, R.Right, R.Bottom);
          end;
        hpDownLeft:
          begin
            OffY := GetCursorHeightMargin;
            Pos.Y := Pos.Y + OffY;
            R.Left := Pos.X - (R.Right - 0);
            R.Top := Pos.Y + 0;
            R.Right := R.Right + R.Left;
            R.Bottom := R.Top + R.Bottom;
            SaveRect := R;
            Rect := Classes.Rect(R.Left, R.Top, R.Right, R.Bottom);
          end;
        hpDownRight:
          begin
            OffY := GetCursorHeightMargin;
            Pos.Y := Pos.Y + OffY;
            R.Left := Pos.X - 0;
            R.Top := Pos.Y + 0;
            R.Right := R.Right + R.Left;
            R.Bottom := R.Top + R.Bottom;
            SaveRect := R;
            Rect := Classes.Rect(R.Left, R.Top - 0, R.Right, R.Bottom);
          end;
      end;
    end;
  end;

begin
  if FBitmap <> nil then
  begin
    FBitmap.Width := 0;
    FBitmap.Height := 0;
  end
  else
    FBitmap := TBitmap.Create;

  try
    if FHint <> nil then
      if Owner <> nil then
      begin
        for I := 0 to Owner.ComponentCount - 1 do
          if Owner.Components[I] is TCnHint then
          begin
            FHint := Owner.Components[I] as TCnHint;
            Break;
          end;
      end;
      
    if FHint = nil then
      FHint := FindCnHint;

    if FHint = nil then
    begin
      AHintStyle := FHintStyle;
      Canvas.Font.Color := clBlack;
    end
    else
    begin
      AHintStyle := FHint.HintStyle;
      if FGlyph.Empty then
        FGlyph.Assign(FHint.FGlyph);
      Canvas.Font.Assign(FHint.Font);
    end;

    IsBalloonHint := (AHintStyle = hsBalloonHint) or
      ((AHintStyle = hsAuto) and ((ATitle <> '') or ((FHint <> nil) and (FHint.Title <> ''))));

    AWidth := 0;
    AHeight := 0;
    Caption := AHint;

    FFirstLineAsTitle := False;
    if ATitle <> '' then
    begin
      Caption := ATitle + #13#10 + Caption;
      FFirstLineAsTitle := True;
    end
    else
    begin
      if (FHint <> nil) then
        if FHint.Title <> '' then
        begin
          Caption := FHint.Title + #13#10 + Caption;
          FFirstLineAsTitle := True;
        end;
    end;
    // 只有显式指定了 Title、或 FHint.Title 不为空时，
    // 才设置 FFirstLineAsTitle 为 True; 并且 Title 的黑体效果要有图片时才能看出来

    if (FGlyph <> nil) and (FGlyph.Width > 0) and (FGlyph.Height > 0) then
    begin
      AWidth := FGlyph.Width;
      AHeight := FGlyph.Height;
    end;

    R := GetHintRect(Canvas, Caption, AWidth, AHeight, IsBalloonHint, FFirstLineAsTitle);
    GetHintRgn;
    case Posi of
      hpUpLeft:
        begin
          if (FHint <> nil) and (Assigned(FHint.FOnMeasureRect)) then
          begin
            FHint.FOnMeasureRect(FHint, R, Caption);
            OffSetRect(R, SaveRect.Right - R.Right, SaveRect.Bottom - R.Bottom);
            GetHintRgn;
          end;
        end;
      hpUpRight:
        begin
          if (FHint <> nil) and (Assigned(FHint.FOnMeasureRect)) then
          begin
            FHint.FOnMeasureRect(FHint, R, Caption);
            OffSetRect(R, 0, SaveRect.Bottom - R.Bottom);
            GetHintRgn;
          end;
        end;
      hpDownLeft:
        begin
          if (FHint <> nil) and (Assigned(FHint.FOnMeasureRect)) then
          begin
            FHint.FOnMeasureRect(FHint, R, Caption);
            OffSetRect(R, SaveRect.Right - R.Right, SaveRect.Top - R.Top);
            GetHintRgn;
          end;
        end;
      hpDownRight:
        begin
          if (FHint <> nil) and (Assigned(FHint.FOnMeasureRect)) then
          begin
            FHint.FOnMeasureRect(FHint, R, Caption);
            OffSetRect(R, 0, SaveRect.Top - R.Top);
            GetHintRgn;
          end;
        end;
    end;

    if IsBalloonHint then
    begin
      PS[0].X := PS[0].X - Rect.Left;
      PS[0].Y := PS[0].Y - Rect.Top;
      PS[1].X := PS[1].X - Rect.Left;
      PS[1].Y := PS[1].Y - Rect.Top;
      PS[2].X := PS[2].X - Rect.Left;
      PS[2].Y := PS[2].Y - Rect.Top;
    end;
    
    R.Left := R.Left - Rect.Left;
    R.Top := R.Top - Rect.Top;
    R.Right := R.Right - Rect.Left;
    R.Bottom := R.Bottom - Rect.Top;
    FBitmap.Width := Rect.Right - Rect.Left;
    FBitmap.Height := Rect.Bottom - Rect.Top + 1;

    if FHint = nil then
    begin
      cR := GetRValue(ColorToRGB(clBlack)) + 1;
      cG := GetGValue(ColorToRGB(clBlack)) + 2;
      cB := GetBValue(ColorToRGB(clBlack)) + 3;
      FBitmap.Canvas.Brush.Color := cB shl 16 or cG shl 8 or cR;
      FBitmap.Canvas.FillRect(Classes.Rect(0, 0, FBitmap.Width, FBitmap.Height));
      FBitmap.Canvas.Brush.Color := clInfoBk;
      FBitmap.Canvas.Pen.Color := clBlack;
    end
    else
    begin
      cR := GetRValue(ColorToRGB(FHint.BorderColor)) + 1;
      cG := GetGValue(ColorToRGB(FHint.BorderColor)) + 2;
      cB := GetBValue(ColorToRGB(FHint.BorderColor)) + 3;
      FBitmap.Canvas.Brush.Color := cB shl 16 or cG shl 8 or cR;
      FBitmap.Canvas.FillRect(Classes.Rect(0, 0, FBitmap.Width, FBitmap.Height));
      FBitmap.Canvas.Brush.Color := FHint.BackColor;
      FBitmap.Canvas.Pen.Color := FHint.BorderColor;
    end;

    if IsBalloonHint then
    begin
      FBitmap.Canvas.RoundRect(R.Left, R.Top, R.Right, R.Bottom, 15, 20);
      H := CreatePolygonRgn(PS, 3, WINDING);
      FillRgn(FBitmap.Canvas.Handle, H, FBitmap.Canvas.Brush.Handle);
      DeleteObject(H);
      FBitmap.Canvas.MoveTo(PS[0].X, PS[0].Y);
      FBitmap.Canvas.LineTo(PS[1].X, PS[1].Y);
      FBitmap.Canvas.LineTo(PS[2].X, PS[2].Y);
    end
    else
    begin
      FBitmap.Canvas.Rectangle(R.Left, R.Top, R.Right, R.Bottom);
      FBitmap.Canvas.Pen.Color := RGB(212, 208, 200);
      FBitmap.Canvas.MoveTo(R.Left, R.Bottom - 1);
      FBitmap.Canvas.LineTo(R.Left, R.Top);
      FBitmap.Canvas.LineTo(R.Right - 1, R.Top);
    end;

    FBitmap.Canvas.Brush.Style := bsClear;
    FBitmap.Canvas.Font.Assign(Canvas.Font);

    if (FHint <> nil) and Assigned(FHint.FOnBeforeHint) then
      FHint.FOnBeforeHint(FHint, R, Caption);

    if (FHint <> nil) and Assigned(FHint.FOnOwnerDraw) then
      FHint.FOnOwnerDraw(FHint, FBitmap.Canvas, R, Caption)
    else
      DrawHintText(FBitmap.Canvas, R, Caption, IsBalloonHint);

    if IsBalloonHint then
      Rgn := CreateRegion(FBitmap, FBitmap.Canvas.Pixels[0, 0])
    else
      Rgn := CreateRegion(FBitmap, FBitmap.Canvas.Pixels[0, 0] - 1);

    SetWindowRgn(Handle, Rgn, True);
    SystemParametersInfo(SPI_GETWORKAREA, 0, @WorkArea, 0);

    if Rect.Left < WorkArea.Left then
      OffsetRect(Rect, WorkArea.Left - Rect.Left, 0);
    if Rect.Top < WorkArea.Top then
      OffsetRect(Rect, 0, WorkArea.Top - Rect.Top);
    if Rect.Right > WorkArea.Right then
      OffsetRect(Rect, WorkArea.Right - Rect.Right, 0);
    if Rect.Bottom > WorkArea.Bottom then
      OffsetRect(Rect, 0, WorkArea.Bottom - Rect.Bottom);

    FTimer.Enabled := False;
    SetWindowPos(Handle, HWND_TOPMOST, Rect.Left, Rect.Top,
      Rect.Right - Rect.Left, Rect.Bottom - Rect.Top, SWP_NOACTIVATE);
    ShowWindow(Handle, SW_SHOWNOACTIVATE);
    Invalidate;

    if HidePause = 0 then
      HidePause := Application.HintHidePause;

    if HidePause > 0 then
    begin
      FTimer.Interval := HidePause;
      FTimer.Enabled := True;
      New(PT);
      PT^ := HintPos;
      PostMessage(Handle, CN_MSG_HINT_NOTIFY, HidePause, Integer(PT));
    end;
  finally
    FLastActive := GetTickCount;
  end;
end;

constructor TCnInternalHintWindow.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := ControlStyle - [csOpaque];
  with Canvas do
  begin
    Brush.Style := bsClear;
  end;
  FModified := False;
  FGlyph := TBitmap.Create;
  FGlyph.OnChange := GlyphChange;
  FTimer := TTimer.Create(Self);
  FTimer.Interval := Application.HintHidePause;
  FTimer.OnTimer := HintTimer;
  FTimer.Enabled := False;

  FAlignment := taLeftJustify;
  FHintStyle := hsAuto;

  if FCnHintWindows = nil then
    FCnHintWindows := TList.Create;
  FCnHintWindows.Add(Self);
end;

procedure TCnInternalHintWindow.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params do
  begin
    Style := Style - WS_BORDER;
    WindowClass.Style := WindowClass.Style or CS_SAVEBITS;
  end;
end;

destructor TCnInternalHintWindow.Destroy;
begin
  Hide;
  FTimer.Free;
  if FCnHintWindows <> nil then
    FCnHintWindows.Remove(Self);
  if FBitmap <> nil then
    FBitmap.Free;
  FGlyph.OnChange := nil;
  FGlyph.Free;
  inherited;
end;

procedure TCnInternalHintWindow.DrawHintText(Canvas: TCanvas; R: TRect;
  const AText: string; IsBalloonHint: Boolean);
var
  I, T, L: Integer;
  ARect, tR: TRect;
  Lines: TStringList;
  Align: TAlignment;
  Empty: Boolean;
  Index: Integer;
  TopText: string;
  Delta: Integer;
  OldStyles: TFontStyles;
begin
  if FHint <> nil then
    Align := FHint.Alignment
  else
    Align := FAlignment;

  ARect := R;
  InflateRect(ARect, -6, 0);
  T := 0;
  Lines := TStringList.Create;
  try
    R := ARect;
    Lines.Text := AText;
    Empty := (FGlyph = nil) or (FGlyph.Width <= 0) or (FGlyph.Height <= 0);
    if not Empty then
    begin
      L := 6 + FGlyph.Width;
      Canvas.Draw(R.Left, R.Top + 6, FGlyph);
      if not IsBalloonHint then
        Delta := 5
      else
        Delta := 10;

      T := Delta;
      Index := -1;
      TopText := '';
      for I := 0 to Lines.Count - 1 do
      begin
        TopText := TopText + Lines[I] + #13#10;
        if (T >= FGlyph.Height) then // T 记录文字累计的高度
        begin
          Index := I - 1;
          Delete(TopText, Length(TopText) - 1, 2);
          Inc(T, Canvas.TextHeight(Lines[I]));
          Break;
        end;
        Inc(T, Canvas.TextHeight(Lines[I]));
      end;

      if Index = -1 then
        Index := Lines.Count - 1;
      tR := GetTextRect(Canvas, TopText, Rect(R.Left + L, R.Top + Delta, R.Right, R.Top + T), Align, vtaTopJustify);
      T := 0;

      OldStyles := Canvas.Font.Style;
      for I := 0 to Index do // 画图片旁边的文字
      begin
        if FFirstLineAsTitle and (I = 0) then // 有图片、并且第一行是黑体时，画黑体字
          Canvas.Font.Style := Canvas.Font.Style + [fsBold]
        else
          Canvas.Font.Style := OldStyles;

        case Align of
          taLeftJustify:
            Canvas.TextRect(R, tR.Left, tR.Top + T, Lines[I]);
          taCenter:
            begin
              Canvas.TextRect(R, tR.Left + (tR.Right - tR.Left - Canvas.TextWidth(Lines[I])) div 2, tR.Top + T, Lines[I]);
            end;
          taRightJustify:
            begin
              Canvas.TextRect(R, tR.Right - Canvas.TextWidth(Lines[I]), tR.Top + T, Lines[I]);
            end;
        end;
        Inc(T, Canvas.TextHeight(Lines[I]));
      end;

      Canvas.Font.Style := OldStyles; // 确保恢复旧字体

      Inc(T, 6);
      L := 0;
      TopText := '';
      for I := Index + 1 to Lines.Count - 1 do
        TopText := TopText + Lines[I] + #13#10;
      Delete(TopText, Length(TopText) - 1, 2);
      tR := GetTextRect(Canvas, TopText, Rect(R.Left + L, R.Top + T + 6, R.Right, R.Bottom), Align, vtaTopJustify);
      T := 0;

      for I := Index + 1 to Lines.Count - 1 do // 画图片下面的文字
      begin
        case Align of
          taLeftJustify:
            Canvas.TextRect(R, tR.Left, tR.Top + T, Lines[I]);
          taCenter:
            begin
              Canvas.TextRect(R, tR.Left + (tR.Right - tR.Left - Canvas.TextWidth(Lines[I])) div 2, tR.Top + T, Lines[I]);
            end;
          taRightJustify:
            begin
              Canvas.TextRect(R, tR.Right - Canvas.TextWidth(Lines[I]), tR.Top + T, Lines[I]);
            end;

        end;
        Inc(T, Canvas.TextHeight(Lines[I]));
      end;
    end
    else // 无图片，不画黑体的 Title，按平常画
    begin
      tR := GetTextRect(Canvas, Lines.Text, R, Align, vtaCenter);
      for I := 0 to Lines.Count - 1 do
      begin
        case Align of
          taLeftJustify:
            Canvas.TextRect(R, tR.Left, tR.Top + T, Lines[I]);
          taCenter:
            begin
              Canvas.TextRect(R, tR.Left + (tR.Right - tR.Left - Canvas.TextWidth(Lines[I])) div 2, tR.Top + T, Lines[I]);
            end;
          taRightJustify:
            begin
              Canvas.TextRect(R, tR.Right - Canvas.TextWidth(Lines[I]), tR.Top + T, Lines[I]);
            end;
        end;
        Inc(T, Canvas.TextHeight(Lines[I]));
      end;
    end;
  finally
    Lines.Free;
  end;
end;

function TCnInternalHintWindow.FindCnHint: TCnHint;
begin
  Result := nil;
  if FCnHints <> nil then
    if FCnHints.Count > 0 then
      Result := TCnHint(FCnHints[0]);
end;

function TCnInternalHintWindow.GetHintPosition(WorkRect: TRect; AWidth, AHeight:
  Integer; Pos: TPoint; IsBalloonHint: Boolean): THintPosition;
var
  R: TRect;
  B: Boolean;
  Delta: Integer;
begin
  R := WorkRect;
  InflateRect(R, -10, -10);
  B := False;

  if IsBalloonHint then
    Delta := 16
  else
    Delta := 0;

  if not FModified and (FHint <> nil) then
    Result := FHint.HintPosition
  else
    Result := FHintPosition;

  case Result of
    hpUpLeft:
      B := ((AHeight + Delta - (Pos.Y - R.Top)) < 0) and ((AWidth - Delta) <= (Pos.X - R.Left));
    hpUpRight:
      B := ((AHeight + Delta - (Pos.Y - R.Top)) < 0) and ((AWidth - Delta + Pos.X) < R.Right);
    hpDownLeft:
      B := ((AHeight + Delta - (R.Bottom - Pos.Y)) < 0) and ((AWidth - Delta) <= (Pos.X - R.Left));
    hpDownRight:
      B := ((AHeight + Delta - (R.Bottom - Pos.Y)) < 0) and ((AWidth - Delta + Pos.X) < R.Right);
  end;

  if B then
    Exit;

  if (AHeight + Delta - (Pos.Y - R.Top)) < 0 then
  begin
    if (AWidth - Delta + Pos.X) < R.Right then
      Result := hpUpRight
    else
      Result := hpUpLeft;
  end
  else
  begin
    if (AWidth - Delta + Pos.X) < R.Right then
      Result := hpDownRight
    else
      Result := hpDownLeft;
  end;
end;

function TCnInternalHintWindow.GetTextRect(ACanvas: TCanvas; Text: string; R: TRect;
  HAlign: TAlignment; VAlign: TVAlignment): TRect;
var
  I, Len: Integer;
  Lines: TStrings;
  str: string;
  tR: TRect;
  intW, intH: Integer;
  OldStyles: TFontStyles;
begin
  Result := Rect(R.Left, R.Top, R.Left, R.Top);
  tR := Result;
  Lines := TStringList.Create;
  try
    Lines.Text := Text;
    OldStyles := ACanvas.Font.Style;
    for I := 0 to Lines.Count - 1 do
    begin
      str := Lines[I];
      if str <> '' then
      begin
        // 首行、无图片、并且有 Title 时，才按黑体来
        if FFirstLineAsTitle and (I = 0) and not Glyph.Empty then
        begin
          ACanvas.Font.Style := [fsBold]
        end
        else
          ACanvas.Font.Style := OldStyles;

        Len := ACanvas.TextWidth(str);
        if (tR.Right - tR.Left) < Len then
          tR.Right := tR.Left + Len;
        tR.Bottom := tR.Bottom + ACanvas.TextHeight(str);
      end;
    end;
    intW := tR.Right - tR.Left;
    intH := tR.Bottom - tR.Top;
    case HAlign of
      taLeftJustify:
        tR.Left := R.Left;
      taCenter:
        tR.Left := R.Left + Max((R.Right - R.Left - (tR.Right - tR.Left)) div 2, 0);
      taRightJustify:
        tR.Left := R.Right - (tR.Right - tR.Left);
    end;
    case VAlign of
      vtaTopJustify:
        tR.Top := R.Top + 3;
      vtaBottomJustify:
        tR.Top := R.Bottom - (tR.Bottom - tR.Top) - 1;
      vtaCenter:
        tR.Top := R.Top + Max((R.Bottom - R.Top - (tR.Bottom - tR.Top)) div 2, 0);

    end;
    tR.Right := tR.Left + intW;
    tR.Bottom := tR.Top + intH;
    Result := tR;
  finally
    Lines.Free;
    ACanvas.Font.Style := OldStyles;
  end;
end;

procedure TCnInternalHintWindow.GlyphChange(Sender: TObject);
begin
  if FUpdating then
    Exit;

  FUpdating := True;
  try
    if (FGlyph <> nil) and not Glyph.Transparent then
    begin
      Glyph.TransparentColor := Glyph.Canvas.Pixels[0, 0];
      Glyph.Transparent := True;
    end;
  finally
    FUpdating := False;
  end;
end;

procedure TCnInternalHintWindow.HintNotify(var message: TMessage);
var
  AMsg: MSG;
  HintControl: TControl;
  P: TPoint;
  PT: PPoint;
begin
  PT := PPoint(Pointer(message.LParam));
  P := PT^;
  HintControl := FindDragTarget(P, True);
  Dispose(PT);

  while (GetMessage(AMsg, 0, 0, 0)) do
  begin
    if (AMsg.message = WM_QUIT) then
    begin
      PostQuitMessage(0);
      Break;
    end
    else if ((AMsg.message >= WM_KEYFIRST) and (AMsg.message <= WM_KEYLAST))
      or ((AMsg.message = CM_ACTIVATE) or (AMsg.message = CM_DEACTIVATE)) or
      (AMsg.message = CM_APPKEYDOWN) or (AMsg.message = CM_APPSYSCOMMAND) or
      (AMsg.message = WM_COMMAND) or ((AMsg.message > WM_MOUSEMOVE) and
      (AMsg.message <= WM_MOUSELAST)) or (AMsg.message = WM_NCMOUSEMOVE) then
    begin
      PostMessage(AMsg.hwnd, AMsg.message, AMsg.wParam, AMsg.lParam);
      Break;
    end
    else if (AMsg.message = WM_MOUSEMOVE) and (HintControl <> FindDragTarget(Mouse.CursorPos, True)) then
    begin
      PostMessage(AMsg.hwnd, AMsg.message, AMsg.wParam, AMsg.lParam);
      Break;
    end
    else
    begin
      TranslateMessage(AMsg);
      DispatchMessage(AMsg);
    end;
  end;

//  DoCancelHint;
end;

procedure TCnInternalHintWindow.Paint;
begin
  BitBlt(Canvas.Handle, 0, 0, FBitmap.Width, FBitmap.Height,
    FBitmap.Canvas.Handle, 0, 0, SRCCOPY);
end;

procedure TCnInternalHintWindow.SetPosition(const Value: THintPosition);
begin
  if FHintPosition <> Value then
  begin
    FHintPosition := Value;
    FModified := True;
  end;
end;

procedure TCnInternalHintWindow.SetGlyph(const Value: TBitmap);
begin
  FGlyph.Assign(Value);
end;

procedure TCnInternalHintWindow.DoCancelHint;
begin
  if Assigned(FOnCancelHint) then
    FOnCancelHint(Self);
end;

procedure TCnInternalHintWindow.HintTimer(Sender: TObject);
begin
  FTimer.Enabled := False;
  DoCancelHint;
  ReleaseHandle;
end;

{ TCnHint }

procedure TCnHint.CMFontChanged(var Message: TMessage);
begin
  inherited;
  Application.ShowHint := not Application.ShowHint;
  Application.ShowHint := not Application.ShowHint;
  UpdateHintWindowFont;
end;

constructor TCnHint.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FBackColor := clInfoBk; //$F0A07D;
  FBorderColor := clBlack;
  FHintPosition := hpDownRight;
  FAlignment := taLeftJustify;

  FFont := TFont.Create;
  FFont.Color := clBlack;
  FGlyph := TBitmap.Create;

  FGlyph.OnChange := GlyphChange;

  if not (csDesigning in ComponentState) then
  begin
    HintWindowClass := TCnInternalHintWindow;
    Application.ShowHint := not Application.ShowHint;
    Application.ShowHint := not Application.ShowHint;
    UpdateHintWindowFont;
  end;

  if FCnHints = nil then
    FCnHints := TList.Create;
  FCnHints.Add(Self);
end;

destructor TCnHint.Destroy;
begin
  if FCnHints <> nil then
    FCnHints.Remove(Self);
  FFont.Free;
  FGlyph.OnChange := nil;
  FGlyph.Free;
  inherited;
end;

procedure TCnHint.GlyphChange(Sender: TObject);
begin
  if FGlyph <> nil then
  begin
    Glyph.TransparentColor := Glyph.Canvas.Pixels[0, 0];
    Glyph.Transparent := True;
  end;
end;

procedure TCnHint.Loaded;
begin
  if not (csDesigning in ComponentState) then
  begin
    inherited Loaded;
    HintWindowClass := TCnInternalHintWindow;
    Application.ShowHint := not Application.ShowHint;
    Application.ShowHint := not Application.ShowHint;
    UpdateHintWindowFont;
  end;
end;

procedure TCnHint.SetFont(const Value: TFont);
begin
  FFont.Assign(Value);
  Application.ShowHint := not Application.ShowHint;
  Application.ShowHint := not Application.ShowHint;
  UpdateHintWindowFont;
end;

procedure TCnHint.SetGlyph(const Value: TBitmap);
begin
  FGlyph.Assign(Value);
end;

procedure TCnHint.UpdateHintWindowFont;
var
  I: Integer;
begin
  if FCnHintWindows <> nil then
    if FCnHintWindows.Count > 0 then
      for I := 0 to FCnHintWindows.Count - 1 do
        if Application.Components[I] is TCnInternalHintWindow then
          TCnInternalHintWindow(FCnHintWindows[I]).Canvas.Font.Assign(FFont);
end;

{ TCnHintWindow }

procedure TCnHintWindow.ActivateHint(const AHint: string; const ATitle: string);
begin
  FHintWindow.ActivateHintFromPos(Mouse.CursorPos, AHint, ATitle);
end;

procedure TCnHintWindow.ActivateHintFromPos(const HintPos: TPoint;
  const AHint, ATitle: string; HidePause: Integer);
begin
  FHintWindow.ActivateHintFromPos(HintPos, AHint, ATitle, HidePause);
end;

constructor TCnHintWindow.Create(AOwner: TComponent);
begin
  inherited;
  FHintWindow := TCnInternalHintWindow.Create(Self);
  FHintWindow.OnCancelHint := HintWindowCancelHint;
end;

destructor TCnHintWindow.Destroy;
begin
  FHintWindow.Free;
  inherited;
end;

function TCnHintWindow.GetAlignment: TAlignment;
begin
  Result := FHintWindow.Alignment;
end;

function TCnHintWindow.GetGlyph: TBitmap;
begin
  Result := FHintWindow.Glyph;
end;

function TCnHintWindow.GetHintPosition: THintPosition;
begin
  Result := FHintWindow.HintPosition;
end;

function TCnHintWindow.GetHintStyle: THintStyle;
begin
  Result := FHintWindow.HintStyle;
end;

procedure TCnHintWindow.HintWindowCancelHint(Sender: TObject);
begin
  if Assigned(FOnCancelHint) then
    FOnCancelHint(FHintWindow);
end;

procedure TCnHintWindow.ReleaseHandle;
begin
  FHintWindow.ReleaseHandle;
end;

procedure TCnHintWindow.SetAlignment(const Value: TAlignment);
begin
  FHintWindow.Alignment := Value;
end;

procedure TCnHintWindow.SetGlyph(const Value: TBitmap);
begin
  FHintWindow.Glyph.Assign(Value);
end;

procedure TCnHintWindow.SetHintStyle(const Value: THintStyle);
begin
  FHintWindow.HintStyle := Value;
end;

procedure TCnHintWindow.SetPosition(const Value: THintPosition);
begin
  FHintWindow.HintPosition := Value;
end;

initialization

finalization
  FreeAndNil(FCnHints);
  FreeAndNil(FCnHintWindows);
  
end.

