spiegel-keyman/windows/src/engine/keyman/UfrmKeymanMenu.pas
Marc Durdin 1a80290fee fix(windows): fix menu popup position
Fixes #4013.

This turned out to be a lot more complicated than I expected, mostly due
to limitations in the Windows Shell API with regard to multiple monitors
and per-monitor DPI awareness (which we don't yet do in Keyman -- a
task for another day). Some of the Shell API functions are also
re-entrant, which is dangerous, so I avoided using those.

We now popup the menu relative to the Keyman icon far more precisely
which gives a much nicer feel to the menu interaction.
2020-12-19 07:31:17 +11:00

971 lines
28 KiB
ObjectPascal

(*
Name: UfrmKeymanMenu
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Create Date: 25 May 2010
Modified Date: 23 Oct 2014
Authors: mcdurdin
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
History: 14 May 2010 - mcdurdin - I2226 - Improve Keyman menu
25 May 2010 - mcdurdin - I2377 - Keyman menu pops up and off-screen when taskbar is at top of screen
30 Nov 2010 - mcdurdin - I2542 - Rework menu styling as per Paul's design
10 Dec 2010 - mcdurdin - I2551, I2554 - Keyman crashes when closing menu
10 Dec 2010 - mcdurdin - I2555 - Transparency issues in Remote Desktop
17 Dec 2010 - mcdurdin - I2542 - Fix horizontal line
11 Jan 2011 - mcdurdin - I2582 - Keyman menu should pop over OSK
31 Jan 2011 - mcdurdin - I2639 - Fix handle and memory leak in Keyman menu
31 Jan 2011 - mcdurdin - I2582 - Re-fix for popping over OSK
17 Mar 2011 - mcdurdin - I2693 - Crash in Keyman menu with multiple screens and no 'nearest' screen returned
18 Mar 2011 - mcdurdin - I2820 - Crash in Keyman menu with multiple screens
03 May 2011 - mcdurdin - I2884 - Crash moving mouse over uninitialized menu
18 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support
26 Jun 2012 - mcdurdin - I3377 - KM9 - Update code references from 8.0 to 9.0
24 Jan 2012 - mcdurdin - I3213 - Crash when Keyman menu is visible and Ctrl+Alt+Del or Win+L pressed
03 Nov 2012 - mcdurdin - I3517 - V9.0 - Merge of I3213 - Crash when Keyman menu is visible and Ctrl+Alt+Del or Win+L pressed
01 Dec 2012 - mcdurdin - I3614 - V9.0 - Start removal of keyboard selection list from UI
24 Oct 2013 - mcdurdin - I3933 - V9.0 - Keyman tray icon menu is not showing installed keyboards
11 Nov 2013 - mcdurdin - I3960 - V9.0 - Reskin the Keyman menu with 9.0 style
29 Nov 2013 - mcdurdin - I3990 - V9.0 - Keyman keyboards menu should be used on OSK toolbar
28 Aug 2014 - mcdurdin - I4390 - V9.0 - Free vs Pro
23 Oct 2014 - mcdurdin - I4429 - Resized window can sometimes cause menu popup to crash [CrashID:keyman.exe_9.0.470.0_2C404908_EAccessViolation]
*)
unit UfrmKeymanMenu; // I3306 // I3614 // I3960
interface
uses
System.Classes,
System.SysUtils,
System.Types,
System.Variants,
Vcl.Controls,
Vcl.Dialogs,
Vcl.ExtCtrls,
Vcl.Forms,
Vcl.Graphics,
Vcl.Menus,
Vcl.Imaging.pngimage,
Winapi.Messages,
Winapi.Windows,
custinterfaces,
KeymanMenuItem,
UserMessages;
type
TfrmKeymanMenu = class(TForm)
imgTitle: TImage;
tmrScrollStart: TTimer;
tmrScroll: TTimer;
imgScrollDown: TImage;
imgScrollUp: TImage;
imgScrollDownDisabled: TImage;
imgScrollUpDisabled: TImage;
procedure FormClick(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormMouseLeave(Sender: TObject);
procedure TntFormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure TntFormHide(Sender: TObject);
procedure TntFormShow(Sender: TObject);
procedure TntFormCreate(Sender: TObject);
procedure TntFormDestroy(Sender: TObject);
procedure tmrScrollStartTimer(Sender: TObject);
procedure tmrScrollTimer(Sender: TObject);
private
FLayerWidth, FLayerHeight: Integer; // I4429
hLayeredBitmap, hBuffer: THandle;
pvBufferBits, pvLayeredBits: Pointer; // I2554
bmi: TBitmapInfo;
MaxScrollPosition, MaxKeyboardWidth, MaxItemWidth,
TotalKeyboardHeight, TotalItemHeight: Integer;
ScrollPosition: Integer;
imgScroll: array[Boolean, 0..1] of TImage;
Fmnu: TPopupMenu;
FItemRects: array of TRect;
FItemOffsets: array of Integer;
ScrollRects: array[0..1] of TRect;
FSelectedScrollRect, FSelectedItemIndex: Integer;
LayeredCanvas: TCanvas;
dcLayered: Cardinal;
TitleAndStripeHeight: Integer; // I3990
FIsKeyboardMenu: Boolean;
ScrollableView: Boolean;
procedure WMUserFormShown(var Message: TMessage); message WM_USER_FormShown;
procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
procedure WMChar(var Message: TMessage); message WM_CHAR;
procedure FitToDesktop(X, Y: Integer);
procedure DrawLayer;
procedure StartCanvas;
procedure EndCanvas;
procedure RedrawMenuItems;
procedure RecalculateScrollPositions;
function OffsetItemRect(i: Integer): TRect;
procedure DoRedraw;
{ Private declarations }
public
{ Public declarations }
procedure PopupEx(mnu: TPopupMenu; X, Y: Integer; TaskBarLocation: TAlign);
end;
implementation
uses
System.Math,
KeymanPaths,
UfrmKeyman7Main;
{$R *.dfm}
const
// +-------------------------------+ H // I3960
// | [] Keyman | I
// |===============================| J
// | | K
// |---[] keyboard---|---[] menu---|
// ...
// | | L
// +-------------------------------+ M
// A B C D E F G
//
// A = OuterBorderWidth
// B = MenuPadding.Left
// C = MenuPadding.Right
// D = 0 (delineation between white and grey)
// E = MenuPadding.Left
// F = MenuPadding.Right
// G = OuterBorderWidth
// H = OuterBorderWidth
// I = imgTitle.Height
// J = StripeHeight
// K = MenuPadding.Top
// L = MenuPadding.Bottom
// M = OuterBorderWidth
OuterBorderWidth = 1;
MenuPadding: TRect = (Left: 8; Top: 4; Right: 8; Bottom: 4);
ItemMargin = 2;
MenuSpacing = 8;
StripeHeight = 5;
ScrollBoxHeight = 24;
const
CWindowBorder: Cardinal = ($404040);
CWindowBackground: Cardinal = $E8E8E8;
CKeyboardBackground: Cardinal = $FFFFFF;
CTitleBackground: Cardinal = $FFFFFF;
CStripeOrange = $FF8917;
CStripeRed = $BA2135;
CStripeBlue = $6AB8D3;
CSelectedItem = $D3B86A;
type
CardinalArray = array[0..$effffff] of Cardinal;
PCardinalArray = ^CardinalArray;
{ TfrmKeymanMenu }
procedure TfrmKeymanMenu.FormClick(Sender: TObject);
var
i: Integer;
pt: TPoint;
begin
if not Assigned(Fmnu) or not Assigned(Fmnu.Items) then // I2884
Exit;
if not GetCursorPos(pt) then Exit; // I3213 // I3517
pt := ScreenToClient(pt);
if PtInRect(ScrollRects[0], pt) or PtInRect(ScrollRects[1], pt) then
Exit;
for i := 0 to Fmnu.Items.Count - 1 do
begin
if PtInRect(OffsetItemRect(i), pt) then
begin
Hide;
Fmnu.Items[i].Click;
Exit;
end;
end;
end;
procedure TfrmKeymanMenu.FormMouseLeave(Sender: TObject);
var
pt: TPoint;
begin
if (not GetCursorPos(pt) or not PtInRect(Rect(Left, Top, Left+Width, Top+Height), pt)) and Showing then // I2554 // I3213 // I3517
begin
FSelectedScrollRect := -1;
FSelectedItemIndex := -1;
tmrScrollStart.Enabled := False;
tmrScroll.Enabled := False;
DoRedraw;
end;
end;
procedure TfrmKeymanMenu.DoRedraw;
begin
StartCanvas;
try
RedrawMenuItems;
finally
EndCanvas;
end;
end;
procedure TfrmKeymanMenu.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
i: Integer;
begin
FSelectedScrollRect := -1;
FSelectedItemIndex := -1;
if not Assigned(Fmnu) or not Assigned(Fmnu.Items) then // I2884
Exit;
// Check if we are in scroll boxes
for i := 0 to 1 do
if PtInRect(ScrollRects[i], Point(X,Y)) then
begin
if not tmrScroll.Enabled and not tmrScrollStart.Enabled then
begin
tmrScrollStart.Enabled := True;
end;
FSelectedScrollRect := i;
DoRedraw;
Exit;
end;
// Not in scroll boxes, so disable scrolling
tmrScrollStart.Enabled := False;
tmrScroll.Enabled := False;
for i := 0 to Fmnu.Items.Count - 1 do
begin
if PtInRect(OffsetItemRect(i), Point(X,Y)) then
begin
FSelectedItemIndex := i;
Break;
end;
end;
DoRedraw;
end;
function TfrmKeymanMenu.OffsetItemRect(i: Integer): TRect;
begin
Result := FItemRects[i];
OffsetRect(Result, 0, FItemOffsets[i]);
end;
procedure TfrmKeymanMenu.RecalculateScrollPositions;
var
VisibleHeight, i, y, n: Integer;
kmi: TKeymanMenuItem;
begin
if not ScrollableView then
Exit;
// ItemRects don't change, only ItemOffsets
n := 0;
VisibleHeight := 0;
y := ScrollRects[0].Height;
for i := 0 to Fmnu.Items.Count - 1 do
begin
if Fmnu.Items[i] is TKeymanMenuItem then
begin
kmi := Fmnu.Items[i] as TKeymanMenuItem;
if (kmi.CMIItemType = _mitKeyboardsList) then // I3933
begin
if n < ScrollPosition then
begin
// Off the top, we'll hide it off the top of the window
FItemOffsets[i] := -FItemRects[i].Top - FItemRects[i].Height;
Dec(y, FItemRects[i].Height - 1);
end
else if VisibleHeight < ScrollRects[1].Top - ScrollRects[0].Bottom then
begin
FItemOffsets[i] := y;
Inc(VisibleHeight, FItemRects[i].Height - 1);
end
else
begin
// Off the bottom, we'll hide it off the top of the window
FItemOffsets[i] := -FItemRects[i].Top - FItemRects[i].Height;
end;
end
else
FItemOffsets[i] := 0;
Inc(n);
end;
end;
end;
procedure TfrmKeymanMenu.RedrawMenuItems;
var
i: Integer;
kmi: TKeymanMenuItem;
bEnabled: Boolean;
begin
RecalculateScrollPositions;
for i := 0 to Fmnu.Items.Count - 1 do
begin
if Fmnu.Items[i] is TKeymanMenuItem then
begin
kmi := Fmnu.Items[i] as TKeymanMenuItem;
if kmi.CMIItemType in [mitBreak, mitSeparator] then Continue; // I2542
if i = FSelectedItemIndex
then kmi.DrawItem80(LayeredCanvas, OffsetItemRect(i), [odSelected])
else kmi.DrawItem80(LayeredCanvas, OffsetItemRect(i), []);
LayeredCanvas.Brush.Style := bsSolid;
end;
end;
if not ScrollableView then
Exit;
for i := 0 to 1 do
begin
if FSelectedScrollRect = i
then LayeredCanvas.Brush.Color := CSelectedItem
else LayeredCanvas.Brush.Color := CKeyboardBackground;
LayeredCanvas.FillRect(ScrollRects[i]);
bEnabled :=
((i = 0) and (ScrollPosition > 0)) or
((i = 1) and (ScrollPosition < MaxScrollPosition));
LayeredCanvas.Draw(
(ScrollRects[i].Left + ScrollRects[i].Right - imgScroll[bEnabled,i].Width) div 2,
(ScrollRects[i].Top + ScrollRects[i].Bottom - imgScroll[bEnabled,i].Height) div 2,
imgScroll[bEnabled,i].Picture.Bitmap
);
end;
end;
procedure TfrmKeymanMenu.PopupEx(mnu: TPopupMenu; X, Y: Integer; TaskBarLocation: TAlign); // I3990
var
kmi: TKeymanMenuItem;
i, ItemWidth, ItemHeight, TempItemHeight: Integer;
v: Integer;
begin
Fmnu := mnu;
MaxScrollPosition := 0;
MaxKeyboardWidth := 0;
MaxItemWidth := 0;
TotalKeyboardHeight := 0;
TotalItemHeight := 0;
ScrollPosition := 0;
SetLength(FItemRects, Fmnu.Items.Count);
SetLength(FItemOffsets, Fmnu.Items.Count);
FIsKeyboardMenu := True;
for i := 0 to Fmnu.Items.Count - 1 do
if (Fmnu.Items[i] is TKeymanMenuItem) and
((Fmnu.Items[i] as TKeymanMenuItem).CMIItemType <> _mitKeyboardsList) then
begin
FIsKeyboardMenu := False;
Break;
end;
if FIsKeyboardMenu then
begin
TitleAndStripeHeight := 0;
end
else
begin
TitleAndStripeHeight := imgTitle.Height + StripeHeight;
end;
for i := 0 to Fmnu.Items.Count - 1 do
begin
FItemOffsets[i] := 0;
if Fmnu.Items[i] is TKeymanMenuItem then
begin
kmi := Fmnu.Items[i] as TKeymanMenuItem;
kmi.OnMeasureItem(kmi, Canvas, ItemWidth, ItemHeight);
if (kmi.CMIItemType = _mitKeyboardsList) then // I3933
begin
FItemRects[i].Top := OuterBorderWidth + TitleAndStripeHeight + MenuPadding.Top + TotalKeyboardHeight;
FItemRects[i].Bottom := FItemRects[i].Top + ItemHeight + 1;
if MaxKeyboardWidth < ItemWidth then
MaxKeyboardWidth := ItemWidth;
Inc(TotalKeyboardHeight, ItemHeight);
end
else
begin
FItemRects[i].Top := OuterBorderWidth + TitleAndStripeHeight + MenuPadding.Top + TotalItemHeight + 1;
FItemRects[i].Bottom := FItemRects[i].Top + ItemHeight + 1;
if MaxItemWidth < ItemWidth then
MaxItemWidth := ItemWidth;
Inc(TotalItemHeight, ItemHeight + ItemMargin);
end;
end;
end;
//
// Do we need scrollable keyboard list? Only if it's greater than 75% of screen height
//
ScrollableView := TotalKeyboardHeight > (Screen.Height * 3 div 4);
if ScrollableView then
begin
TotalKeyboardHeight := Screen.Height * 3 div 4;
// Calculate the maximum scroll
TempItemHeight := 0;
for i := Fmnu.Items.Count - 1 downto 0 do
begin
if Fmnu.Items[i] is TKeymanMenuItem then
begin
kmi := Fmnu.Items[i] as TKeymanMenuItem;
kmi.OnMeasureItem(kmi, Canvas, ItemWidth, ItemHeight);
if (kmi.CMIItemType = _mitKeyboardsList) then
begin
Inc(TempItemHeight, ItemHeight);
if TempItemHeight > TotalKeyboardHeight - ScrollBoxHeight*2 then
begin
MaxScrollPosition := i + 1;
Break;
end;
end;
end;
end;
end;
if not FIsKeyboardMenu then
Inc(MaxItemWidth, MenuSpacing);
Inc(MaxKeyboardWidth, MenuSpacing);
Dec(TotalItemHeight, ItemMargin); // No bottom margin on last item
if not FIsKeyboardMenu then
if MaxItemWidth + MaxKeyboardWidth + MenuPadding.Left * 2 + MenuPadding.Right * 2 < imgTitle.Width then
MaxItemWidth := imgTitle.Width - MaxKeyboardWidth - MenuPadding.Left * 2 - MenuPadding.Right * 2;
if FIsKeyboardMenu
then Width := OuterBorderWidth + MenuPadding.Left + MaxKeyboardWidth + MenuPadding.Right + OuterBorderWidth
else Width := OuterBorderWidth + MenuPadding.Left + MaxKeyboardWidth + MenuPadding.Right + MenuPadding.Left + MaxItemWidth + MenuPadding.Right + OuterBorderWidth;
Height := OuterBorderWidth + TitleAndStripeHeight + MenuPadding.Top + Max(TotalKeyboardHeight, TotalItemHeight) + MenuPadding.Bottom + OuterBorderWidth;
case taskbarlocation of
alLeft:
begin
Left := X;
Top := Y - Height div 2;
end;
alTop:
begin
Left := X - Width div 2;
Top := Y;
end;
alRight:
begin
Left := X - Width;
Top := Y - Height div 2;
end;
alBottom:
begin
Left := X - Width div 2;
Top := Y - Height;
end;
end;
v := OuterBorderWidth + TitleAndStripeHeight + MenuPadding.Top;
for i := 0 to Fmnu.Items.Count - 1 do
begin
kmi := Fmnu.Items[i] as TKeymanMenuItem;
if (kmi.CMIItemType = _mitKeyboardsList) then // I3933
begin
FItemRects[i].Left := OuterBorderWidth + MenuPadding.Left;
FItemRects[i].Right := FItemRects[i].Left + MaxKeyboardWidth;
end
else
begin
if kmi.CMIItemType = mitSeparator then
begin
// Remaining items are at the bottom of the right hand menu
v := Height - (Fmnu.Items.Count - i - 1) * (ItemHeight + ItemMargin) - MenuPadding.Bottom - OuterBorderWidth + ItemMargin;
Continue;
end;
ItemHeight := FItemRects[i].Bottom - FItemRects[i].Top;
FItemRects[i].Top := v;
FItemRects[i].Bottom := FItemRects[i].Top + ItemHeight;
Inc(v, ItemHeight + ItemMargin);
FItemRects[i].Left := OuterBorderWidth + MenuPadding.Left + MaxKeyboardWidth + MenuPadding.Right + MenuPadding.Left;
FItemRects[i].Right := FItemRects[i].Left + MaxItemWidth;
end;
end;
FSelectedItemIndex := -1;
FSelectedScrollRect := -1;
//
// If the keyboard list is scrollable, we need to display the scroll box items
//
if ScrollableView then
begin
// Scroll up rect
ScrollRects[0] := Rect(
OuterBorderWidth + MenuPadding.Left,
OuterBorderWidth + TitleAndStripeHeight + MenuPadding.Top,
OuterBorderWidth + MenuPadding.Left + MaxKeyboardWidth,
OuterBorderWidth + TitleAndStripeHeight + MenuPadding.Top + ScrollBoxHeight + 1
);
// Scroll down rect
ScrollRects[1] := Rect(
OuterBorderWidth + MenuPadding.Left,
Height - MenuPadding.Bottom - OuterBorderWidth - ScrollBoxHeight - 1,
OuterBorderWidth + MenuPadding.Left + MaxKeyboardWidth,
Height - MenuPadding.Bottom - OuterBorderWidth
);
end
else
begin
ScrollRects[0] := Rect(-1,-1,-1,-1);
ScrollRects[1] := Rect(-1,-1,-1,-1);
end;
FitToDesktop(X, Y);
Show;
end;
procedure TfrmKeymanMenu.DrawLayer; // I3990
procedure DrawBackgroundAndBorders(p: PCardinalArray);
var
x, y: Integer;
dy: Integer;
x1: Integer;
x2: Integer;
begin
{ Draw bottom border }
y := 0;
for x := 0 to FLayerWidth - 1 do p^[x + y] := CWindowBorder; // I2555 // I4429
{ Draw top border }
y := FLayerWidth * (FLayerHeight - 1); // I4429
for x := 0 to FLayerWidth - 1 do p^[x + y] := CWindowBorder; // I2555 // I4429
{ Draw side borders }
for y := 1 to FLayerHeight - 2 do // I4429
begin
dy := FLayerWidth * y; // I4429
p^[dy] := CWindowBorder;
p^[dy + FLayerWidth - 1] := CWindowBorder; // I4429
if not FIsKeyboardMenu and (y >= FLayerHeight - OuterBorderWidth - imgTitle.Height) then // I4429
begin
for x := 1 to FLayerWidth - 2 do p^[dy + x] := CTitleBackground; // I4429
end
else if not FIsKeyboardMenu and (y >= FLayerHeight - OuterBorderWidth - imgTitle.Height - StripeHeight) then // I4429
begin
x1 := FLayerWidth * 5 div 8; // I4429
x2 := FLayerWidth * 7 div 8;
for x := 1 to x1-1 do p^[dy + x] := CStripeOrange;
for x := x1 to x2-1 do p^[dy + x] := CStripeRed;
for x := x2 to FLayerWidth - 2 do p^[dy + x] := CStripeBlue; // I4429
end
else
begin
for x := 1 to MenuPadding.Left + MaxKeyboardWidth + MenuPadding.Right do p^[dy + x] := CKeyboardBackground;
for x := MenuPadding.Left + MaxKeyboardWidth + MenuPadding.Right + 1 to FLayerWidth - 2 do p^[dy + x] := CWindowBackground; // I4429
end;
end;
end;
procedure DrawTitle(dc: THandle);
var
cc: TCanvas;
png: TPngImage;
begin
{ draw the title image into the dc }
cc := TCanvas.Create;
try
cc.Handle := dc;
cc.Brush.Color := $FFFFFF;
if not FIsKeyboardMenu then
begin
png := imgTitle.Picture.Graphic as TPngImage;
cc.Draw(OuterBorderWidth,OuterBorderWidth,png);
end;
finally
cc.Handle := 0;
cc.Free;
end;
end;
var
dc, dcScreen: THandle;
hOldBitmap: THandle;
p: PCardinalArray;
begin
if not Assigned(Fmnu) or not Assigned(Fmnu.Items) then Exit; // I2884
//
// Draw the static elements of the window into a cached bitmap
//
dcScreen := GetDC(Handle);
dc := CreateCompatibleDC(dcScreen);
try
// zero the memory for the bitmap info
ZeroMemory(@bmi, sizeof(BITMAPINFO));
FLayerWidth := ClientWidth; // I4429
FLayerHeight := ClientHeight; // I4429
// setup bitmap info
bmi.bmiHeader.biSize := sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth := FLayerWidth; // I4429
bmi.bmiHeader.biHeight := FLayerHeight; // I4429
bmi.bmiHeader.biPlanes := 1;
bmi.bmiHeader.biBitCount := 32; // four 8-bit components
bmi.bmiHeader.biCompression := BI_RGB;
bmi.bmiHeader.biSizeImage := FLayerWidth * FLayerHeight * 4; // I4429
if hBuffer <> 0 then // I2639
DeleteObject(hBuffer);
if hLayeredBitmap <> 0 then // I2639
DeleteObject(hLayeredBitmap);
// create our DIB section and select the bitmap into the dc
hBuffer := CreateDIBSection(dc, bmi, DIB_RGB_COLORS, pvBufferBits, 0, 0);
hLayeredBitmap := CreateDIBSection(dc, bmi, DIB_RGB_COLORS, pvLayeredBits, 0, 0); // I2555
hOldBitmap := SelectObject(dc, hBuffer);
try
p := PCardinalArray(pvBufferBits);
DrawBackgroundAndBorders(p);
DrawTitle(dc);
finally
SelectObject(dc, hOldBitmap);
end;
finally
DeleteDC(dc);
ReleaseDC(Handle, dcScreen);
end;
DoRedraw;
end;
procedure TfrmKeymanMenu.FitToDesktop(X, Y: Integer);
var
m: TMonitor;
wr, mr: TRect;
begin
wr := Rect(Left,Top,Left+Width,Top+Height);
m := Screen.MonitorFromPoint(Point(X, Y), mdNearest);
if not Assigned(m) // I2693, I2820
then mr := Screen.WorkAreaRect
else mr := m.WorkareaRect;
if wr.Top < mr.Top then Top := mr.Top
else if wr.Bottom > mr.Bottom then Top := mr.Bottom - (wr.Bottom-wr.Top);
if wr.Left < mr.Left then Left := mr.Left
else if wr.Right > mr.Right then Left := mr.Right - (wr.Right-wr.Left);
end;
procedure TfrmKeymanMenu.TntFormCreate(Sender: TObject);
var
FImagePath: string;
begin
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or WS_EX_LAYERED);
FImagePath := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KeymanMenuTitleImage);
if FileExists(FImagePath) then
imgTitle.Picture.LoadFromFile(TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KeymanMenuTitleImage));
imgTitle.AutoSize := True;
imgScroll[False,0] := imgScrollUpDisabled;
imgScroll[False,1] := imgScrollDownDisabled;
imgScroll[True,0] := imgScrollUp;
imgScroll[True,1] := imgScrollDown;
end;
procedure TfrmKeymanMenu.TntFormDestroy(Sender: TObject);
begin
if hBuffer <> 0 then
DeleteObject(hBuffer);
hBuffer := 0;
pvBufferBits := nil; // I2554
if hLayeredBitmap <> 0 then
DeleteObject(hLayeredBitmap);
hLayeredBitmap := 0;
pvLayeredBits := nil; // I2554
end;
procedure TfrmKeymanMenu.TntFormHide(Sender: TObject);
begin
if hBuffer <> 0 then
DeleteObject(hBuffer);
hBuffer := 0;
tmrScrollStart.Enabled := False;
tmrScroll.Enabled := False;
end;
procedure TfrmKeymanMenu.TntFormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
FOldIndex: Integer;
i: Integer;
FOldRect: TRect;
begin
tmrScrollStart.Enabled := False;
tmrScroll.Enabled := False;
if not Assigned(Fmnu) or not Assigned(Fmnu.Items) then Exit; // I2884
case Key of
VK_UP:
begin
Key := 0;
Dec(FSelectedItemIndex);
if FSelectedItemIndex < 0 then FSelectedItemIndex := Fmnu.Items.Count - 1;
while FSelectedItemIndex >= 0 do
if Fmnu.Items[FSelectedItemIndex].IsLine then Dec(FselectedItemIndex) else Break;
end;
VK_DOWN:
begin
Key := 0;
Inc(FSelectedItemIndex);
if FSelectedItemIndex >= Fmnu.Items.Count then FSelectedItemIndex := 0;
while FSelectedItemIndex < Fmnu.Items.Count - 1 do
if Fmnu.Items[FSelectedItemIndex].IsLine then Inc(FselectedItemIndex) else Break;
end;
VK_LEFT:
begin
Key := 0;
if FSelectedItemIndex < 0 then Exit;
FOldIndex := FSelectedItemIndex;
FOldRect := OffsetItemRect(FOldIndex);
OffsetRect(FOldRect, 0, FItemOffsets[FOldIndex]);
for i := 0 to FOldIndex - 1 do
begin
if (OffsetItemRect(i).Right <= FOldRect.Left) and
(OffsetItemRect(i).Bottom >= (FOldRect.Top+FOldRect.Bottom) div 2) then
begin
FSelectedItemIndex := i;
Break;
end;
end;
while FSelectedItemIndex < Fmnu.Items.Count - 1 do
if Fmnu.Items[FSelectedItemIndex].IsLine then Inc(FselectedItemIndex) else Break;
end;
VK_RIGHT:
begin
Key := 0;
if FSelectedItemIndex < 0 then Exit;
FOldIndex := FSelectedItemIndex;
FOldRect := OffsetItemRect(FOldIndex);
for i := FOldIndex + 1 to Fmnu.Items.Count - 1 do
begin
if (OffsetItemRect(i).Left >= FOldRect.Right) and
(OffsetItemRect(i).Bottom >= (FOldRect.Top+FOldRect.Bottom) div 2) then
begin
FSelectedItemIndex := i;
Break;
end;
end;
while FSelectedItemIndex < Fmnu.Items.Count - 1 do
if Fmnu.Items[FSelectedItemIndex].IsLine then Inc(FselectedItemIndex) else Break;
end;
VK_RETURN:
begin
Key := 0;
if (FSelectedItemIndex >= 0) then
begin
Hide;
Fmnu.Items[FSelectedItemIndex].Click;
end;
Exit;
end;
VK_ESCAPE:
begin
Key := 0;
Hide;
frmKeyman7Main.SetLastFocus;
Exit;
end
else Exit;
end;
if ScrollableView and (FSelectedItemIndex >= 0) and ((Fmnu.Items[FSelectedItemIndex] as TKeymanMenuItem).CMIItemType = _mitKeyboardsList) then
begin
if FSelectedItemIndex < ScrollPosition then
ScrollPosition := FSelectedItemIndex
else
while (FItemRects[FSelectedItemIndex].Top + FItemOffsets[FSelectedItemIndex] < 0) or
(FItemRects[FSelectedItemIndex].Bottom + FItemOffsets[FSelectedItemIndex] > ScrollRects[1].Top) do
begin
Inc(ScrollPosition);
RecalculateScrollPositions;
end;
end;
DoRedraw;
end;
procedure TfrmKeymanMenu.StartCanvas;
var
dcScreen: THandle;
begin
Assert(not Assigned(LayeredCanvas)); // I2554
if Assigned(pvLayeredBits) and Assigned(pvBufferBits) and (hLayeredBitmap <> 0) then // I2554
begin
dcScreen := GetDC(Handle);
dcLayered := CreateCompatibleDC(dcScreen);
SelectObject(dcLayered, hLayeredBitmap);
ReleaseDC(Handle, dcScreen);
CopyMemory(pvLayeredBits, pvBufferBits, FLayerWidth * FLayerHeight * 4); // I4429
LayeredCanvas := TCanvas.Create;
LayeredCanvas.Handle := dcLayered;
end;
end;
procedure TfrmKeymanMenu.tmrScrollStartTimer(Sender: TObject);
begin
tmrScrollStart.Enabled := False;
tmrScroll.Enabled := True;
end;
procedure TfrmKeymanMenu.tmrScrollTimer(Sender: TObject);
begin
if FSelectedScrollRect < 0 then
tmrScroll.Enabled := False
else if FSelectedScrollRect = 0 then
begin
if ScrollPosition = 0 then
tmrScroll.Enabled := False
else
Dec(ScrollPosition);
end
else
begin
if ScrollPosition >= MaxScrollPosition then
tmrScroll.Enabled := False
else
Inc(ScrollPosition);
end;
DoRedraw;
end;
procedure TfrmKeymanMenu.EndCanvas;
var
dcScreen: THandle;
blend: TBlendFunction;
ptPos, ptSrc: TPoint;
szWnd: TSize;
begin
if not Assigned(LayeredCanvas) then Exit; // I2554
try
LayeredCanvas.Handle := 0;
if Assigned(pvBufferBits) and Assigned(pvLayeredBits) then // I2554
begin
GdiFlush;
blend.BlendOp := AC_SRC_OVER;
blend.BlendFlags := 0;
blend.SourceConstantAlpha := 255;
blend.AlphaFormat := AC_SRC_ALPHA;
ptPos := Point(Left, Top);
szWnd.cx := FLayerWidth; // I4429
szWnd.cy := FLayerHeight; // I4429
ptSrc := Point(0, 0);
dcScreen := GetDC(Handle);
if not UpdateLayeredWindow(Handle, dcScreen, @ptPos, @szWnd, dcLayered, @ptSrc, 0, @blend, ULW_OPAQUE) then
RaiseLastOSError;
ReleaseDC(Handle, dcScreen);
end;
finally
FreeAndNil(LayeredCanvas);
DeleteDC(dcLayered);
dcLayered := 0; // I2554
end;
end;
procedure TfrmKeymanMenu.TntFormShow(Sender: TObject);
begin
PostMessage(Handle, WM_USER_FormShown, 0, 0); // I2582
DrawLayer;
end;
procedure TfrmKeymanMenu.WMChar(var Message: TMessage);
var
i: Integer;
kmi: TKeymanMenuItem;
begin
if not Assigned(Fmnu) or not Assigned(Fmnu.Items) then Exit; // I2884
for i := 0 to Fmnu.Items.Count - 1 do
begin
if Fmnu.Items[i] is TKeymanMenuItem then
begin
kmi := Fmnu.Items[i] as TKeymanMenuItem;
if IsAccel(Message.WParam, kmi.WideCaption) then
begin
Hide;
Fmnu.Items[i].Click;
Exit;
end;
end;
end;
end;
procedure TfrmKeymanMenu.WMKillFocus(var Message: TWMKillFocus);
begin
inherited;
Hide;
end;
procedure TfrmKeymanMenu.WMUserFormShown(var Message: TMessage); // I2582
begin
BringToFront; // I2582
end;
end.