2126 lines
63 KiB
ObjectPascal
2126 lines
63 KiB
ObjectPascal
{-----------------------------------------------------------------------------
|
|
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: JvBalloonHint.PAS, released on 2001-02-28.
|
|
|
|
The Initial Developer of the Original Code is Remko Bonte <remkobonte att myrealbox dott com>
|
|
Portions created by Remko Bonte are Copyright (C) 2002 Remko Bonte.
|
|
All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
|
|
2006-01-17 - J. Vignoles - Added support for Unicode hint and header
|
|
|
|
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:
|
|
* Only dropdown shadow for windows xp systems.
|
|
* Only custom animation for windows xp systems, because of use of window region.
|
|
-----------------------------------------------------------------------------}
|
|
// $Id: JvBalloonHint.pas 11300 2007-05-27 20:28:24Z ahuser $
|
|
|
|
unit JvBalloonHint;
|
|
|
|
{$I jvcl.inc}
|
|
{$I windowsonly.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFDEF UNITVERSIONING}
|
|
JclUnitVersioning,
|
|
{$ENDIF UNITVERSIONING}
|
|
Windows, Messages, Classes, Controls, Graphics, Forms, ImgList,
|
|
JvComponentBase;
|
|
|
|
const
|
|
cJvBallonHintVisibleTimeDefault = 5000;
|
|
|
|
type
|
|
TJvStemSize = (ssSmall, ssNormal, ssLarge);
|
|
TJvIconKind = (ikCustom, ikNone, ikApplication, ikError, ikInformation, ikQuestion, ikWarning);
|
|
TJvBalloonOption = (boUseDefaultHeader, boUseDefaultIcon, boUseDefaultImageIndex,
|
|
boShowCloseBtn, boCustomAnimation, boPlaySound);
|
|
TJvBalloonOptions = set of TJvBalloonOption;
|
|
TJvApplicationHintOption = (ahShowHeaderInHint, ahShowIconInHint, ahPlaySound);
|
|
TJvApplicationHintOptions = set of TJvApplicationHintOption;
|
|
TJvBalloonPosition = (bpAuto, bpLeftDown, bpRightDown, bpLeftUp, bpRightUp);
|
|
TJvAnimationStyle = (atNone, atSlide, atRoll, atRollHorNeg, atRollHorPos, atRollVerNeg,
|
|
atRollVerPos, atSlideHorNeg, atSlideHorPos, atSlideVerNeg, atSlideVerPos, atCenter, atBlend);
|
|
|
|
TJvBalloonHint = class;
|
|
|
|
PHintData = ^THintData;
|
|
THintData = record
|
|
RAnchorWindow: TCustomForm;
|
|
{ Position of the top-left edge of the window balloon inside the client
|
|
rect of the anchor window (Used to move the balloon window if the
|
|
anchor window moves): }
|
|
RAnchorPosition: TPoint;
|
|
{ Position of the stem point inside the client rect of the balloon window
|
|
(Used the check on resize of the anchor window whether the stem point is
|
|
still inside the balloon window): }
|
|
RStemPointPosition: TPoint;
|
|
RUTF8Header: string;
|
|
RUTF8Hint: string;
|
|
RIconKind: TJvIconKind;
|
|
RImageIndex: TImageIndex;
|
|
RVisibleTime: Integer;
|
|
RShowCloseBtn: Boolean;
|
|
RAnimationStyle: TJvAnimationStyle;
|
|
RAnimationTime: Cardinal;
|
|
{ If the position of the balloon needs to be changed - for example if
|
|
DefaultBalloonPosition = bpAuto - RSwitchHeight indicates how much we
|
|
change the vertical position; if the balloon is an application hint,
|
|
RSwitchHeight is the height of the cursor; if the balloon is attached to
|
|
a control, RSwitchHeight is the height of that control }
|
|
RSwitchHeight: Integer;
|
|
end;
|
|
|
|
TJvBalloonWindow = class(THintWindow)
|
|
private
|
|
FCurrentPosition: TJvBalloonPosition;
|
|
FDeltaY: Integer;
|
|
FSwitchHeight: Integer;
|
|
FShowIcon: Boolean;
|
|
FShowHeader: Boolean;
|
|
FMsg: WideString;
|
|
FHeader: WideString;
|
|
FMessageTop: Integer;
|
|
FTipHeight: Integer;
|
|
FTipWidth: Integer;
|
|
FTipDelta: Integer;
|
|
FImageSize: TSize;
|
|
function GetStemPointPosition: TPoint;
|
|
function GetStemPointPositionInRect(const ARect: TRect): TPoint;
|
|
function MultiLineWidth(const Value: string): Integer;
|
|
protected
|
|
procedure CMTextChanged(var Msg: TMessage); message CM_TEXTCHANGED;
|
|
procedure CMShowingChanged(var Msg: TMessage); message CM_SHOWINGCHANGED;
|
|
procedure WMEraseBkgnd(var Msg: TWMEraseBkgnd); message WM_ERASEBKGND;
|
|
procedure CreateParams(var Params: TCreateParams); override;
|
|
{$IFDEF COMPILER6_UP}
|
|
procedure NCPaint(DC: HDC); override;
|
|
{$ELSE}
|
|
procedure WMNCPaint(var Msg: TMessage); message WM_NCPAINT;
|
|
{$ENDIF COMPILER6_UP}
|
|
procedure Paint; override;
|
|
|
|
function CreateRegion: HRGN;
|
|
procedure UpdateRegion;
|
|
procedure CalcAutoPosition(var ARect: TRect);
|
|
procedure CheckPosition(var ARect: TRect);
|
|
|
|
function CalcOffset(const ARect: TRect): TPoint;
|
|
function CalcHeaderRect(MaxWidth: Integer): TRect; virtual;
|
|
function CalcMsgRect(MaxWidth: Integer): TRect; virtual;
|
|
procedure Init(AData: Pointer); virtual;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
procedure ActivateHint(Rect: TRect; const AHint: string); override;
|
|
function CalcHintRect(MaxWidth: Integer; const AHint: string;
|
|
AData: Pointer): TRect; override;
|
|
function CalcHintRectUTF8(MaxWidth: Integer; const AUTF8Hint: string;
|
|
AData: Pointer): TRect; virtual;
|
|
function CalcHintRectW(MaxWidth: Integer; const AHint: WideString;
|
|
AData: Pointer): TRect; virtual;
|
|
property StemPointPosition: TPoint read GetStemPointPosition;
|
|
end;
|
|
|
|
TJvBalloonWindowEx = class(TJvBalloonWindow)
|
|
private
|
|
FCtrl: TJvBalloonHint;
|
|
FCloseBtnRect: TRect;
|
|
FCloseState: Cardinal;
|
|
FImageIndex: TImageIndex;
|
|
FIconKind: TJvIconKind;
|
|
FAnimationTime: Cardinal;
|
|
FAnimationStyle: TJvAnimationStyle;
|
|
FShowCloseBtn: Boolean;
|
|
FIsAnchored: Boolean;
|
|
protected
|
|
procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
|
|
procedure WMMouseMove(var Msg: TWMMouseMove); message WM_MOUSEMOVE;
|
|
procedure WMLButtonDown(var Msg: TWMLButtonDown); message WM_LBUTTONDOWN;
|
|
procedure WMLButtonUp(var Msg: TWMLButtonUp); message WM_LBUTTONUP;
|
|
procedure WMActivateApp(var Msg: TWMActivateApp); message WM_ACTIVATEAPP;
|
|
|
|
procedure Paint; override;
|
|
|
|
{ Either calls NormalizeTopMost or RestoreTopMost depending on whether the
|
|
anchor window has focus }
|
|
procedure EnsureTopMost;
|
|
{ Sets the balloon on top of anchor window; but below other windows }
|
|
procedure NormalizeTopMost;
|
|
{ Sets the balloon top most }
|
|
procedure RestoreTopMost;
|
|
|
|
procedure InternalActivateHint(var Rect: TRect; const AHint: string);
|
|
procedure MoveWindow(NewPos: TPoint);
|
|
procedure ChangeCloseState(const AState: Cardinal);
|
|
|
|
function CalcHeaderRect(MaxWidth: Integer): TRect; override;
|
|
procedure Init(AData: Pointer); override;
|
|
end;
|
|
|
|
TJvBalloonHint = class(TJvComponent)
|
|
private
|
|
FHint: TJvBalloonWindowEx;
|
|
FActive: Boolean;
|
|
FOptions: TJvBalloonOptions;
|
|
FImages: TCustomImageList;
|
|
FDefaultHeader: WideString;
|
|
FDefaultIcon: TJvIconKind;
|
|
FDefaultImageIndex: TImageIndex;
|
|
FData: THintData;
|
|
FApplicationHintOptions: TJvApplicationHintOptions;
|
|
FDefaultBalloonPosition: TJvBalloonPosition;
|
|
FCustomAnimationTime: Cardinal;
|
|
FCustomAnimationStyle: TJvAnimationStyle;
|
|
|
|
FOnBalloonClick: TNotifyEvent;
|
|
FOnClose: TNotifyEvent;
|
|
FOnCloseBtnClick: TCloseQueryEvent;
|
|
FOnDblClick: TNotifyEvent;
|
|
FOnMouseDown: TMouseEvent;
|
|
FOnMouseMove: TMouseMoveEvent;
|
|
FOnMouseUp: TMouseEvent;
|
|
|
|
FHandle: THandle;
|
|
FTimerActive: Boolean;
|
|
FMaxWidth: Integer;
|
|
|
|
function GetHandle: THandle;
|
|
function GetUseBalloonAsApplicationHint: Boolean;
|
|
procedure SetImages(const Value: TCustomImageList);
|
|
procedure SetOptions(const Value: TJvBalloonOptions);
|
|
procedure SetUseBalloonAsApplicationHint(const Value: Boolean);
|
|
protected
|
|
function HookProc(var Msg: TMessage): Boolean;
|
|
procedure Hook;
|
|
procedure UnHook;
|
|
|
|
procedure HandleMouseDown(Sender: TObject; Button: TMouseButton;
|
|
Shift: TShiftState; X, Y: Integer);
|
|
procedure HandleMouseUp(Sender: TObject; Button: TMouseButton;
|
|
Shift: TShiftState; X, Y: Integer);
|
|
procedure HandleMouseMove(Sender: TObject; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
procedure HandleClick(Sender: TObject);
|
|
procedure HandleDblClick(Sender: TObject);
|
|
function HandleCloseBtnClick: Boolean;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
|
|
procedure StartHintTimer(Value: Integer);
|
|
procedure StopHintTimer;
|
|
|
|
procedure InternalActivateHintPos;
|
|
procedure InternalActivateHint(ACtrl: TControl);
|
|
|
|
procedure WndProc(var Msg: TMessage);
|
|
|
|
property Handle: THandle read GetHandle;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
procedure ActivateHint(ACtrl: TControl; const AHint: WideString; const AHeader: WideString = '';
|
|
const VisibleTime: Integer = cJvBallonHintVisibleTimeDefault); overload;
|
|
procedure ActivateHint(ACtrl: TControl; const AHint: WideString; const AImageIndex: TImageIndex;
|
|
const AHeader: WideString = ''; const VisibleTime: Integer = cJvBallonHintVisibleTimeDefault); overload;
|
|
procedure ActivateHint(ACtrl: TControl; const AHint: WideString; const AIconKind: TJvIconKind;
|
|
const AHeader: WideString = ''; const VisibleTime: Integer = cJvBallonHintVisibleTimeDefault); overload;
|
|
procedure ActivateHintPos(AAnchorWindow: TCustomForm; AAnchorPosition: TPoint;
|
|
const AHeader, AHint: WideString; const VisibleTime: Integer = cJvBallonHintVisibleTimeDefault;
|
|
const AIconKind: TJvIconKind = ikInformation; const AImageIndex: TImageIndex = -1);
|
|
procedure ActivateHintRect(ARect: TRect; const AHeader, AHint: WideString;
|
|
const VisibleTime: Integer = cJvBallonHintVisibleTimeDefault; const AIconKind: TJvIconKind = ikInformation;
|
|
const AImageIndex: TImageIndex = -1);
|
|
procedure CancelHint;
|
|
|
|
property Active: Boolean read FActive;
|
|
published
|
|
property CustomAnimationStyle: TJvAnimationStyle read FCustomAnimationStyle write
|
|
FCustomAnimationStyle default atBlend;
|
|
property CustomAnimationTime: Cardinal read FCustomAnimationTime write FCustomAnimationTime
|
|
default 100;
|
|
property DefaultBalloonPosition: TJvBalloonPosition read FDefaultBalloonPosition write
|
|
FDefaultBalloonPosition default bpAuto;
|
|
property DefaultImageIndex: TImageIndex read FDefaultImageIndex write FDefaultImageIndex
|
|
default -1;
|
|
property DefaultHeader: WideString read FDefaultHeader write FDefaultHeader;
|
|
property DefaultIcon: TJvIconKind read FDefaultIcon write FDefaultIcon default ikInformation;
|
|
property Images: TCustomImageList read FImages write SetImages;
|
|
property Options: TJvBalloonOptions read FOptions write SetOptions default [boShowCloseBtn];
|
|
property ApplicationHintOptions: TJvApplicationHintOptions read FApplicationHintOptions write
|
|
FApplicationHintOptions default [ahShowHeaderInHint, ahShowIconInHint];
|
|
property UseBalloonAsApplicationHint: Boolean read GetUseBalloonAsApplicationHint write
|
|
SetUseBalloonAsApplicationHint default False;
|
|
|
|
property MaxWidth: Integer read FMaxWidth write FMaxWidth default 0;
|
|
|
|
property OnBalloonClick: TNotifyEvent read FOnBalloonClick write FOnBalloonClick;
|
|
property OnCloseBtnClick: TCloseQueryEvent read FOnCloseBtnClick write FOnCloseBtnClick;
|
|
property OnClose: TNotifyEvent read FOnClose write FOnClose;
|
|
property OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick;
|
|
property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown;
|
|
property OnMouseMove: TMouseMoveEvent read FOnMouseMove write FOnMouseMove;
|
|
property OnMouseUp: TMouseEvent read FOnMouseUp write FOnMouseUp;
|
|
end;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
const
|
|
UnitVersioning: TUnitVersionInfo = (
|
|
RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvBalloonHint.pas $';
|
|
Revision: '$Revision: 11300 $';
|
|
Date: '$Date: 2007-05-27 22:28:24 +0200 (dim., 27 mai 2007) $';
|
|
LogPath: 'JVCL\run'
|
|
);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysUtils, Math,
|
|
Registry, CommCtrl, MMSystem,
|
|
ComCtrls, // needed for GetComCtlVersion
|
|
JvJVCLUtils, JvThemes, JvWndProcHook, JvResources, JvWin32,
|
|
JclUnicode, JvVCL5Utils;
|
|
|
|
const
|
|
{ TJvStemSize = (ssSmall, ssNormal, ssLarge);
|
|
ssLarge isn't used (yet)
|
|
}
|
|
cTipHeight: array [TJvStemSize] of Integer = (8, 16, 24);
|
|
cTipWidth: array [TJvStemSize] of Integer = (8, 16, 24);
|
|
cTipDelta: array [TJvStemSize] of Integer = (16, 15, 17);
|
|
DefaultTextFlags: Longint = DT_LEFT or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX;
|
|
|
|
// Unicode wrapping around DrawTextW so that if ran under Win98/Me, it
|
|
// continues to work.
|
|
type
|
|
TDrawTextW = function(hDC: HDC; lpString: PWideChar; nCount: Integer;
|
|
var lpRect: TRect; uFormat: UINT): Integer; stdcall;
|
|
|
|
var
|
|
_DrawTextW: TDrawTextW = nil;
|
|
|
|
procedure InitUnicodeWrap;
|
|
var
|
|
UserHandle: HMODULE;
|
|
begin
|
|
{ The system DLLs for Windows 98 have export symbols for wide character
|
|
functions as well, but they all return FALSE, and GetLastError would return
|
|
ERROR_CALL_NOT_IMPLEMENTED (120), so don't try to load DrawTextW for
|
|
Windows 98 }
|
|
|
|
if Win32Platform = VER_PLATFORM_WIN32_NT then
|
|
begin
|
|
{ All Windows programs already load user32.dll so we can use GetModuleHandle }
|
|
UserHandle := GetModuleHandle('USER32');
|
|
if UserHandle <> 0 then
|
|
@_DrawTextW := GetProcAddress(UserHandle, 'DrawTextW');
|
|
end;
|
|
end;
|
|
|
|
function DrawTextW(hDC: HDC; const WS: WideString; var lpRect: TRect; uFormat: UINT): Integer;
|
|
var
|
|
S: string;
|
|
begin
|
|
if Assigned(_DrawTextW) then
|
|
Result := _DrawTextW(hDC, PWideChar(WS), Length(WS), lpRect, uFormat)
|
|
else
|
|
begin
|
|
{ The Microsoft Layer for Unicode dll UNICOWS.DLL does probably the same as
|
|
the following: }
|
|
S := WideCharLenToString(PWideChar(WS), Length(WS));
|
|
Result := DrawTextA(hDC, PChar(S), Length(S), lpRect, uFormat);
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF COMPILER5}
|
|
|
|
type
|
|
TAnimateWindowProc = function(hWnd: HWND; dwTime: DWORD; dwFlags: DWORD): BOOL; stdcall;
|
|
|
|
var
|
|
AnimateWindowProc: TAnimateWindowProc = nil;
|
|
|
|
procedure InitD5Controls;
|
|
var
|
|
UserHandle: HMODULE;
|
|
begin
|
|
if not Assigned(AnimateWindowProc) then
|
|
begin
|
|
UserHandle := GetModuleHandle('USER32');
|
|
if UserHandle <> 0 then
|
|
@AnimateWindowProc := GetProcAddress(UserHandle, 'AnimateWindow');
|
|
end;
|
|
end;
|
|
|
|
{$ENDIF COMPILER5}
|
|
|
|
function WorkAreaRect: TRect;
|
|
begin
|
|
SystemParametersInfo(SPI_GETWORKAREA, 0, @Result, 0);
|
|
end;
|
|
|
|
function DesktopRect: TRect;
|
|
begin
|
|
Result := Rect(GetSystemMetrics(SM_XVIRTUALSCREEN),
|
|
GetSystemMetrics(SM_YVIRTUALSCREEN),
|
|
GetSystemMetrics(SM_CXVIRTUALSCREEN),
|
|
GetSystemMetrics(SM_CYVIRTUALSCREEN));
|
|
end;
|
|
|
|
function IsWinXP_UP: Boolean;
|
|
begin
|
|
Result := (Win32Platform = VER_PLATFORM_WIN32_NT) and
|
|
((Win32MajorVersion > 5) or
|
|
(Win32MajorVersion = 5) and (Win32MinorVersion >= 1));
|
|
end;
|
|
|
|
{$IFDEF COMPILER6_UP}
|
|
function InternalClientToParent(AControl: TControl; const Point: TPoint;
|
|
AParent: TWinControl): TPoint;
|
|
begin
|
|
Result := AControl.ClientToParent(Point, AParent);
|
|
end;
|
|
{$ELSE}
|
|
function InternalClientToParent(AControl: TControl; const Point: TPoint;
|
|
AParent: TWinControl): TPoint;
|
|
var
|
|
LParent: TWinControl;
|
|
begin
|
|
if AParent = nil then
|
|
AParent := AControl.Parent;
|
|
if AParent = nil then
|
|
raise EInvalidOperation.CreateResFmt(@RsEParentRequired, [AControl.Name]);
|
|
Result := Point;
|
|
Inc(Result.X, AControl.Left);
|
|
Inc(Result.Y, AControl.Top);
|
|
LParent := AControl.Parent;
|
|
while LParent <> nil do
|
|
begin
|
|
if LParent.Parent <> nil then
|
|
begin
|
|
Inc(Result.X, LParent.Left);
|
|
Inc(Result.Y, LParent.Top);
|
|
end;
|
|
if LParent = AParent then
|
|
Break
|
|
else
|
|
LParent := LParent.Parent;
|
|
end;
|
|
if LParent = nil then
|
|
raise EInvalidOperation.CreateResFmt(@RsEParentGivenNotAParent, [AControl.Name]);
|
|
end;
|
|
{$ENDIF COMPILER6_UP}
|
|
|
|
type
|
|
TGlobalCtrl = class(TComponent)
|
|
private
|
|
FBkColor: TColor;
|
|
FCtrls: TList;
|
|
FDefaultImages: TImageList;
|
|
FNeedUpdateBkColor: Boolean;
|
|
FOldHintWindowClass: THintWindowClass;
|
|
FSounds: array [TJvIconKind] of string;
|
|
FUseBalloonAsApplicationHint: Boolean;
|
|
FDesigning: Boolean;
|
|
function GetMainCtrl: TJvBalloonHint;
|
|
procedure GetDefaultImages;
|
|
procedure GetDefaultSounds;
|
|
procedure SetBkColor(const Value: TColor);
|
|
procedure SetUseBalloonAsApplicationHint(const Value: Boolean);
|
|
protected
|
|
procedure Add(ABalloonHint: TJvBalloonHint);
|
|
procedure Remove(ABalloonHint: TJvBalloonHint);
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
class function Instance: TGlobalCtrl;
|
|
function HintImageSize: TSize; overload;
|
|
function HintImageSize(const AIconKind: TJvIconKind;
|
|
const AImageIndex: TImageIndex): TSize; overload;
|
|
procedure DrawHintImage(Canvas: TCanvas; X, Y: Integer; const ABkColor: TColor); overload;
|
|
procedure DrawHintImage(Canvas: TCanvas; X, Y: Integer; const AIconKind: TJvIconKind;
|
|
const AImageIndex: TImageIndex; const ABkColor: TColor); overload;
|
|
procedure PlaySound(const AIconKind: TJvIconKind);
|
|
|
|
property BkColor: TColor read FBkColor write SetBkColor;
|
|
property MainCtrl: TJvBalloonHint read GetMainCtrl;
|
|
property UseBalloonAsApplicationHint: Boolean read FUseBalloonAsApplicationHint
|
|
write SetUseBalloonAsApplicationHint;
|
|
end;
|
|
|
|
var
|
|
GGlobalCtrl: TGlobalCtrl = nil;
|
|
{ A TJvBalloonHint may be needed, while there isn't an instance of it around.
|
|
For example, if the user sets HintWindowClass to TJvBalloonWindow.
|
|
}
|
|
GMainCtrl: TJvBalloonHint = nil;
|
|
|
|
//=== { TJvBalloonWindow } ===================================================
|
|
|
|
constructor TJvBalloonWindow.Create(AOwner: TComponent);
|
|
begin
|
|
{$IFDEF COMPILER5}
|
|
InitD5Controls;
|
|
{$ENDIF COMPILER5}
|
|
inherited Create(AOwner);
|
|
ControlStyle := [csCaptureMouse, csClickEvents, csDoubleClicks];
|
|
end;
|
|
|
|
function TJvBalloonWindow.MultiLineWidth(const Value: string): Integer;
|
|
var
|
|
W: Integer;
|
|
P, Start: PChar;
|
|
S: string;
|
|
begin
|
|
Result := 0;
|
|
P := Pointer(Value);
|
|
if P <> nil then
|
|
while P^ <> #0 do
|
|
begin
|
|
Start := P;
|
|
while not (P^ in [#0, #10, #13]) do
|
|
P := StrNextChar(P);
|
|
SetString(S, Start, P - Start);
|
|
W := Self.Canvas.TextWidth(S);
|
|
if W > Result then
|
|
Result := W;
|
|
if P^ = #13 then Inc(P);
|
|
if P^ = #10 then Inc(P);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindow.ActivateHint(Rect: TRect; const AHint: string);
|
|
var
|
|
Delta: Integer;
|
|
begin
|
|
if HandleAllocated and IsWindowVisible(Handle) then
|
|
ShowWindow(Handle, SW_HIDE);
|
|
|
|
if UseRightToLeftAlignment then
|
|
begin
|
|
// Remove the offset set by TApplication.ActivateHint
|
|
Delta := MultiLineWidth(AHint) + 5;
|
|
Inc(Rect.Left, Delta);
|
|
Inc(Rect.Right, Delta);
|
|
end;
|
|
CheckPosition(Rect);
|
|
|
|
Inc(Rect.Bottom, 4);
|
|
UpdateBoundsRect(Rect);
|
|
Dec(Rect.Bottom, 4);
|
|
|
|
UpdateRegion;
|
|
|
|
with TGlobalCtrl.Instance do
|
|
if ahPlaySound in MainCtrl.ApplicationHintOptions then
|
|
PlaySound(MainCtrl.DefaultIcon);
|
|
inherited ActivateHint(Rect, AHint);
|
|
end;
|
|
|
|
procedure TJvBalloonWindow.CalcAutoPosition(var ARect: TRect);
|
|
var
|
|
NewPosition: TJvBalloonPosition;
|
|
ScreenRect: TRect;
|
|
LStemPointPosition: TPoint;
|
|
begin
|
|
{ bpAuto returns the same value as bpLeftDown; bpLeftDown is choosen
|
|
arbitrary }
|
|
FCurrentPosition := bpLeftDown;
|
|
ScreenRect := WorkAreaRect;
|
|
|
|
{ Note: 2*(Left + Width div 2) = 2*(Left + (Right-Left) div 2) ~=
|
|
2*Left + (Right-Left) = Left + Right;
|
|
|
|
Thus multiply everything with 2
|
|
|
|
Monitor:
|
|
|---------------|
|
|
| | |
|
|
| 1 | 2 |
|
|
| | |
|
|
|---------------|
|
|
| | |
|
|
| 3 | 4 |
|
|
| | |
|
|
|---------------|
|
|
|
|
}
|
|
with GetStemPointPositionInRect(ARect) do
|
|
LStemPointPosition := Point(X * 2, Y * 2);
|
|
|
|
if LStemPointPosition.Y < ScreenRect.Top + ScreenRect.Bottom then
|
|
begin
|
|
if LStemPointPosition.X < ScreenRect.Left + ScreenRect.Right then
|
|
{ 1 }
|
|
NewPosition := bpLeftUp
|
|
else
|
|
{ 2 }
|
|
NewPosition := bpRightUp;
|
|
end
|
|
else
|
|
begin
|
|
if LStemPointPosition.X < ScreenRect.Left + ScreenRect.Right then
|
|
{ 3 }
|
|
NewPosition := bpLeftDown
|
|
else
|
|
{ 4 }
|
|
NewPosition := bpRightDown;
|
|
end;
|
|
|
|
if NewPosition <> FCurrentPosition then
|
|
begin
|
|
{ Reset the offset.. }
|
|
with CalcOffset(ARect) do
|
|
OffsetRect(ARect, -X, -Y);
|
|
|
|
FCurrentPosition := NewPosition;
|
|
|
|
{ ..and set the offset }
|
|
with CalcOffset(ARect) do
|
|
OffsetRect(ARect, X, Y);
|
|
end;
|
|
|
|
case FCurrentPosition of
|
|
bpLeftDown, bpRightDown:
|
|
FDeltaY := FTipHeight;
|
|
bpLeftUp, bpRightUp:
|
|
FDeltaY := 0;
|
|
end;
|
|
end;
|
|
|
|
function TJvBalloonWindow.CalcHeaderRect(MaxWidth: Integer): TRect;
|
|
begin
|
|
if FShowHeader then
|
|
begin
|
|
Result := Rect(0, 0, MaxWidth, 0);
|
|
Canvas.Font := Screen.HintFont;
|
|
Canvas.Font.Style := Canvas.Font.Style + [fsBold];
|
|
DrawTextW(Canvas.Handle, FHeader, Result,
|
|
DT_CALCRECT or DefaultTextFlags or DrawTextBiDiModeFlagsReadingOnly);
|
|
|
|
{ Other }
|
|
Inc(Result.Right, 13);
|
|
Inc(Result.Bottom, 11);
|
|
|
|
if FShowIcon then
|
|
with FImageSize do
|
|
begin
|
|
{ Include image }
|
|
Inc(Result.Right, cx + 8);
|
|
Result.Bottom := Max(Result.Bottom, cy + 11);
|
|
end;
|
|
end
|
|
else
|
|
if FShowIcon then
|
|
with FImageSize do
|
|
Result := Rect(0, 0, cx + 11, cy + 11)
|
|
else
|
|
SetRectEmpty(Result);
|
|
end;
|
|
|
|
function TJvBalloonWindow.CalcHintRect(MaxWidth: Integer; const AHint: string;
|
|
AData: Pointer): TRect;
|
|
begin
|
|
// Mantis 3855: CalcHintRect is called by the VCL code and gives a non
|
|
// UTF-8 string. However, this string may contain characters above 127 which
|
|
// would then be interpreted as UTF-8 markers. So when you are sure the
|
|
// string for the hint is UTF-8, use CalcHintRectUTF8 below. This is what is
|
|
// done by the TJvBalloonHint.InternalActivateHintPos code.
|
|
// In any case the CalcHintRectW function is called in the end.
|
|
Result := CalcHintRectW(MaxWidth, WideString(AHint), AData);
|
|
end;
|
|
|
|
function TJvBalloonWindow.CalcHintRectUTF8(MaxWidth: Integer;
|
|
const AUTF8Hint: string; AData: Pointer): TRect;
|
|
begin
|
|
Result := CalcHintRectW(MaxWidth, UTF8ToWideString(AUTF8Hint), AData);
|
|
end;
|
|
|
|
function TJvBalloonWindow.CalcHintRectW(MaxWidth: Integer;
|
|
const AHint: WideString; AData: Pointer): TRect;
|
|
var
|
|
MsgRect, HeaderRect: TRect;
|
|
StemSize: TJvStemSize;
|
|
begin
|
|
Init(AData);
|
|
|
|
FMsg := AHint;
|
|
|
|
{ Calc HintRect }
|
|
MsgRect := CalcMsgRect(MaxWidth);
|
|
|
|
{ Calc HeaderRect }
|
|
HeaderRect := CalcHeaderRect(MaxWidth);
|
|
if IsRectEmpty(HeaderRect) then
|
|
begin
|
|
HeaderRect.Bottom := 9;
|
|
FMessageTop := 7;
|
|
StemSize := ssSmall;
|
|
end
|
|
else
|
|
begin
|
|
Inc(HeaderRect.Right, 12);
|
|
FMessageTop := HeaderRect.Bottom + 1;
|
|
StemSize := ssNormal;
|
|
end;
|
|
|
|
FTipHeight := cTipHeight[StemSize];
|
|
FTipWidth := cTipWidth[StemSize];
|
|
FTipDelta := cTipDelta[StemSize];
|
|
|
|
{ Combine }
|
|
Result := Rect(0, 0, Max(MsgRect.Right, HeaderRect.Right),
|
|
HeaderRect.Bottom + MsgRect.Bottom + FTipHeight + 13);
|
|
|
|
with CalcOffset(Result) do
|
|
OffsetRect(Result, X, Y);
|
|
{ bpAuto returns the same value as bpLeftDown; bpLeftDown is choosen
|
|
arbitrary }
|
|
case FCurrentPosition of
|
|
bpAuto, bpLeftDown, bpRightDown:
|
|
FDeltaY := FTipHeight;
|
|
bpLeftUp, bpRightUp:
|
|
FDeltaY := 0;
|
|
end;
|
|
end;
|
|
|
|
function TJvBalloonWindow.CalcMsgRect(MaxWidth: Integer): TRect;
|
|
begin
|
|
if FMsg > '' then
|
|
begin
|
|
Result := Rect(0, 0, MaxWidth, 0);
|
|
Canvas.Font := Screen.HintFont;
|
|
// Canvas.Font.Style := Canvas.Font.Style - [fsBold];
|
|
DrawTextW(Canvas.Handle, FMsg, Result,
|
|
DT_CALCRECT or DefaultTextFlags or DrawTextBiDiModeFlagsReadingOnly);
|
|
|
|
{ Other }
|
|
Inc(Result.Right, 27);
|
|
end
|
|
else
|
|
SetRectEmpty(Result);
|
|
end;
|
|
|
|
function TJvBalloonWindow.CalcOffset(const ARect: TRect): TPoint;
|
|
begin
|
|
with ARect do
|
|
case FCurrentPosition of
|
|
{ bpAuto returns the same value as bpLeftDown; bpLeftDown is choosen
|
|
arbitrary }
|
|
bpAuto, bpLeftDown:
|
|
Result := Point(Left - Right + FTipDelta, 0);
|
|
bpRightDown:
|
|
Result := Point(-FTipDelta, 0);
|
|
bpLeftUp:
|
|
Result := Point(Left - Right + FTipDelta, Top - Bottom - FSwitchHeight);
|
|
bpRightUp:
|
|
Result := Point(-FTipDelta, Top - Bottom - FSwitchHeight);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindow.CheckPosition(var ARect: TRect);
|
|
var
|
|
NewPosition: TJvBalloonPosition;
|
|
ScreenRect: TRect;
|
|
begin
|
|
if FCurrentPosition = bpAuto then
|
|
CalcAutoPosition(ARect);
|
|
|
|
NewPosition := FCurrentPosition;
|
|
ScreenRect := WorkAreaRect;
|
|
|
|
if ARect.Bottom > ScreenRect.Bottom - ScreenRect.Top then
|
|
begin
|
|
if NewPosition = bpLeftDown then
|
|
NewPosition := bpLeftUp
|
|
else
|
|
if NewPosition = bpRightDown then
|
|
NewPosition := bpRightUp;
|
|
end;
|
|
if ARect.Right > ScreenRect.Right - ScreenRect.Left then
|
|
begin
|
|
if NewPosition = bpRightDown then
|
|
NewPosition := bpLeftDown
|
|
else
|
|
if NewPosition = bpRightUp then
|
|
NewPosition := bpLeftUp;
|
|
end;
|
|
if ARect.Left < ScreenRect.Left then
|
|
begin
|
|
if NewPosition = bpLeftDown then
|
|
NewPosition := bpRightDown
|
|
else
|
|
if NewPosition = bpLeftUp then
|
|
NewPosition := bpRightUp;
|
|
end;
|
|
if ARect.Top < ScreenRect.Top then
|
|
begin
|
|
if NewPosition = bpLeftUp then
|
|
NewPosition := bpLeftDown
|
|
else
|
|
if NewPosition = bpRightUp then
|
|
NewPosition := bpRightDown;
|
|
end;
|
|
|
|
if NewPosition <> FCurrentPosition then
|
|
begin
|
|
{ Reset the offset.. }
|
|
with CalcOffset(ARect) do
|
|
OffsetRect(ARect, -X, -Y);
|
|
FCurrentPosition := NewPosition;
|
|
|
|
{ ..and set the offset }
|
|
with CalcOffset(ARect) do
|
|
OffsetRect(ARect, X, Y);
|
|
end;
|
|
{ final adjustment - just make sure no part is disappearing outside the top/left edge }
|
|
if ARect.Left < ScreenRect.Left then
|
|
begin
|
|
with CalcOffset(ARect) do
|
|
OffsetRect(ARect, -X, -Y);
|
|
if FCurrentPosition = bpLeftUp then
|
|
FCurrentPosition := bpRightUp
|
|
else
|
|
if FCurrentPosition = bpLeftDown then
|
|
FCurrentPosition := bpRightDown;
|
|
with CalcOffset(ARect) do
|
|
OffsetRect(ARect, X, Y);
|
|
end;
|
|
|
|
if ARect.Top < ScreenRect.Top then
|
|
begin
|
|
with CalcOffset(ARect) do
|
|
OffsetRect(ARect, -X, -Y);
|
|
if FCurrentPosition = bpLeftUp then
|
|
FCurrentPosition := bpLeftDown
|
|
else
|
|
if FCurrentPosition = bpRightUp then
|
|
FCurrentPosition := bpRightDown;
|
|
with CalcOffset(ARect) do
|
|
OffsetRect(ARect, X, Y);
|
|
end;
|
|
|
|
case FCurrentPosition of
|
|
bpLeftDown, bpRightDown:
|
|
FDeltaY := FTipHeight;
|
|
bpLeftUp, bpRightUp:
|
|
FDeltaY := 0;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindow.CMShowingChanged(var Msg: TMessage);
|
|
begin
|
|
{ In response of RecreateWnd, SetParentWindow calls, only respond when visible }
|
|
{ Actually only necessairy for TJvBalloonWindow not for TJvBalloonWindowEx }
|
|
if Showing then
|
|
UpdateRegion;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TJvBalloonWindow.CMTextChanged(var Msg: TMessage);
|
|
begin
|
|
{inherited;}
|
|
end;
|
|
|
|
procedure TJvBalloonWindow.CreateParams(var Params: TCreateParams);
|
|
begin
|
|
inherited CreateParams(Params);
|
|
{ Drop shadow in combination with custom animation may cause blurry effect,
|
|
no solution.
|
|
}
|
|
if IsWinXP_UP then
|
|
Params.WindowClass.Style := Params.WindowClass.Style or CS_DROPSHADOW;
|
|
end;
|
|
|
|
function TJvBalloonWindow.CreateRegion: HRGN;
|
|
var
|
|
Rect: TRect;
|
|
RegionRound, RegionTip: HRGN;
|
|
PtTail: array [0..2] of TPoint;
|
|
begin
|
|
SetRect(Rect, 0, 0, Width, Height);
|
|
|
|
case FCurrentPosition of
|
|
bpLeftDown:
|
|
begin
|
|
{ 0
|
|
/ |
|
|
/ |
|
|
/ |
|
|
2----1
|
|
}
|
|
|
|
PtTail[0] := Point(Rect.Right - (FTipDelta + 1), 0);
|
|
PtTail[1] := Point(Rect.Right - (FTipDelta + 1), FTipHeight + 1);
|
|
PtTail[2] := Point(Rect.Right - (FTipDelta + FTipWidth + 2), FTipHeight + 1);
|
|
end;
|
|
bpRightDown:
|
|
begin
|
|
{ 0
|
|
| \
|
|
| \
|
|
| \
|
|
1----2
|
|
}
|
|
PtTail[0] := Point(FTipDelta + 1, 0);
|
|
PtTail[1] := Point(FTipDelta + 1, FTipHeight + 1);
|
|
PtTail[2] := Point(FTipDelta + FTipWidth + 2, FTipHeight + 1);
|
|
end;
|
|
bpLeftUp:
|
|
begin
|
|
{ 2----1
|
|
\ |
|
|
\ |
|
|
\ |
|
|
0
|
|
}
|
|
|
|
PtTail[0] := Point(Rect.Right - (FTipDelta + 1), Rect.Bottom + 1);
|
|
PtTail[1] := Point(Rect.Right - (FTipDelta + 1), Rect.Bottom - (FTipHeight + 1));
|
|
PtTail[2] := Point(Rect.Right - (FTipDelta + FTipWidth + 2), Rect.Bottom - (FTipHeight + 1));
|
|
end;
|
|
bpRightUp:
|
|
begin
|
|
{ 1----2
|
|
| /
|
|
| /
|
|
| /
|
|
0
|
|
}
|
|
|
|
PtTail[0] := Point(FTipDelta + 1, Rect.Bottom);
|
|
PtTail[1] := Point(FTipDelta + 1, Rect.Bottom - (FTipHeight + 1));
|
|
PtTail[2] := Point(FTipDelta + FTipWidth + 2, Rect.Bottom - (FTipHeight + 1));
|
|
end;
|
|
end;
|
|
|
|
RegionTip := CreatePolygonRgn(PtTail, 3, WINDING);
|
|
case FCurrentPosition of
|
|
bpLeftDown, bpRightDown:
|
|
RegionRound := CreateRoundRectRgn(1, FTipHeight + 1, Width, Height - 3, 11, 11);
|
|
else {bpLeftUp, bpRightUp:}
|
|
RegionRound := CreateRoundRectRgn(1, 1, Rect.Right, Rect.Bottom - FTipHeight, 11, 11);
|
|
end;
|
|
Result := CreateRectRgn(0, 0, 1, 1);
|
|
|
|
CombineRgn(Result, RegionTip, RegionRound, RGN_OR);
|
|
DeleteObject(RegionTip);
|
|
DeleteObject(RegionRound);
|
|
end;
|
|
|
|
function TJvBalloonWindow.GetStemPointPosition: TPoint;
|
|
begin
|
|
Result := GetStemPointPositionInRect(BoundsRect);
|
|
end;
|
|
|
|
function TJvBalloonWindow.GetStemPointPositionInRect(const ARect: TRect): TPoint;
|
|
begin
|
|
{ bpAuto returns the same value as bpLeftDown; bpLeftDown is choosen
|
|
arbitrary }
|
|
with ARect do
|
|
case FCurrentPosition of
|
|
bpAuto, bpLeftDown:
|
|
Result := Point(Right - FTipDelta, Top);
|
|
bpRightDown:
|
|
Result := Point(Left + FTipDelta, Top);
|
|
bpLeftUp:
|
|
Result := Point(Right - FTipDelta, Bottom);
|
|
bpRightUp:
|
|
Result := Point(Left + FTipDelta, Bottom);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindow.Init(AData: Pointer);
|
|
begin
|
|
with TGlobalCtrl.Instance.MainCtrl do
|
|
begin
|
|
FShowIcon := (ahShowIconInHint in ApplicationHintOptions) and
|
|
(DefaultIcon <> ikNone) and
|
|
((DefaultIcon <> ikCustom) or (DefaultImageIndex > -1));
|
|
FShowHeader := (ahShowHeaderInHint in ApplicationHintOptions) and (DefaultHeader <> '');
|
|
FHeader := DefaultHeader;
|
|
FCurrentPosition := DefaultBalloonPosition;
|
|
end;
|
|
|
|
FImageSize := TGlobalCtrl.Instance.HintImageSize;
|
|
FSwitchHeight := GetSystemMetrics(SM_CYCURSOR);
|
|
end;
|
|
|
|
{$IFDEF COMPILER6_UP}
|
|
procedure TJvBalloonWindow.NCPaint(DC: HDC);
|
|
begin
|
|
{ Do nothing, thus prevent TJvHintWindow from drawing }
|
|
end;
|
|
{$ELSE}
|
|
procedure TJvBalloonWindow.WMNCPaint(var Msg: TMessage);
|
|
begin
|
|
{ Do nothing, thus prevent TJvHintWindow from drawing }
|
|
end;
|
|
{$ENDIF COMPILER6_UP}
|
|
|
|
procedure TJvBalloonWindow.Paint;
|
|
var
|
|
HintRect: TRect;
|
|
HeaderRect: TRect;
|
|
begin
|
|
if FShowIcon then
|
|
TGlobalCtrl.Instance.DrawHintImage(Canvas, 12, FDeltaY + 8, Color);
|
|
|
|
if FMsg > '' then
|
|
begin
|
|
HintRect := ClientRect;
|
|
Inc(HintRect.Left, 12);
|
|
Inc(HintRect.Top, FDeltaY + FMessageTop);
|
|
Canvas.Font := Screen.HintFont;
|
|
// Canvas.Font.Style := Canvas.Font.Style - [fsBold];
|
|
DrawTextW(Canvas.Handle, FMsg, HintRect,
|
|
DefaultTextFlags or DrawTextBiDiModeFlagsReadingOnly);
|
|
end;
|
|
|
|
if FShowHeader then
|
|
begin
|
|
HeaderRect := ClientRect;
|
|
Inc(HeaderRect.Left, 12);
|
|
if FShowIcon then
|
|
Inc(HeaderRect.Left, FImageSize.cx + 8);
|
|
Inc(HeaderRect.Top, FDeltaY + 8);
|
|
Canvas.Font := Screen.HintFont;
|
|
Canvas.Font.Style := Canvas.Font.Style + [fsBold];
|
|
DrawTextW(Canvas.Handle, FHeader, HeaderRect,
|
|
DefaultTextFlags or DrawTextBiDiModeFlagsReadingOnly);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindow.UpdateRegion;
|
|
var
|
|
Region: HRGN;
|
|
begin
|
|
Region := CreateRegion;
|
|
if SetWindowRgn(Handle, Region, False) = 0 then
|
|
DeleteObject(Region);
|
|
{ MSDN: After a successful call to SetWindowRgn, the system owns the region
|
|
specified by the region handle hRgn. The system does not make a copy of
|
|
the region. Thus, you should not make any further function calls with
|
|
this region handle. In particular, do not delete this region handle. The
|
|
system deletes the region handle when it no longer needed. }
|
|
end;
|
|
|
|
procedure TJvBalloonWindow.WMEraseBkgnd(var Msg: TWMEraseBkgnd);
|
|
var
|
|
Brush, BrushBlack: HBRUSH;
|
|
Region: HRGN;
|
|
begin
|
|
Brush := CreateSolidBrush(ColorToRGB(Color));
|
|
BrushBlack := CreateSolidBrush(0);
|
|
try
|
|
Region := CreateRegion;
|
|
OffsetRgn(Region, -1, -1);
|
|
FillRgn(Msg.DC, Region, Brush);
|
|
FrameRgn(Msg.DC, Region, BrushBlack, 1, 1);
|
|
DeleteObject(Region);
|
|
finally
|
|
DeleteObject(Brush);
|
|
DeleteObject(BrushBlack);
|
|
end;
|
|
Msg.Result := 1;
|
|
end;
|
|
|
|
//=== { TJvBalloonHint } =====================================================
|
|
|
|
constructor TJvBalloonHint.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FActive := False;
|
|
FHint := TJvBalloonWindowEx.Create(Self);
|
|
FHint.FCtrl := Self;
|
|
FHint.Visible := False;
|
|
FHint.OnMouseDown := HandleMouseDown;
|
|
FHint.OnMouseUp := HandleMouseUp;
|
|
FHint.OnMouseMove := HandleMouseMove;
|
|
FHint.OnClick := HandleClick;
|
|
FHint.OnDblClick := HandleDblClick;
|
|
FOptions := [boShowCloseBtn];
|
|
FApplicationHintOptions := [ahShowHeaderInHint, ahShowIconInHint];
|
|
FDefaultIcon := ikInformation;
|
|
FDefaultBalloonPosition := bpAuto;
|
|
FDefaultImageIndex := -1;
|
|
FCustomAnimationTime := 100;
|
|
FCustomAnimationStyle := atBlend;
|
|
FMaxWidth := 0;
|
|
|
|
TGlobalCtrl.Instance.Add(Self);
|
|
end;
|
|
|
|
destructor TJvBalloonHint.Destroy;
|
|
begin
|
|
CancelHint;
|
|
StopHintTimer;
|
|
|
|
if FHandle <> 0 then
|
|
DeallocateHWndEx(FHandle);
|
|
|
|
TGlobalCtrl.Instance.Remove(Self);
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.ActivateHint(ACtrl: TControl; const AHint: WideString;
|
|
const AImageIndex: TImageIndex; const AHeader: WideString;
|
|
const VisibleTime: Integer);
|
|
begin
|
|
if not Assigned(ACtrl) then
|
|
Exit;
|
|
|
|
CancelHint;
|
|
|
|
with FData do
|
|
begin
|
|
RUTF8Hint := WideStringToUTF8(AHint);
|
|
RIconKind := ikCustom;
|
|
RImageIndex := AImageIndex;
|
|
RUTF8Header := WideStringToUTF8(AHeader);
|
|
RVisibleTime := VisibleTime;
|
|
end;
|
|
|
|
InternalActivateHint(ACtrl);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.ActivateHint(ACtrl: TControl;
|
|
const AHint, AHeader: WideString; const VisibleTime: Integer);
|
|
begin
|
|
if not Assigned(ACtrl) then
|
|
Exit;
|
|
|
|
CancelHint;
|
|
|
|
with FData do
|
|
begin
|
|
RUTF8Hint := WideStringToUTF8(AHint);
|
|
RUTF8Header := WideStringToUTF8(AHeader);
|
|
RVisibleTime := VisibleTime;
|
|
RIconKind := ikNone;
|
|
end;
|
|
|
|
InternalActivateHint(ACtrl);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.ActivateHint(ACtrl: TControl; const AHint: WideString;
|
|
const AIconKind: TJvIconKind; const AHeader: WideString; const VisibleTime: Integer);
|
|
begin
|
|
if not Assigned(ACtrl) then
|
|
Exit;
|
|
|
|
CancelHint;
|
|
|
|
with FData do
|
|
begin
|
|
RUTF8Hint := WideStringToUTF8(AHint);
|
|
RIconKind := AIconKind;
|
|
RImageIndex := -1;
|
|
RUTF8Header := WideStringToUTF8(AHeader);
|
|
RVisibleTime := VisibleTime;
|
|
end;
|
|
|
|
InternalActivateHint(ACtrl);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.ActivateHintPos(AAnchorWindow: TCustomForm;
|
|
AAnchorPosition: TPoint; const AHeader, AHint: WideString;
|
|
const VisibleTime: Integer; const AIconKind: TJvIconKind;
|
|
const AImageIndex: TImageIndex);
|
|
begin
|
|
CancelHint;
|
|
|
|
with FData do
|
|
begin
|
|
RAnchorWindow := AAnchorWindow;
|
|
RAnchorPosition := AAnchorPosition;
|
|
RUTF8Header := WideStringToUTF8(AHeader);
|
|
RUTF8Hint := WideStringToUTF8(AHint);
|
|
RVisibleTime := VisibleTime;
|
|
RIconKind := AIconKind;
|
|
RImageIndex := AImageIndex;
|
|
RSwitchHeight := 0;
|
|
end;
|
|
|
|
InternalActivateHintPos;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.ActivateHintRect(ARect: TRect; const AHeader,
|
|
AHint: WideString; const VisibleTime: Integer; const AIconKind: TJvIconKind;
|
|
const AImageIndex: TImageIndex);
|
|
begin
|
|
CancelHint;
|
|
|
|
with FData do
|
|
begin
|
|
RAnchorWindow := nil;
|
|
RAnchorPosition := Point((ARect.Left + ARect.Right) div 2, ARect.Bottom);
|
|
RUTF8Header := WideStringToUTF8(AHeader);
|
|
RUTF8Hint := WideStringToUTF8(AHint);
|
|
RVisibleTime := VisibleTime;
|
|
RIconKind := AIconKind;
|
|
RImageIndex := AImageIndex;
|
|
RSwitchHeight := ARect.Bottom - ARect.Top;
|
|
end;
|
|
|
|
InternalActivateHintPos;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.CancelHint;
|
|
begin
|
|
if not FActive then
|
|
Exit;
|
|
|
|
FActive := False;
|
|
StopHintTimer;
|
|
UnHook;
|
|
|
|
if GetCapture = FHint.Handle then
|
|
ReleaseCapture;
|
|
{ Ensure property Visible is set to False: }
|
|
FHint.Hide;
|
|
{ If ParentWindow = 0, calling Hide won't trigger the CM_SHOWINGCHANGED message
|
|
thus ShowWindow/SetWindowPos isn't called. We do it ourselfs: }
|
|
if FHint.ParentWindow = 0 then
|
|
ShowWindow(FHint.Handle, SW_HIDE);
|
|
|
|
FHint.ParentWindow := 0;
|
|
|
|
if Assigned(FOnClose) then
|
|
FOnClose(Self);
|
|
end;
|
|
|
|
function TJvBalloonHint.GetHandle: THandle;
|
|
begin
|
|
if FHandle = 0 then
|
|
FHandle := AllocateHWndEx(WndProc);
|
|
Result := FHandle;
|
|
end;
|
|
|
|
function TJvBalloonHint.GetUseBalloonAsApplicationHint: Boolean;
|
|
begin
|
|
Result := TGlobalCtrl.Instance.UseBalloonAsApplicationHint;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.HandleClick(Sender: TObject);
|
|
begin
|
|
if Assigned(FOnBalloonClick) then
|
|
FOnBalloonClick(Self);
|
|
end;
|
|
|
|
function TJvBalloonHint.HandleCloseBtnClick: Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(FOnCloseBtnClick) then
|
|
FOnCloseBtnClick(Self, Result);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.HandleDblClick(Sender: TObject);
|
|
begin
|
|
if Assigned(FOnDblClick) then
|
|
FOnDblClick(Self);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.HandleMouseDown(Sender: TObject;
|
|
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
if Assigned(FOnMouseDown) then
|
|
FOnMouseDown(Self, Button, Shift, X, Y);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.HandleMouseMove(Sender: TObject;
|
|
Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
if Assigned(FOnMouseMove) then
|
|
FOnMouseMove(Self, Shift, X, Y);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.HandleMouseUp(Sender: TObject;
|
|
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
if Assigned(FOnMouseUp) then
|
|
FOnMouseUp(Self, Button, Shift, X, Y);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.Hook;
|
|
begin
|
|
if Assigned(FData.RAnchorWindow) then
|
|
RegisterWndProcHook(FData.RAnchorWindow, HookProc, hoBeforeMsg);
|
|
end;
|
|
|
|
function TJvBalloonHint.HookProc(var Msg: TMessage): Boolean;
|
|
begin
|
|
Result := False;
|
|
case Msg.Msg of
|
|
WM_MOVE:
|
|
with FData do
|
|
FHint.MoveWindow(RAnchorWindow.ClientToScreen(RAnchorPosition));
|
|
WM_SIZE:
|
|
with FData do
|
|
{ (rb) This goes wrong if the balloon is anchored to the window itself }
|
|
if not PtInRect(RAnchorWindow.ClientRect, RStemPointPosition) then
|
|
CancelHint;
|
|
WM_SHOWWINDOW:
|
|
;
|
|
WM_WINDOWPOSCHANGED:
|
|
{ Hide/Restore the balloon if the window is minimized }
|
|
FHint.Visible :=
|
|
not IsIconic(FData.RAnchorWindow.Handle) and
|
|
not IsIconic(Application.Handle);
|
|
WM_ACTIVATE:
|
|
if Msg.WParam = WA_INACTIVE then
|
|
{ Remove HWND_TOPMOST flag }
|
|
FHint.NormalizeTopMost
|
|
else
|
|
{ Restore HWND_TOPMOST flag }
|
|
FHint.RestoreTopMost;
|
|
WM_CLOSE:
|
|
CancelHint;
|
|
WM_NCACTIVATE, WM_EXITSIZEMOVE:
|
|
{ (rb) Weird behaviour of windows ? }
|
|
FHint.RestoreTopMost;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.InternalActivateHint(ACtrl: TControl);
|
|
var
|
|
LParentForm: TCustomForm;
|
|
begin
|
|
if not Assigned(ACtrl) then
|
|
Exit;
|
|
|
|
LParentForm := GetParentForm(ACtrl);
|
|
with ACtrl, FData do
|
|
begin
|
|
RAnchorWindow := LParentForm;
|
|
if LParentForm = ACtrl then
|
|
RAnchorPosition := Point(Width div 2, ClientHeight)
|
|
else
|
|
RAnchorPosition := InternalClientToParent(ACtrl, Point(Width div 2, Height), LParentForm);
|
|
|
|
RSwitchHeight := ACtrl.Height;
|
|
end;
|
|
|
|
InternalActivateHintPos;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.InternalActivateHintPos;
|
|
var
|
|
Rect: TRect;
|
|
Animate: BOOL;
|
|
TmpMaxWidth: Integer;
|
|
begin
|
|
with FData do
|
|
begin
|
|
{ Use defaults if necessairy: }
|
|
if boUseDefaultHeader in Options then
|
|
RUTF8Header := WideStringToUTF8(DefaultHeader);
|
|
if boUseDefaultIcon in Options then
|
|
RIconKind := DefaultIcon;
|
|
if boUseDefaultImageIndex in Options then
|
|
RImageIndex := DefaultImageIndex;
|
|
RShowCloseBtn := boShowCloseBtn in Options;
|
|
|
|
{ Determine animation style }
|
|
if not IsWinXP_UP then
|
|
RAnimationStyle := atNone
|
|
else
|
|
if boCustomAnimation in Options then
|
|
begin
|
|
RAnimationStyle := FCustomAnimationStyle;
|
|
RAnimationTime := FCustomAnimationTime;
|
|
end
|
|
else
|
|
begin
|
|
SystemParametersInfo(SPI_GETTOOLTIPANIMATION, 0, @Animate, 0);
|
|
if Animate then
|
|
begin
|
|
SystemParametersInfo(SPI_GETTOOLTIPFADE, 0, @Animate, 0);
|
|
if Animate then
|
|
RAnimationStyle := atBlend
|
|
else
|
|
RAnimationStyle := atSlide;
|
|
end
|
|
else
|
|
RAnimationStyle := atNone;
|
|
RAnimationTime := 100;
|
|
end;
|
|
|
|
{ Hook the anchor window }
|
|
FActive := True;
|
|
Hook;
|
|
|
|
{ Determine the size of the balloon rect, the stem point will be on
|
|
position (0, 0) }
|
|
if MaxWidth = 0 then
|
|
TmpMaxWidth := Screen.Width
|
|
else
|
|
TmpMaxWidth := MaxWidth;
|
|
Rect := FHint.CalcHintRectUTF8(TmpMaxWidth, RUTF8Hint, @FData);
|
|
|
|
{ Offset the rectangle to the anchor position }
|
|
if Assigned(RAnchorWindow) then
|
|
with RAnchorWindow.ClientToScreen(RAnchorPosition) do
|
|
OffsetRect(Rect, X, Y)
|
|
else
|
|
with RAnchorPosition do
|
|
OffsetRect(Rect, X, Y);
|
|
|
|
if boPlaySound in Options then
|
|
TGlobalCtrl.Instance.PlaySound(RIconKind);
|
|
|
|
FHint.InternalActivateHint(Rect, RUTF8Hint);
|
|
|
|
{ Now we can determine the actual anchor & stempoint position: }
|
|
if Assigned(RAnchorWindow) then
|
|
begin
|
|
RAnchorPosition := RAnchorWindow.ScreenToClient(Rect.TopLeft);
|
|
RStemPointPosition := RAnchorWindow.ScreenToClient(FHint.StemPointPosition);
|
|
end
|
|
else
|
|
begin
|
|
RAnchorPosition := Rect.TopLeft;
|
|
RStemPointPosition := FHint.StemPointPosition;
|
|
end;
|
|
|
|
{ Last call because of possible CancelHint call in StartHintTimer }
|
|
if RVisibleTime > 0 then
|
|
StartHintTimer(RVisibleTime);
|
|
{if GetCapture = 0 then
|
|
SetCapture(FHint.Handle);
|
|
ReleaseCapture;}
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.Notification(AComponent: TComponent;
|
|
Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if (Operation = opRemove) and (AComponent = Images) then
|
|
Images := nil;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.SetImages(const Value: TCustomImageList);
|
|
begin
|
|
FImages := Value;
|
|
if Images <> nil then
|
|
Images.FreeNotification(Self);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.SetOptions(const Value: TJvBalloonOptions);
|
|
begin
|
|
if Value <> FOptions then
|
|
FOptions := Value;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.SetUseBalloonAsApplicationHint(
|
|
const Value: Boolean);
|
|
begin
|
|
TGlobalCtrl.Instance.UseBalloonAsApplicationHint := Value;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.StartHintTimer(Value: Integer);
|
|
begin
|
|
StopHintTimer;
|
|
if SetTimer(Handle, 1, Value, nil) = 0 then
|
|
CancelHint
|
|
else
|
|
FTimerActive := True;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.StopHintTimer;
|
|
begin
|
|
if FTimerActive then
|
|
begin
|
|
KillTimer(Handle, 1);
|
|
FTimerActive := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonHint.UnHook;
|
|
begin
|
|
if Assigned(FData.RAnchorWindow) then
|
|
UnRegisterWndProcHook(FData.RAnchorWindow, HookProc, hoBeforeMsg);
|
|
end;
|
|
|
|
procedure TJvBalloonHint.WndProc(var Msg: TMessage);
|
|
begin
|
|
with Msg do
|
|
if Msg = WM_TIMER then
|
|
try
|
|
CancelHint;
|
|
except
|
|
{$IFDEF COMPILER6_UP}
|
|
if Assigned(ApplicationHandleException) then
|
|
ApplicationHandleException(Self);
|
|
{$ELSE}
|
|
Application.HandleException(Self);
|
|
{$ENDIF COMPILER6_UP}
|
|
end
|
|
else
|
|
Result := DefWindowProc(Handle, Msg, WParam, LParam);
|
|
end;
|
|
|
|
//=== { TGlobalCtrl } ========================================================
|
|
|
|
constructor TGlobalCtrl.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
|
|
FCtrls := TList.Create;
|
|
|
|
if IsWinXP_UP then
|
|
begin
|
|
FDefaultImages := TImageList.Create(nil);
|
|
{ According to MSDN flag ILC_COLOR32 needs to be included (?) }
|
|
FDefaultImages.Handle := ImageList_Create(16, 16, ILC_COLOR32 or ILC_MASK, 4, 4);
|
|
end
|
|
else
|
|
FDefaultImages := TImageList.CreateSize(16, 16);
|
|
|
|
{ Only need to update the background color in XP when using pre v6.0 ComCtl32.dll
|
|
image lists }
|
|
FNeedUpdateBkColor := IsWinXP_UP and (GetComCtlVersion < $00060000);
|
|
|
|
if FNeedUpdateBkColor then
|
|
FDefaultImages.BkColor := Application.HintColor
|
|
else
|
|
FDefaultImages.BkColor := clNone;
|
|
|
|
FBkColor := Application.HintColor;
|
|
FUseBalloonAsApplicationHint := False;
|
|
|
|
GetDefaultImages;
|
|
GetDefaultSounds;
|
|
end;
|
|
|
|
destructor TGlobalCtrl.Destroy;
|
|
begin
|
|
FDefaultImages.Free;
|
|
FCtrls.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TGlobalCtrl.Add(ABalloonHint: TJvBalloonHint);
|
|
begin
|
|
FCtrls.Add(ABalloonHint);
|
|
{ Determine whether we are designing }
|
|
if Assigned(ABalloonHint) then
|
|
FDesigning := csDesigning in ABalloonHint.ComponentState;
|
|
end;
|
|
|
|
procedure TGlobalCtrl.DrawHintImage(Canvas: TCanvas; X, Y: Integer; const ABkColor: TColor);
|
|
begin
|
|
DrawHintImage(Canvas, X, Y, MainCtrl.DefaultIcon, MainCtrl.DefaultImageIndex, ABkColor);
|
|
end;
|
|
|
|
procedure TGlobalCtrl.DrawHintImage(Canvas: TCanvas; X, Y: Integer;
|
|
const AIconKind: TJvIconKind; const AImageIndex: TImageIndex; const ABkColor: TColor);
|
|
const
|
|
cDefaultImages: array [TJvIconKind] of Integer = (-1, -1, 0, 1, 2, 3, 4);
|
|
begin
|
|
case AIconKind of
|
|
ikCustom:
|
|
with MainCtrl do
|
|
if not Assigned(Images) or (AImageIndex < 0) or (AImageIndex >= Images.Count) then
|
|
begin
|
|
BkColor := ABkColor;
|
|
FDefaultImages.Draw(Canvas, X, Y, cDefaultImages[ikInformation]);
|
|
end
|
|
else
|
|
Images.Draw(Canvas, X, Y, AImageIndex);
|
|
ikNone:
|
|
;
|
|
else
|
|
begin
|
|
BkColor := ABkColor;
|
|
FDefaultImages.Draw(Canvas, X, Y, cDefaultImages[AIconKind]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TGlobalCtrl.GetDefaultImages;
|
|
type
|
|
TPictureType = (ptXP, ptNormal, ptSimple);
|
|
const
|
|
{ Get the images:
|
|
|
|
For From ID TJvIconKind Spec
|
|
---------------------------------------------------------------------------
|
|
Windows XP User32.dll 100 ikApplication 16x16 32x32 48x48 1,4,8,32 bpp
|
|
101 ikWarning
|
|
102 ikQuestion
|
|
103 ikError
|
|
104 ikInformation
|
|
105 ikApplication
|
|
All (?) comctl32.dll 20480 ikError 16x16 32x32 4 bpp
|
|
20481 ikInformation
|
|
20482 ikWarning
|
|
}
|
|
|
|
{ ikApplication, ikError, ikInformation, ikQuestion, ikWarning }
|
|
cIcons: array [TPictureType, ikApplication..ikWarning] of Integer =
|
|
(
|
|
(100, 103, 104, 102, 101), // XP
|
|
(OIC_SAMPLE, 20480, 20481, OIC_QUES, 20482), // Normal
|
|
(OIC_SAMPLE, OIC_HAND, OIC_NOTE, OIC_QUES, OIC_BANG) // Paranoid
|
|
);
|
|
cFlags: array [Boolean] of UINT = (0, LR_SHARED);
|
|
var
|
|
IconKind: TJvIconKind;
|
|
PictureType: TPictureType;
|
|
IconHandle: THandle;
|
|
Shared: Boolean;
|
|
Modules: array [Boolean] of HMODULE;
|
|
begin
|
|
PictureType := ptNormal;
|
|
Modules[True] := 0;
|
|
|
|
if IsWinXP_UP then
|
|
begin
|
|
Modules[False] := GetModuleHandle('user32.dll');
|
|
if Modules[False] <> 0 then
|
|
PictureType := ptXP
|
|
end;
|
|
|
|
if PictureType = ptNormal then
|
|
begin
|
|
Modules[False] := GetModuleHandle('comctl32.dll');
|
|
if Modules[False] = 0 then
|
|
PictureType := ptSimple;
|
|
end;
|
|
|
|
{ Now PictureType = ptXP -> Modules = (user32.dll handle, 0)
|
|
PictureType = ptNormal -> Modules = (comctl32.dll handle, 0)
|
|
PictureType = ptSimple -> Modules = (0, 0)
|
|
}
|
|
|
|
for IconKind := Low(cIcons[PictureType]) to High(cIcons[PictureType]) do
|
|
begin
|
|
Shared := (PictureType = ptSimple) or
|
|
(PictureType = ptNormal) and (IconKind in [ikApplication, ikQuestion]);
|
|
IconHandle :=
|
|
LoadImage(Modules[Shared], MakeIntResource(cIcons[PictureType, IconKind]),
|
|
IMAGE_ICON, 16, 16, cFlags[Shared]);
|
|
ImageList_AddIcon(FDefaultImages.Handle, IconHandle);
|
|
{ MSDN: Do not use DestroyIcon to destroy a shared icon. A shared icon is
|
|
valid as long as the module from which it was loaded remains in memory }
|
|
if not Shared then
|
|
DestroyIcon(IconHandle);
|
|
end;
|
|
end;
|
|
|
|
procedure TGlobalCtrl.GetDefaultSounds;
|
|
{ Taken from ActnMenus.pas }
|
|
var
|
|
Registry: TRegistry;
|
|
|
|
function ReadSoundSetting(KeyStr: string): string;
|
|
var
|
|
S: string;
|
|
begin
|
|
Registry.RootKey := HKEY_CURRENT_USER;
|
|
Result := '';
|
|
if Registry.OpenKeyReadOnly('\AppEvents\Schemes\Apps\.Default\' + KeyStr) then
|
|
try
|
|
S := Registry.ReadString('');
|
|
SetLength(Result, 4096);
|
|
SetLength(Result, ExpandEnvironmentStrings(PChar(S), PChar(Result), 4096) - 1);
|
|
finally
|
|
Registry.CloseKey;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Registry := TRegistry.Create;
|
|
try
|
|
FSounds[ikCustom] := ReadSoundSetting('SystemNotification\.Current');
|
|
FSounds[ikNone] := FSounds[ikCustom];
|
|
FSounds[ikApplication] := FSounds[ikCustom];
|
|
FSounds[ikError] := ReadSoundSetting('SystemHand\.Current');
|
|
FSounds[ikInformation] := ReadSoundSetting('SystemAsterisk\.Current');
|
|
FSounds[ikQuestion] := ReadSoundSetting('SystemQuestion\.Current');
|
|
FSounds[ikWarning] := ReadSoundSetting('SystemExclamation\.Current');
|
|
finally
|
|
Registry.Free;
|
|
end;
|
|
end;
|
|
|
|
function TGlobalCtrl.GetMainCtrl: TJvBalloonHint;
|
|
begin
|
|
if FCtrls.Count = 0 then
|
|
begin
|
|
if GMainCtrl = nil then
|
|
GMainCtrl := TJvBalloonHint.Create(Self);
|
|
Result := GMainCtrl;
|
|
end
|
|
else
|
|
Result := TJvBalloonHint(FCtrls[0]);
|
|
end;
|
|
|
|
function TGlobalCtrl.HintImageSize: TSize;
|
|
begin
|
|
Result := HintImageSize(MainCtrl.DefaultIcon, MainCtrl.DefaultImageIndex);
|
|
end;
|
|
|
|
function TGlobalCtrl.HintImageSize(const AIconKind: TJvIconKind;
|
|
const AImageIndex: TImageIndex): TSize;
|
|
begin
|
|
case AIconKind of
|
|
ikCustom:
|
|
with MainCtrl do
|
|
if not Assigned(Images) or (AImageIndex < 0) or (AImageIndex >= Images.Count) then
|
|
begin
|
|
Result.cx := 16;
|
|
Result.cy := 16;
|
|
end
|
|
else
|
|
begin
|
|
Result.cx := Images.Width;
|
|
Result.cy := Images.Height;
|
|
end;
|
|
ikNone:
|
|
begin
|
|
Result.cx := 0;
|
|
Result.cy := 0;
|
|
end;
|
|
else
|
|
begin
|
|
Result.cx := 16;
|
|
Result.cy := 16;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
class function TGlobalCtrl.Instance: TGlobalCtrl;
|
|
begin
|
|
if not Assigned(GGlobalCtrl) then
|
|
GGlobalCtrl := TGlobalCtrl.Create(nil);
|
|
Result := GGlobalCtrl;
|
|
end;
|
|
|
|
procedure TGlobalCtrl.PlaySound(const AIconKind: TJvIconKind);
|
|
begin
|
|
if Length(FSounds[AIconKind]) > 0 then
|
|
sndPlaySound(PChar(FSounds[AIconKind]), SND_NOSTOP or SND_ASYNC);
|
|
end;
|
|
|
|
procedure TGlobalCtrl.Remove(ABalloonHint: TJvBalloonHint);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := FCtrls.IndexOf(ABalloonHint);
|
|
if I >= 0 then
|
|
begin
|
|
FCtrls.Delete(I);
|
|
|
|
if FCtrls.Count = 0 then
|
|
UseBalloonAsApplicationHint := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TGlobalCtrl.SetBkColor(const Value: TColor);
|
|
begin
|
|
if FNeedUpdateBkColor and (FBkColor <> Value) then
|
|
begin
|
|
{ Icons in windows XP use an alpha channel to 'blend' with the background.
|
|
If the background color changes, then the images must be redrawn,
|
|
when using pre v6.0 ComCtl32.dll image lists
|
|
}
|
|
FBkColor := Value;
|
|
FDefaultImages.Clear;
|
|
FDefaultImages.BkColor := FBkColor;
|
|
GetDefaultImages;
|
|
end;
|
|
end;
|
|
|
|
procedure TGlobalCtrl.SetUseBalloonAsApplicationHint(const Value: Boolean);
|
|
begin
|
|
if FDesigning then
|
|
FUseBalloonAsApplicationHint := Value
|
|
else
|
|
if Value <> FUseBalloonAsApplicationHint then
|
|
begin
|
|
FUseBalloonAsApplicationHint := Value;
|
|
|
|
Application.CancelHint;
|
|
|
|
if FUseBalloonAsApplicationHint then
|
|
begin
|
|
FOldHintWindowClass := HintWindowClass;
|
|
HintWindowClass := TJvBalloonWindow;
|
|
end
|
|
else
|
|
HintWindowClass := FOldHintWindowClass;
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvBalloonWindowEx } =================================================
|
|
|
|
function TJvBalloonWindowEx.CalcHeaderRect(MaxWidth: Integer): TRect;
|
|
begin
|
|
Result := inherited CalcHeaderRect(MaxWidth);
|
|
if FShowCloseBtn then
|
|
begin
|
|
Inc(Result.Right, 20);
|
|
if Result.Bottom < 20 then
|
|
Result.Bottom := 20;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.ChangeCloseState(const AState: Cardinal);
|
|
{$IFDEF JVCLThemesEnabled}
|
|
var
|
|
Details: TThemedElementDetails;
|
|
Button: TThemedToolTip;
|
|
{$ENDIF JVCLThemesEnabled}
|
|
begin
|
|
if AState <> FCloseState then
|
|
begin
|
|
FCloseState := AState;
|
|
{$IFDEF JVCLThemesEnabled}
|
|
if ThemeServices.ThemesEnabled then
|
|
begin
|
|
if (AState and DFCS_PUSHED > 0) and (AState and DFCS_HOT = 0) then
|
|
Button := tttCloseNormal
|
|
else
|
|
if AState and DFCS_PUSHED > 0 then
|
|
Button := tttClosePressed
|
|
else
|
|
if AState and DFCS_HOT > 0 then
|
|
Button := tttCloseHot
|
|
else
|
|
Button := tttCloseNormal;
|
|
|
|
Details := ThemeServices.GetElementDetails(Button);
|
|
ThemeServices.DrawElement(Canvas.Handle, Details, FCloseBtnRect);
|
|
end
|
|
else
|
|
{$ENDIF JVCLThemesEnabled}
|
|
DrawFrameControl(Canvas.Handle, FCloseBtnRect, DFC_CAPTION, DFCS_TRANSPARENT or
|
|
DFCS_CAPTIONCLOSE or FCloseState);
|
|
end;
|
|
end;
|
|
|
|
function FormHasFocus(FormHandle: HWND): Boolean;
|
|
var
|
|
H: HWND;
|
|
begin
|
|
H := GetFocus;
|
|
while IsWindow(H) and (H <> FormHandle) do
|
|
H := GetParent(H);
|
|
Result := H = FormHandle;
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.EnsureTopMost;
|
|
begin
|
|
if not Assigned(FCtrl.FData.RAnchorWindow) then
|
|
Exit;
|
|
|
|
if not FormHasFocus(FCtrl.FData.RAnchorWindow.Handle) then
|
|
{ Current window is not focused, thus place the balloon behind the
|
|
window that has focus }
|
|
NormalizeTopMost
|
|
else
|
|
RestoreTopMost;
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.Init(AData: Pointer);
|
|
begin
|
|
Canvas.Font := Screen.HintFont;
|
|
Color := Application.HintColor;
|
|
|
|
with PHintData(AData)^ do
|
|
begin
|
|
FImageIndex := RImageIndex;
|
|
FIconKind := RIconKind;
|
|
FHeader := UTF8ToWideString(RUTF8Header);
|
|
if FHeader = '' then
|
|
FHeader := RUTF8Header;
|
|
|
|
FShowHeader := FHeader > '';
|
|
FShowIcon := (FIconKind <> ikNone) and
|
|
((FIconKind <> ikCustom) or (FImageIndex <> -1));
|
|
FShowCloseBtn := RShowCloseBtn;
|
|
|
|
FAnimationTime := RAnimationTime;
|
|
FAnimationStyle := RAnimationStyle;
|
|
|
|
FSwitchHeight := RSwitchHeight;
|
|
FIsAnchored := Assigned(RAnchorWindow);
|
|
end;
|
|
|
|
FImageSize := TGlobalCtrl.Instance.HintImageSize(FIconKind, FImageIndex);
|
|
FCurrentPosition := FCtrl.DefaultBalloonPosition;
|
|
end;
|
|
|
|
procedure BoundRect(var ARect: TRect; const BoundingRect: TRect);
|
|
begin
|
|
if BoundingRect.Left > ARect.Left then
|
|
begin
|
|
ARect.Right := ARect.Right + (BoundingRect.Left - ARect.Left);
|
|
ARect.Left := BoundingRect.Left;
|
|
end;
|
|
if BoundingRect.Top > ARect.Top then
|
|
begin
|
|
ARect.Bottom := ARect.Bottom + (BoundingRect.Top - ARect.Top);
|
|
ARect.Top := BoundingRect.Top;
|
|
end;
|
|
if BoundingRect.Right < ARect.Right then
|
|
begin
|
|
ARect.Left := ARect.Left - (ARect.Right - BoundingRect.Right);
|
|
ARect.Right := BoundingRect.Right;
|
|
end;
|
|
if BoundingRect.Bottom < ARect.Bottom then
|
|
begin
|
|
ARect.Top := ARect.Top - (ARect.Bottom - BoundingRect.Bottom);
|
|
ARect.Bottom := BoundingRect.Bottom;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.InternalActivateHint(var Rect: TRect;
|
|
const AHint: string);
|
|
const
|
|
{TJvAnimationStyle = (atNone, atSlide, atRoll, atRollHorNeg, atRollHorPos, atRollVerNeg,
|
|
atRollVerPos, atSlideHorNeg, atSlideHorPos, atSlideVerNeg, atSlideVerPos, atCenter, atBlend);}
|
|
cAnimationStyle: array [TJvAnimationStyle] of Integer =
|
|
(0, AW_SLIDE, 0, AW_HOR_NEGATIVE,
|
|
AW_HOR_POSITIVE, AW_VER_NEGATIVE, AW_VER_POSITIVE, AW_HOR_NEGATIVE or AW_SLIDE,
|
|
AW_HOR_POSITIVE or AW_SLIDE, AW_VER_NEGATIVE or AW_SLIDE, AW_VER_POSITIVE or AW_SLIDE,
|
|
AW_CENTER, AW_BLEND);
|
|
var
|
|
AutoValue: Integer;
|
|
begin
|
|
CheckPosition(Rect);
|
|
|
|
if HandleAllocated and IsWindowVisible(Handle) then
|
|
begin
|
|
Hide;
|
|
if ParentWindow = 0 then
|
|
ShowWindow(Handle, SW_HIDE);
|
|
end;
|
|
|
|
{ This will prevent focusing/unfocusing of the application button on the
|
|
taskbar when clicking on the balloon window }
|
|
if FIsAnchored then
|
|
{ Application Handle, so we automatically get minimized/restored when the
|
|
application minimizes/restores }
|
|
ParentWindow := Application.Handle
|
|
else
|
|
ParentWindow := 0;
|
|
|
|
BoundRect(Rect, DesktopRect);
|
|
|
|
with Rect do
|
|
SetBounds(Left, Top, Right - Left, Bottom - Top);
|
|
UpdateRegion;
|
|
|
|
{ Set the Z order of the balloon }
|
|
if Assigned(FCtrl.FData.RAnchorWindow) then
|
|
begin
|
|
if not IsWindowVisible(FCtrl.FData.RAnchorWindow.Handle) or
|
|
IsIconic(FCtrl.FData.RAnchorWindow.Handle) then
|
|
{ Current window is minimized, thus do not show the balloon }
|
|
Exit
|
|
else
|
|
EnsureTopMost;
|
|
end
|
|
else
|
|
RestoreTopMost;
|
|
|
|
if (FAnimationStyle <> atNone) and IsWinXP_UP and Assigned(AnimateWindowProc) then
|
|
begin
|
|
if FAnimationStyle in [atSlide, atRoll] then
|
|
case FCurrentPosition of
|
|
bpLeftDown, bpRightDown:
|
|
AutoValue := AW_VER_POSITIVE;
|
|
else {bpLeftUp, bpRightUp:}
|
|
AutoValue := AW_VER_NEGATIVE;
|
|
end
|
|
else
|
|
AutoValue := 0;
|
|
{ This function will fail on systems other than Windows XP,
|
|
because of use of the window region: }
|
|
AnimateWindowProc(Handle, FAnimationTime, cAnimationStyle[FAnimationStyle] or AutoValue);
|
|
end;
|
|
|
|
{ Ensure property Visible is set to True: }
|
|
Show;
|
|
{ If ParentWindow = 0, calling Show won't trigger the CM_SHOWINGCHANGED message
|
|
thus ShowWindow/SetWindowPos isn't called. We do it ourselfs: }
|
|
if ParentWindow = 0 then
|
|
ShowWindow(Handle, SW_SHOWNOACTIVATE);
|
|
{$IFDEF COMPILER5}
|
|
Invalidate;
|
|
{$ENDIF COMPILER5}
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.MoveWindow(NewPos: TPoint);
|
|
begin
|
|
BoundsRect := Rect(NewPos.X, NewPos.Y, NewPos.X + Width, NewPos.Y + Height);
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.NormalizeTopMost;
|
|
var
|
|
TopWindow: HWND;
|
|
begin
|
|
if not Assigned(FCtrl.FData.RAnchorWindow) then
|
|
Exit;
|
|
|
|
{ Retrieve the window below the anchor window in the Z order. }
|
|
TopWindow := GetWindow(FCtrl.FData.RAnchorWindow.Handle, GW_HWNDPREV);
|
|
if GetWindowLong(TopWindow, GWL_EXSTYLE) and WS_EX_TOPMOST <> 0 then
|
|
TopWindow := HWND_NOTOPMOST;
|
|
|
|
SetWindowPos(Handle, TopWindow, 0, 0, 0, 0,
|
|
SWP_NOMOVE or SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOOWNERZORDER);
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.Paint;
|
|
var
|
|
HintRect: TRect;
|
|
HeaderRect: TRect;
|
|
begin
|
|
HintRect := ClientRect;
|
|
|
|
if FShowIcon then
|
|
TGlobalCtrl.Instance.DrawHintImage(Canvas, 12, FDeltaY + 7, FIconKind, FImageIndex, Color);
|
|
|
|
FCloseState := 0;
|
|
if FShowCloseBtn then
|
|
begin
|
|
FCloseBtnRect := Rect(HintRect.Right - 22, FDeltaY + 5, HintRect.Right - 6, FDeltaY + 21);
|
|
{$IFDEF JVCLThemesEnabled}
|
|
if ThemeServices.ThemesEnabled then
|
|
begin
|
|
Dec(FCloseBtnRect.Left);
|
|
Dec(FCloseBtnRect.Top);
|
|
end;
|
|
{$ENDIF JVCLThemesEnabled}
|
|
ChangeCloseState(DFCS_FLAT);
|
|
end;
|
|
|
|
if FMsg > '' then
|
|
begin
|
|
Inc(HintRect.Left, 12);
|
|
Inc(HintRect.Top, FDeltaY + FMessageTop);
|
|
Canvas.Font := Screen.HintFont;
|
|
Canvas.Font.Style := Canvas.Font.Style - [fsBold];
|
|
DrawTextW(Canvas.Handle, FMsg, HintRect,
|
|
DefaultTextFlags or DrawTextBiDiModeFlagsReadingOnly);
|
|
end;
|
|
|
|
if FShowHeader then
|
|
begin
|
|
HeaderRect := ClientRect;
|
|
Inc(HeaderRect.Left, 12);
|
|
if FShowIcon then
|
|
Inc(HeaderRect.Left, FImageSize.cx + 8);
|
|
Inc(HeaderRect.Top, FDeltaY + 8);
|
|
Canvas.Font.Style := Canvas.Font.Style + [fsBold];
|
|
DrawTextW(Canvas.Handle, FHeader, HeaderRect,
|
|
DefaultTextFlags or DrawTextBiDiModeFlagsReadingOnly);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.RestoreTopMost;
|
|
begin
|
|
SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0,
|
|
SWP_NOMOVE or SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOOWNERZORDER);
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.WMActivateApp(var Msg: TWMActivateApp);
|
|
begin
|
|
inherited;
|
|
if Msg.Active then
|
|
EnsureTopMost;
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.WMLButtonDown(var Msg: TWMLButtonDown);
|
|
begin
|
|
inherited;
|
|
if FShowCloseBtn then
|
|
begin
|
|
if PtInRect(FCloseBtnRect, SmallPointToPoint(Msg.Pos)) then
|
|
begin
|
|
{SetCapture(Handle);}// handled in inherited
|
|
ChangeCloseState(FCloseState or DFCS_PUSHED);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.WMLButtonUp(var Msg: TWMLButtonUp);
|
|
begin
|
|
if FShowCloseBtn then
|
|
begin
|
|
if FCloseState and DFCS_PUSHED > 0 then
|
|
begin
|
|
{ReleaseCapture;}// handled in inherited
|
|
ChangeCloseState(FCloseState and not DFCS_PUSHED);
|
|
if PtInRect(FCloseBtnRect, SmallPointToPoint(Msg.Pos)) then
|
|
begin
|
|
{ Prevent firing of OnClick event in inherited call }
|
|
ControlState := ControlState - [csClicked];
|
|
if FCtrl.HandleCloseBtnClick then
|
|
FCtrl.CancelHint;
|
|
end;
|
|
end;
|
|
end;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.WMMouseMove(var Msg: TWMMouseMove);
|
|
var
|
|
State: Cardinal;
|
|
begin
|
|
inherited;
|
|
if FShowCloseBtn then
|
|
begin
|
|
State := DFCS_FLAT;
|
|
|
|
if PtInRect(FCloseBtnRect, SmallPointToPoint(Msg.Pos)) then
|
|
begin
|
|
{ Note: DFCS_HOT is not supported in windows 95 systems }
|
|
State := State or DFCS_HOT;
|
|
if FCloseState and DFCS_PUSHED > 0 then
|
|
State := State or DFCS_PUSHED;
|
|
end;
|
|
ChangeCloseState(State);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBalloonWindowEx.WMNCHitTest(var Msg: TWMNCHitTest);
|
|
begin
|
|
Msg.Result := HTCLIENT;
|
|
end;
|
|
|
|
initialization
|
|
{$IFDEF UNITVERSIONING}
|
|
RegisterUnitVersion(HInstance, UnitVersioning);
|
|
{$ENDIF UNITVERSIONING}
|
|
InitUnicodeWrap;
|
|
|
|
finalization
|
|
FreeAndNil(GGlobalCtrl);
|
|
{$IFDEF UNITVERSIONING}
|
|
UnregisterUnitVersion(HInstance);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
end.
|
|
|