lazarus/lcl/interfaces/qt6/qtint.pp

575 lines
19 KiB
ObjectPascal

{
/***************************************************************************
qtint.pp - Qt6 Interface Object
-------------------
Initial Revision : Fri Oct 28 2022
***************************************************************************/
*****************************************************************************
This file is part of the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
}
unit qtint;
{$mode objfpc}{$H+}
interface
{$I qtdefines.inc}
{$ifdef Trace}
{$ASSERTIONS ON}
{$endif}
uses
{$IFDEF MSWINDOWS}
Windows, // used to retrieve correct caption color values
{$ENDIF}
// Bindings - qt6 must come first to avoid type redefinition problems
qt6,
// FPC
Classes, SysUtils, Math, Types,
// LCL
InterfaceBase, LCLPlatformDef, LCLProc, LCLType, LMessages,
LCLMessageGlue, LCLStrConsts, Controls, ExtCtrls, Forms,
Dialogs, StdCtrls, LCLIntf, GraphUtil, Themes,
// LazUtils
GraphType, LazStringUtils, LazUtilities, LazLoggerBase, LazUTF8, Maps,
// WS
{$IFDEF HASX11}
XAtom, X, XLib, XKB, xkblib,
qtx11dummywidget,
{$ENDIF}
qtproc;
type
{ TQtWidgetSet }
TQtWidgetSet = Class(TWidgetSet)
private
App: QApplicationH;
{$IFDEF QtUseNativeEventLoop}
FMainTimerID: integer;
{$ENDIF}
FIsLibraryInstance: Boolean;
// cache for WindowFromPoint
FLastWFPMousePos: TPoint;
FLastWFPResult: HWND;
// global actions
FGlobalActions: TFPList;
FAppActive: Boolean;
FOverrideCursor: TObject;
SavedDCList: TFPList;
CriticalSection: TRTLCriticalSection;
SavedHandlesList: TMap;
FSocketEventMap: TMap;
StayOnTopList: TMap;
SysTrayIconsList: TFPList;
// global hooks
FAppEventApplicationStateHook: QGuiApplication_hookH;
FAppEvenFilterHook: QObject_hookH;
{$IFDEF QTUSEFOCUSCHANGEDHOOK}
FAppFocusChangedHook: QApplication_hookH;
{$ENDIF}
FAppSessionQuit: QGUIApplication_hookH;
FAppSaveSessionRequest: QGUIApplication_hookH;
// default application font name (FamilyName for "default" font)
FDefaultAppFontName: WideString;
FDockImage: QRubberBandH;
FDragImageList: QWidgetH;
FDragHotSpot: TPoint;
FDragImageLock: Boolean;
FCachedColors: array[0..MAX_SYS_COLORS] of PLongWord;
FSysColorBrushes: array[0..MAX_SYS_COLORS] of HBrush;
{$IFDEF HASX11}
SavedHintHandlesList: TFPList;
FWindowManagerName: String; // Track various incompatibilities between WM. Initialized at WS start.
{$ENDIF}
// qt style does not have pixel metric for themed menubar (menu) height
// so we must calculate it somehow.
FCachedMenuBarHeight: Integer;
function GetMenuHeight: Integer;
procedure ClearCachedColors;
function GetStyleName: String;
procedure SetOverrideCursor(const AValue: TObject);
procedure QtRemoveStayOnTop(const ASystemTopAlso: Boolean = False);
procedure QtRestoreStayOnTop(const ASystemTopAlso: Boolean = False);
procedure SetDefaultAppFontName;
protected
FPenForSetPixel: QPenH;
FInGetPixel: boolean;
FStockNullBrush: HBRUSH;
FStockBlackBrush: HBRUSH;
FStockLtGrayBrush: HBRUSH;
FStockGrayBrush: HBRUSH;
FStockDkGrayBrush: HBRUSH;
FStockWhiteBrush: HBRUSH;
FStockNullPen: HPEN;
FStockBlackPen: HPEN;
FStockWhitePen: HPEN;
FStockSystemFont: HFONT;
FStockDefaultDC: HDC;
{$IFDEF HASX11}
FX11Display: PDisplay;
FWSFrameRect: TRect;
{$ENDIF}
function CreateThemeServices: TThemeServices; override;
function EventFilter(Sender: QObjectH; Event: QEventH): Boolean; cdecl;
{$IFDEF QTUSEFOCUSCHANGEDHOOK}
procedure FocusChanged(aold: QWidgetH; anew: QWidgetH); cdecl;
{$ENDIF}
procedure AppStateChanged(AState: QtApplicationState); cdecl;
procedure OnWakeMainThread(Sender: TObject);
{$ifndef QT_NO_SESSIONMANAGER}
procedure SlotCommitDataRequest(sessionManager: QSessionManagerH); cdecl;
procedure SlotSaveDataRequest(sessionManager: QSessionManagerH); cdecl;
{$endif}
public
function LCLPlatform: TLCLPlatform; override;
function GetLCLCapability(ACapability: TLCLCapability): PtrUInt; override;
// Application
procedure AppInit(var ScreenInfo: TScreenInfo); override;
procedure AppRun(const ALoop: TApplicationMainLoop); override;
procedure AppWaitMessage; override;
procedure AppProcessMessages; override;
procedure AppTerminate; override;
procedure AppMinimize; override;
procedure AppRestore; override;
procedure AppBringToFront; override;
procedure AppSetIcon(const Small, Big: HICON); override;
procedure AppSetTitle(const ATitle: string); override;
function AppRemoveStayOnTopFlags(const ASystemTopAlso: Boolean = False): Boolean; override;
function AppRestoreStayOnTopFlags(const ASystemTopAlso: Boolean = False): Boolean; override;
{$IFDEF HASX11}
function CreateDummyWidgetFrame(const ALeft, ATop, AWidth, AHeight: integer): boolean;
function GetDummyWidgetFrame: TRect;
function x11Display: PDisplay;
{$ENDIF}
public
constructor Create; override;
destructor Destroy; override;
function DCGetPixel(CanvasHandle: HDC; X, Y: integer): TGraphicsColor; override;
procedure DCSetPixel(CanvasHandle: HDC; X, Y: integer; AColor: TGraphicsColor); override;
procedure DCRedraw(CanvasHandle: HDC); override;
procedure DCSetAntialiasing(CanvasHandle: HDC; AEnabled: Boolean); override;
procedure SetDesigning(AComponent: TComponent); override;
// create and destroy
function CreateTimer(Interval: integer; TimerFunc: TWSTimerProc): TLCLHandle; override;
function DestroyTimer(TimerHandle: TLCLHandle): boolean; override;
// device contexts
function IsValidDC(const DC: HDC): Boolean; virtual;
function IsValidGDIObject(const GDIObject: HGDIOBJ): Boolean; virtual;
// qt object handles map
procedure AddHandle(AHandle: TObject);
procedure RemoveHandle(AHandle: TObject);
function IsValidHandle(AHandle: HWND): Boolean;
// qt systray icons map
procedure RegisterSysTrayIcon(AHandle: TObject);
procedure UnRegisterSysTrayIcon(AHandle: TObject);
function IsValidSysTrayIcon(AHandle: HWND): Boolean;
{$IFDEF HASX11}
// qt hints handles map (needed on X11 only)
procedure AddHintHandle(AHandle: TObject);
procedure RemoveHintHandle(AHandle: TObject);
procedure RemoveAllHintsHandles;
function IsValidHintHandle(AHandle: TObject): Boolean;
procedure HideAllHints;
procedure RestoreAllHints;
{$ENDIF}
// application global actions (mainform mainmenu mnemonics Alt+XX)
procedure ClearGlobalActions;
procedure AddGlobalAction(AnAction: QActionH);
function ShortcutInGlobalActions(const AMnemonicText: WideString;
out AGlobalActionIndex: Integer): Boolean;
procedure TriggerGlobalAction(const ActionIndex: Integer);
// cache for WindowFromPoint to reduce very expensive calls
// of QApplication_widgetAt() inside WindowFromPoint().
function IsWidgetAtCache(AHandle: HWND): Boolean;
procedure InvalidateWidgetAtCache;
function IsValidWidgetAtCachePointer: Boolean;
function GetWidgetAtCachePoint: TPoint;
// drag image list
function DragImageList_BeginDrag(AImage: QImageH; AHotSpot: TPoint): Boolean;
procedure DragImageList_EndDrag;
function DragImageList_DragMove(X, Y: Integer): Boolean;
function DragImageList_SetVisible(NewVisible: Boolean): Boolean;
public
{$IFDEF HASX11}
FLastMinimizeEvent: DWord; // track mainform minimize events -> TQtMainWindow.EventFilter
FMinimizedByPager: Boolean; // track if app is minimized via desktop pager or by us.
{$ENDIF}
{$IFDEF MSWINDOWS}
function GetWinKeyState(AKeyState: LongInt): SHORT;
{$ENDIF}
function CreateDefaultFont: HFONT; virtual;
function GetDefaultAppFontName: WideString;
function GetQtDefaultDC: HDC; virtual;
procedure DeleteDefaultDC; virtual;
procedure SetQtDefaultDC(Handle: HDC); virtual;
procedure InitStockItems;
procedure FreeStockItems;
procedure FreeSysColorBrushes(const AInvalidateHandlesOnly: Boolean = False);
property AppActive: Boolean read FAppActive;
property DragImageLock: Boolean read FDragImageLock write FDragImageLock;
{do not create new QApplication object if we are called from library }
property IsLibraryInstance: Boolean read FIsLibraryInstance;
property OverrideCursor: TObject read FOverrideCursor write SetOverrideCursor;
property StyleName: String read GetStyleName;
{$IFDEF HASX11}
property WindowManagerName: String read FWindowManagerName;
{$ENDIF}
{$I qtwinapih.inc}
{$I qtlclintfh.inc}
end;
type
TEventProc = record
Name : String[25];
CallBack : procedure(Data : TObject);
Data : Pointer;
end;
CallbackProcedure = procedure (Data : Pointer);
pTRect = ^TRect;
function HwndFromWidgetH(const WidgetH: QWidgetH): HWND;
function DTFlagsToQtFlags(const Flags: Cardinal): Integer;
function GetPixelMetric(AMetric: QStylePixelMetric; AOption: QStyleOptionH;
AWidget: QWidgetH): Integer;
function GetQtVersion: String;
function QtVersionCheck(const AMajor, AMinor, AMicro: Integer): Boolean;
{$IFDEF HASX11}
function IsX11: boolean;
function IsWayland: Boolean; {pure wayland, not XWayland !}
function IsCurrentDesktop(AWidget: QWidgetH): Boolean;
function isCompositingManagerRunning: boolean;
function X11Raise(AHandle: PtrUInt): boolean;
function X11GetActiveWindow: QWidgetH;
function GetWindowManager: String;
procedure SetSkipX11Taskbar(Widget: QWidgetH; const ASkipTaskBar: Boolean);
{check if XWindow have _NET_WM_STATE_ABOVE and our form doesn''t know anything about it}
function GetAlwaysOnTopX11(Widget: QWidgetH): boolean;
{check KDE session version. Possible results are > 2, -1 means not running under KDE}
function GetKdeSessionVersion: integer;
{force mapping}
procedure MapX11Window(AWinID: PtrUInt);
{$IFDEF QtUseX11Extras}
// do not remove those, used for testing purposes
function GetX11WindowRealized(AWinID: PtrUInt): boolean;
function GetX11WindowAttributes(AWinID: PtrUInt; out ALeft, ATop, AWidth, AHeight, ABorder: integer): boolean;
function GetX11SupportedAtoms(AWinID: PtrUInt; AList: TStrings): boolean;
{Ask for _NET_FRAME_EXTENTS,_KDE_NET_WM_SHADOW,_GTK_NET_FRAME_EXTENTS}
function GetX11RectForAtom(AWinID: PtrUInt; const AAtomName: string; out ARect: TRect): boolean;
function GetX11WindowPos(AWinID: PtrUInt; out ALeft, ATop: integer): boolean;
function SetX11WindowPos(AWinID: PtrUInt; const ALeft, ATop: integer): boolean;
function GetX11WindowGeometry(AWinID: PtrUInt; out ARect: TRect): boolean;
{check if wm supports request for frame extents}
function AskX11_NET_REQUEST_FRAME_EXTENTS(AWinID: PtrUInt; out AMargins: TRect): boolean;
{$ENDIF}
{$ENDIF}
const
QtVersionMajor: Integer = 0;
QtVersionMinor: Integer = 0;
QtVersionMicro: Integer = 0;
QtMinimumWidgetSize = 0;
QtMaximumWidgetSize = 16777215;
TargetEntrys = 3;
QEventLCLMessage = QEventUser;
// QEventType(Ord(QEventUser) + $1000) is reserved by
// LCLQt_Destroy (qtobjects) to reduce includes !
LCLQt_CheckSynchronize = QEventType(Ord(QEventUser) + $1001);
LCLQt_PopupMenuClose = QEventType(Ord(QEventUser) + $1002);
LCLQt_PopupMenuTriggered = QEventType(Ord(QEventUser) + $1003);
// QEventType(Ord(QEventUser) + $1004 is reserved by
// LCLQt_ClipboardPrimarySelection (qtobjects) to reduce includes !
LCLQt_ApplicationActivate = QEventType(Ord(QEventUser) + $1005);
// deactivate sent from qt
LCLQt_ApplicationDeactivate = QEventType(Ord(QEventUser) + $1006);
// deactivate sent from LCLQt_ApplicationDeactivate to check it twice
// instead of using timer.
LCLQt_ApplicationDeactivate_Check = QEventType(Ord(QEventUser) + $1007);
// needed by itemviews (TQtListWidget, TQtTreeWidget)
LCLQt_ItemViewAfterMouseRelease = QEventType(Ord(QEventUser) + $1008);
// used by TQtTabWidget
LCLQt_DelayLayoutRequest = QEventType(Ord(QEventUser) + $1009);
// delayed resize event if wincontrol is computing bounds
LCLQt_DelayResizeEvent = QEventType(Ord(QEventUser) + $1010);
// systemtrayicon event, used to find and register private QWidget of QSystemTrayIcon
LCLQt_RegisterSystemTrayIcon = QEventType(Ord(QEventUser) + $1011);
// combobox OnCloseUp should be in order OnChange->OnSelect->OnCloseUp
LCLQt_ComboBoxCloseUp = QEventType(Ord(QEventUser) + $1012);
QtTextSingleLine = $0100;
QtTextDontClip = $0200;
QtTextExpandTabs = $0400;
QtTextShowMnemonic = $0800;
QtTextWordWrap = $1000;
QtTextWrapAnywhere = $2000;
QtTextHideMnemonic = $8000;
QtTextDontPrint = $4000;
QtTextIncludeTrailingSpaces = $08000000;
QtTextJustificationForced = $10000;
var
QtWidgetSet: TQtWidgetSet;
implementation
uses
////////////////////////////////////////////////////
// I M P O R T A N T
////////////////////////////////////////////////////
// To get as little as possible circles,
// uncomment only those units with implementation
////////////////////////////////////////////////////
QtCaret,
QtThemes,
////////////////////////////////////////////////////
Graphics, buttons, Menus,
// Bindings
QtWSFactory, qtwidgets, qtobjects, qtsystemtrayicon;
function DTFlagsToQtFlags(const Flags: Cardinal): Integer;
begin
Result := 0;
// horizontal alignment
if Flags and DT_CENTER <> 0 then
Result := Result or QtAlignHCenter
else
if Flags and DT_RIGHT <> 0 then
Result := Result or QtAlignRight
else
Result := Result or QtAlignLeft;
// vertical alignment
if Flags and DT_VCENTER <> 0 then
Result := Result or QtAlignVCenter
else
if Flags and DT_BOTTOM <> 0 then
Result := Result or QtAlignBottom
else
Result := Result or QtAlignTop;
// mutually exclusive wordbreak and singleline
if Flags and DT_WORDBREAK <> 0 then
Result := Result or QtTextWordWrap
else
if Flags and DT_SINGLELINE <> 0 then
Result := Result or QtTextSingleLine;
if Flags and DT_NOPREFIX = 0 then
Result := Result or QtTextShowMnemonic;
if Flags and DT_NOCLIP <> 0 then
Result := Result or QtTextDontClip;
if Flags and DT_EXPANDTABS <> 0 then
Result := Result or QtTextExpandTabs;
end;
function GetPixelMetric(AMetric: QStylePixelMetric; AOption: QStyleOptionH;
AWidget: QWidgetH): Integer;
begin
Result := QStyle_pixelMetric(QApplication_style(),
AMetric, AOption, AWidget);
end;
function QtObjectFromWidgetH(const WidgetH: QWidgetH): TQtWidget;
var
V: QVariantH;
Ok: Boolean;
Obj: TObject;
QtWg: TQtWidget;
begin
Result := nil;
if WidgetH = nil then
exit;
V := QVariant_Create();
try
QObject_property(QObjectH(WidgetH), V, 'lclwidget');
if not QVariant_IsNull(v) and QVariant_isValid(V) then
begin
//Write('Got a valid variant .. ');
{$IFDEF CPU32}
Obj := TObject(QVariant_toUint(V, @Ok));
{$ENDIF}
{$IFDEF CPU64}
Obj := TObject(QVariant_toULongLong(V, @Ok));
{$ENDIF}
if OK and QtWidgetset.IsValidHandle(HWND(Obj)) then
begin
if not (Obj is TQtWidget) then
raise Exception.Create('QtObjectFromWidgetH: QObject_property returned '
+ 'a variant which is not TQtWidget ' + dbgHex(PtrUInt(Obj)));
QtWg := TQtWidget(Obj);
//Write('Converted successfully, Control=');
if QtWg<>nil then
begin
Result := QtWg;
//WriteLn(Result.LCLObject.Name);
end else
;//WriteLn('nil');
end else
;//WriteLn('Can''t convert to UINT');
end else
;//Writeln('GetFocus: Variant is NULL or INVALID');
finally
QVariant_Destroy(V);
end;
end;
function HwndFromWidgetH(const WidgetH: QWidgetH): HWND;
begin
Result := 0;
if WidgetH = nil then
exit;
Result := HWND(QtObjectFromWidgetH(WidgetH));
end;
function GetFirstQtObjectFromWidgetH(WidgetH: QWidgetH): TQtWidget;
begin
Result := nil;
if WidgetH = nil then
Exit;
repeat
Result := QtObjectFromWidgetH(WidgetH);
if Result = nil then
begin
WidgetH := QWidget_parentWidget(WidgetH);
if WidgetH = nil then
break;
end;
until Result <> nil;
end;
function ConvertFontWeightToQtConst(Value: Integer): Integer;
begin
case Value of
QtFontWeight_Thin: Result := FW_THIN;
QtFontWeight_ExtraLight: Result := FW_EXTRALIGHT;
QtFontWeight_Light: Result := FW_LIGHT;
QtFontWeight_Normal: Result := FW_NORMAL;
QtFontWeight_Medium: Result := FW_MEDIUM;
QtFontWeight_DemiBold: Result := FW_SEMIBOLD;
QtFontWeight_Bold: Result := FW_BOLD;
QtFontWeight_ExtraBold: Result := FW_EXTRABOLD;
QtFontWeight_Black: Result := FW_HEAVY;
else
Result := Round(Value * 9.5);
end;
end;
{------------------------------------------------------------------------------
Method: GetQtVersion
Params: none
Returns: String
Returns current Qt lib version used by application.
------------------------------------------------------------------------------}
function GetQtVersion: String;
begin
Result := QtVersion;
end;
procedure QtVersionInt(out AMajor, AMinor, AMicro: integer);
var
S: String;
AList: TStringList;
begin
AMajor := 0;
AMinor := 0;
AMicro := 0;
S := GetQtVersion;
// 5 is usual length of qt5 version eg. 5.6.1
if length(S) < 5 then
exit;
AList := TStringList.Create;
try
AList.Delimiter := '.';
AList.DelimitedText := S;
TryStrToInt(AList[0], AMajor);
TryStrToInt(AList[1], AMinor);
TryStrToInt(AList[2], AMicro);
finally
AList.Free;
end;
end;
{------------------------------------------------------------------------------
Method: QtVersionCheck
Params: AMajor, AMinor, AMicro: Integer
Returns: Boolean
Function checks if qt lib version satisfies our function params values.
Returns TRUE if successfull.
It is possible to check Major and/or Minor version only (or any of those
3 params) by setting it's param to -1.
eg. QtVersionCheck(4, 5, -1) checks only major and minor version and will
not process micro version check.
NOTE: It checks qt lib version used by application.
------------------------------------------------------------------------------}
function QtVersionCheck(const AMajor, AMinor, AMicro: Integer): Boolean;
begin
Result := False;
if AMajor > 0 then
Result := AMajor = QtVersionMajor;
if (AMajor > 0) and not Result then
exit;
if AMinor >= 0 then
Result := AMinor = QtVersionMinor;
if (AMinor >= 0) and not Result then
exit;
if AMicro >= 0 then
Result := AMicro = QtVersionMicro;
end;
{$IFDEF HASX11}
{$I qtx11.inc}
{$ENDIF}
{$I qtobject.inc}
{$I qtwinapi.inc}
{$I qtlclintf.inc}
end.