lazarus/lcl/interfaces/gtk2/gtk2callback.inc

3868 lines
126 KiB
PHP

{%MainUnit gtk2proc.pp}
{
*****************************************************************************
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.
*****************************************************************************
}
function DoDeliverPaintMessage(const Target: TObject; var PaintMsg: TLMPaint): PtrInt;
var
WidgetInfo: PWidgetInfo;
IsCustomControl: Boolean;
begin
{
erase backgound of custom controls
use only for real custom controls that are GTKAPIWidget
}
IsCustomControl := TObject(Target) is TCustomControl;
if IsCustomControl then begin
Include(TWinControlAccess(Target).FWinControlFlags, wcfEraseBackground);
TWinControl(Target).Perform(LM_ERASEBKGND, PtrInt(PaintMsg.DC), 0);
Exclude(TWinControlAccess(Target).FWinControlFlags, wcfEraseBackground);
end;
Result := DeliverMessage(Target, PaintMsg);
if IsCustomControl then begin
WidgetInfo := GetWidgetInfo({%H-}PGtkWidget(TCustomControl(Target).Handle));
if WidgetInfo <> nil then
WidgetInfo^.UpdateRect := Rect(0,0,0,0);
end;
end;
function DeliverPaintMessage(const Target: Pointer; var TheMessage): GBoolean;
var
PaintMsg: TLMPaint;
begin
if TLMessage(TheMessage).Msg = LM_GTKPAINT then
PaintMsg := GtkPaintMessageToPaintMessage(TLMGtkPaint(TheMessage), True)
else
PaintMsg := TLMPaint(TheMessage);
Result := DoDeliverPaintMessage(TObject(Target), PaintMsg) = 0;
FinalizePaintMessage(PLMessage(@PaintMsg));
end;
{-------------------------------------------------------------------------------
function DeliverPostMessage(const Target: Pointer; var TheMessage): GBoolean;
'TheMessage' is in TLMessage format. Don't confuse this with tagMsg.
--------------------------------------------------------------------------------}
function DeliverPostMessage(const Target: Pointer; var TheMessage): GBoolean;
begin
if TObject(Target) is TWinControl then
begin
// convert TLMessage into a tagMsg and push on the message queue
Result := PostMessage(TWinControl(Target).Handle,
TLMessage(TheMessage).Msg,
TLMessage(TheMessage).WParam,
TLMessage(TheMessage).LParam
);
end
else
begin
if TLMessage(TheMessage).Msg <> LM_GTKPAINT then
Result := DeliverMessage(Target, TheMessage) = 0
else
Result := DeliverPaintMessage(Target, TheMessage);
end;
end;
function DeliverGtkPaintMessage(Target: Pointer; Widget: PGtkWidget;
Area: PGDKRectangle; RepaintAll, IsAfterGtk: boolean): GBoolean;
var
Msg: TLMGtkPaint;
AInfo: PWidgetInfo;
begin
//DebugLn(['DeliverGtkPaintMessage ',DbgSName(TObject(Target)),' Widget=',GetWidgetDebugReport(Widget),' RepaintAll=',RepaintAll,' AfterGtk=',IsAfterGtk,' Area=',dbgs(Area)]);
// default is, that a control receives the paint message after gtk (including the child paints)
// In case of TCustomControl, there is no gtk painting only the
// child paintings. Let the TCustomControl paint the background.
// ToDo: Eventually there must be a 'before paint message'.
if (TObject(Target) is TCustomForm) then
begin
AInfo := GetWidgetInfo(Widget);
AInfo^.FirstPaint := True;
end;
if IsAfterGtk then
begin
if TObject(Target) is TCustomControl then exit(false);
end else
begin
if not (TObject(Target) is TCustomControl) then exit(false);
end;
if (not RepaintAll) and ((Area^.Width<1) or (Area^.Height<1)) then exit(false);
Msg.Msg := LM_GTKPAINT;
Msg.Data := TLMGtkPaintData.Create;
Msg.Data.Widget := Widget;
Msg.Data.State := GtkPaint_LCLWidget;
Msg.Data.Rect := Bounds(Area^.x, Area^.y, Area^.Width, Area^.Height);
Msg.Data.RepaintAll := RepaintAll;
// the gtk2 has a working double buffering and expose event area
Result := DeliverPaintMessage(Target, Msg);
end;
procedure EventTrace(const TheMessage : string; data : pointer);
begin
// if Data = nil then
//DebugLn(Format('Trace:Event [%s] fired',[Themessage]))
// else
//DebugLn(Format('Trace:Event [%s] fired for %s',
// [TheMessage, TObject(data).Classname]));
end;
{*************************************************************}
{ callback routines }
{*************************************************************}
{-------------------------------------------------------------------------------
function gtkNoteBookCloseBtnClicked
Params: Widget: PGtkWidget; Data: Pointer
Result: GBoolean
gtkNoteBookCloseBtnClicked is called by the gtk, whenever a close button in
the tab of a notebook page is clicked.
-------------------------------------------------------------------------------}
function gtkNoteBookCloseBtnClicked({%H-}Widget: PGtkWidget;
Data: Pointer): GBoolean; cdecl;
var APage: TCustomPage;
begin
Result:=true; // handled = true
if ComponentIsDestroyingHandle(TWinControl(Data)) then exit;
APage:=TCustomPage(Data);
TCustomTabControl(APage.Parent).DoCloseTabClicked(APage);
end;
function FilterFuc({%H-}xevent: PGdkXEvent; {%H-}event: PGdkEvent; {%H-}data: gpointer): TGdkFilterReturn; cdecl;
{$ifdef windows}
var
AForm: TCustomForm absolute data;
{$endif}
begin
Result := GDK_FILTER_CONTINUE;
{$ifdef windows}
if (PMSG(xevent)^.message = WM_NCLBUTTONDOWN) and
(PMSG(xevent)^.wParam = HTCAPTION) and
not (csDesigning in AForm.ComponentState) and
(TWinControlAccess(TWinControl(AForm)).DragKind = dkDock) and
(TWinControlAccess(TWinControl(AForm)).DragMode = dmAutomatic) then
begin
AForm.BeginDrag(True);
Result := GDK_FILTER_REMOVE;
end;
{$endif}
end;
{-------------------------------------------------------------------------------
function GTKRealizeCB
Params: Widget: PGtkWidget; Data: Pointer
Result: GBoolean
GTKRealizeCB is called by the gtk, whenever a widget is realized (ie mapped),
but before the widget itself gets the realize signal.
That means that the gdk window on the xserver has been created.
-------------------------------------------------------------------------------}
function gtkRealizeCB(Widget: PGtkWidget; Data: Pointer): GBoolean; cdecl;
var
decor,Func : Longint;
TheWindow: PGdkWindow;
TheForm: TCustomForm;
begin
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('realize', nil);
{$ENDIF}
if (Data<>nil) then begin
if TObject(Data) is TCustomForm then begin
TheForm:=TCustomForm(Data);
if TheForm.Parent=nil then begin
TheWindow:=gtk_widget_get_toplevel(Widget)^.window;
//apart from none and sizeable, this will
//only work if WM supports motif flags
//properly, which very few actually do.
Decor := GetWindowDecorations(TheForm);
Func := GetWindowFunction(TheForm);
gdk_window_set_decorations(TheWindow, decor);
gdk_window_set_functions(TheWindow, func);
{$ifdef windows} // for drag/dock
gdk_window_add_filter(TheWindow, @FilterFuc, TheForm)
{$endif}
end;
end;
if (csDesigning in TComponent(Data).ComponentState) then begin
//DebugLn(['gtkRealizeCB ',dbgsName(TComponent(Data)),' ',GetWidgetDebugReport(Widget)]);
end else begin
RealizeAccelerator(TComponent(Data),Widget);
end;
end;
end;
{-------------------------------------------------------------------------------
function GTKRealizeAfterCB
Params: Widget: PGtkWidget; Data: Pointer
Result: GBoolean
GTKRealizeAfterCB is called by the gtk, whenever a widget is realized
(ie mapped), and after the widget itself got the realize signal.
That means that the gdk window on the xserver has been created and the widget
initialized the gdkwindow. This function is used for the second part of
the initialization of a widget.
-------------------------------------------------------------------------------}
function gtkRealizeAfterCB(Widget: PGtkWidget; Data: Pointer): GBoolean; cdecl;
var
MainWidget, ClientWidget: PGtkWidget;
WinWidgetInfo: PWidgetInfo;
GdkWidget, GdkClient: PGDKWindow;
NewEventMask: TGdkEventMask;
LCLObject: TObject;
TheWinControl: TWinControl;
begin
Result:=CallBackDefaultReturn;
if Data=nil then ;
{$IFDEF EventTrace}
EventTrace('realizeafter', nil);
{$ENDIF}
MainWidget:=GetMainWidget(Widget);
Assert(Assigned(MainWidget), 'gtkRealizeAfterCB: MainWidget not assigned.');
WinWidgetInfo:=g_object_get_data(PGObject(MainWidget), 'widgetinfo');
Assert(Assigned(WinWidgetInfo), 'gtkRealizeAfterCB: WinWidgetInfo not assigned.');
LCLObject:=GetLCLObject(MainWidget);
if LCLObject=nil then exit;
if (LCLObject is TWinControl) then
TheWinControl:=TWinControl(LCLObject)
else
TheWinControl:=nil;
// set extra signal masks after the widget window is created
// define extra events we're interrested in
//if TheWinControl<>nil then DbgOut(' ',TheWinControl.Name,':',TheWinControl.ClassName,' ',DbgS(TheWinControl.Handle));
//DebugLn(' Widget=',DbgS(Widget),' Fixed=',DbgS(GetFixedWidget(Widget)),' Main=',DbgS(GetMainWidget(Widget)));
if TheWinControl<>nil then
begin
//DebugLn(['gtkRealizeAfterCB ',GetWidgetDebugReport(Widget)]);
{$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
GdkWidget:=GetControlWindow(Widget);
NewEventMask:=gdk_window_get_events(GdkWidget) or WinWidgetInfo^.EventMask;
gtk_widget_add_events(Widget,NewEventMask);
gdk_window_set_events(GdkWidget,NewEventMask);
ClientWidget:=GetFixedWidget(Widget);
GdkClient:=GetControlWindow(ClientWidget);
if (ClientWidget<>nil) and (GdkClient<>nil) and (GdkClient<>GdkWidget) then
begin
//DebugLn(['gtkRealizeAfterCB ClientWindow<>Window']);
NewEventMask:=gdk_window_get_events(GdkClient) or WinWidgetInfo^.EventMask;
gtk_widget_add_events(ClientWidget,WinWidgetInfo^.EventMask);
gdk_window_set_events(GdkClient,NewEventMask);
end;
//DebugLn('BBB1 ',DbgS(NewEventMask),8),' ',DbgS(Cardinal(gdk_window_get_events(Widget^.Window)));
{$IFDEF DebugGDK}EndGDKErrorTrap;{$ENDIF}
TheWinControl.CNPreferredSizeChanged;
TGtkPrivateWidgetClass(TheWinControl.WidgetSetClass.WSPrivate).UpdateCursor(WinWidgetInfo);
ConnectInternalWidgetsSignals(MainWidget,TheWinControl);
if (TheWinControl is TCustomPage) and not (TheWinControl.Parent is TTabControl) then
UpdateNotebookPageTab(nil,TheWinControl);
if TheWinControl is TCustomForm then
SetFormShowInTaskbar(TCustomForm(TheWinControl),
TCustomForm(TheWinControl).ShowInTaskbar);
end;
end;
function gtkshowCB( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMShowWindow;
begin
Result := True;
{$IFDEF EventTrace}
EventTrace('show', data);
{$ENDIF}
FillChar(Mess{%H-},SizeOf(Mess),0);
Mess.Msg := LM_SHOWWINDOW;
Mess.Show := True;
Result := DeliverMessage(Data, Mess) = 0;
end;
function gtkHideCB( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMShowWindow;
begin
Result := True;
{$IFDEF EventTrace}
EventTrace('hide', data);
{$ENDIF}
FillChar(Mess{%H-},SizeOf(Mess),0);
Mess.Msg := LM_SHOWWINDOW;
Mess.Show := False;
Result := DeliverMessage(Data, Mess) = 0;
end;
function gtkactivateCB(widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess: TLMActivate;
begin
Result:= True;
{$IFDEF EventTrace}
EventTrace('activate', data);
{$ENDIF}
ResetDefaultIMContext;
if LockOnChange(PgtkObject(Widget),0) > 0 then Exit;
FillChar(Mess{%H-}, SizeOf(Mess), #0);
{$IFDEF VerboseIDEDisplayState}
debugln(['gtkactivateCB ',DbgSName(TObject(Data))]);
{$ENDIF}
Mess.Msg := LM_ACTIVATE;
Mess.Active := WA_ACTIVE;
Mess.Minimized := False;
if GtkWidgetIsA(Widget, gtk_window_get_type) then
Mess.ActiveWindow := HWnd({%H-}PtrUInt(PGTKWindow(Widget)^.focus_widget))
else
Mess.ActiveWindow := 0;
Mess.Result := 0;
//DebugLn('gtkactivateCB ',DbgSName(TObject(Data)));
DeliverMessage(Data, Mess);
Result := CallBackDefaultReturn;
end;
function GTKCheckMenuToggeledCB(AMenuItem: PGTKCheckMenuItem; AData: gPointer): GBoolean; cdecl;
// AData --> LCLMenuItem
var
LCLMenuItem: TMenuItem;
begin
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('toggled', AData);
{$ENDIF}
LCLMenuItem := TMenuItem(AData);
// some sanity checks
if LCLMenuItem = nil then Exit;
if not LCLMenuItem.IsCheckItem then Exit; // ???
// the gtk always toggles the check flag
// -> restore 'checked' flag if needed
if gtk_check_menu_item_get_active(AMenuItem) = LCLMenuItem.Checked then Exit;
if LCLMenuItem.AutoCheck then Exit;
// restore it
LockOnChange(PgtkObject(AMenuItem), +1);
gtk_check_menu_item_set_active(AMenuItem, LCLMenuItem.Checked);
LockOnChange(PgtkObject(AMenuItem), -1);
end;
function gtkchangedCB( widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
Result := CallBackDefaultReturn;
if ComponentIsDestroyingHandle(TWinControl(Data))
or (LockOnChange(PgtkObject(Widget),0)>0) then exit;
{$IFDEF EventTrace}
EventTrace('changed', data);
{$ENDIF}
FillChar(Mess{%H-}, SizeOf(Mess), 0);
Mess.Msg := LM_CHANGED;
DeliverMessage(Data, Mess);
end;
function GtkEntryDelayCursorPos(AGtkWidget: Pointer): GBoolean; cdecl;
var
Info: PWidgetInfo;
begin
Result := AGtkWidget <> nil;
if Result then
begin
g_idle_remove_by_data(AGtkWidget);
Info := GetWidgetInfo(AGtkWidget);
if Info <> nil then
gtkchanged_editbox(PGtkWidget(AGtkWidget),
Info^.LCLObject);
end;
end;
function postpone_changed_signal(Widget: PGtkWidget): GBoolean; cdecl;
begin
Result := gtk_false;
g_object_set_data(PGObject(Widget), 'lcl-postpone-changed-signal', nil);
gtkchanged_editbox(Widget, GetWidgetInfo(Widget)^.LCLObject);
end;
procedure gtkchanged_editbox_delete_text(Widget: PGtkWidget; AStartPos,
AEndPos: gint; data: gPointer); cdecl;
var
id: gPointer;
begin
// delete key pressed, but nothing is deleted (eg. cursor at the end or empty text)
if AStartPos = AEndPos then
begin
g_signal_stop_emission_by_name(Widget, 'delete-text');
Exit;
end;
id := g_object_get_data(PGObject(Widget), 'lcl-postpone-changed-signal');
if id = nil then
begin
{%H-}PtrUInt(id) := g_idle_add(TGtkFunction(@postpone_changed_signal), Widget);
g_object_set_data(PGObject(Widget), 'lcl-postpone-changed-signal', id);
end;
end;
procedure gtkchanged_editbox_insert_text(Widget: PGtkWidget; ANewText: PgChar;
ANewTextLength: gint; APosition: pgint; data: gPointer); cdecl;
var
id: gPointer;
EntryText: PgChar;
LCLObject: TObject;
begin
EntryText := gtk_entry_get_text(PGtkEntry(Widget));
LCLObject := TObject(data);
if (widget <> nil) and (LCLObject is TCustomEdit) then
{ NumbersOnly: stop signal if inserted text is not a number }
if TCustomEdit(LCLObject).NumbersOnly and not IsNumber(ANewText) then
begin
gtk_entry_set_text(PGtkEntry(widget), EntryText);
g_signal_stop_emission_by_name(Widget, 'insert-text');
end;
id := g_object_get_data(PGObject(Widget), 'lcl-postpone-changed-signal');
if id <> nil then
begin
g_source_remove({%H-}PtrUInt(id));
g_object_set_data(PGObject(Widget), 'lcl-postpone-changed-signal', nil);
end;
if LockOnChange(PgtkObject(Widget), 0) > 0 then
g_object_set_data(PGObject(Widget), 'lcl-lock-changed-signal', gPointer(-1))
else
g_object_set_data(PGObject(Widget), 'lcl-lock-changed-signal', nil);
end;
function gtkchanged_editbox( widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
GStart, GEnd: gint;
EntryText: PgChar;
begin
//debugln('gtkchanged_editbox');
Result := CallBackDefaultReturn;
if g_object_get_data(PGObject(Widget), 'lcl-lock-changed-signal') <> nil then
begin
g_object_set_data(PGObject(Widget), 'lcl-lock-changed-signal', nil);
Exit;
end;
if g_object_get_data(PGObject(Widget), 'lcl-postpone-changed-signal') <> nil then
Exit;
if LockOnChange(PgtkObject(Widget),0)>0 then exit;
{$IFDEF EventTrace}
EventTrace('changed_editbox', data);
{$ENDIF}
if GTK_IS_ENTRY(Widget) then
begin
{cheat GtkEditable to update cursor pos in gtkEntry. issue #7243}
gtk_editable_get_selection_bounds(PGtkEditable(Widget), @GStart, @GEnd);
EntryText := gtk_entry_get_text(PGtkEntry(Widget));
if (GStart = GEnd) and
(UTF8Length(EntryText) >= PGtkEntry(Widget)^.text_length) then
begin
if g_object_get_data(PGObject(Widget),'lcl-gtkentry-pasted-data') <> nil then
begin
g_object_set_data(PGObject(Widget),'lcl-gtkentry-pasted-data',nil);
gtk_editable_set_position(PGtkEditable(Widget), GStart);
end else
begin
g_object_set_data(PGObject(Widget),'lcl-gtkentry-pasted-data',Widget);
g_idle_add(@GtkEntryDelayCursorPos, Widget);
exit;
end;
end;
end;
FillByte(Mess{%H-},SizeOf(Mess),0);
Mess.Msg := CM_TEXTCHANGED;
//debugln('gtkchanged_editbox B: DeliverMessage(CM_TextChanged)');
DeliverMessage(Data, Mess);
end;
function gtkchanged_editbox_delete(widget: PGtkWidget;
AType: TGtkDeleteType; APos: gint; data: gPointer): GBoolean; cdecl;
begin
Result := CallBackDefaultReturn;
end;
function gtkpaste_editbox(Widget: PGtkWidget; data: gPointer): GBoolean; cdecl;
var
FClipText: string;
FClip: gtk2.PGtkClipboard;
LCLObject: TObject;
begin
Result := CallBackDefaultReturn;
LCLObject := TObject(data);
if (widget <> nil) and (LCLObject is TCustomEdit) then
{ NumbersOnly }
if TCustomEdit(LCLObject).NumbersOnly then
begin
{ get clipboard text and stop signal if pasted text is not a number }
FClipText := '';
FClip := gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
if (FClip <> nil) and gtk_clipboard_wait_is_text_available(FClip) then
FClipText := gtk_clipboard_wait_for_text(FClip);
if Assigned(FClip) and not IsNumber(FClipText) then
g_signal_stop_emission_by_name(Widget, 'paste-clipboard');
end;
end;
function gtkdaychanged(Widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
MSG: TLMessage;
begin
Result := CallBackDefaultReturn;
if LockOnChange(PgtkObject(Widget),0)>0 then exit;
EventTrace('day changed', data);
MSG.Msg := LM_DAYCHANGED;
DeliverPostMessage(Data, MSG);
Result := CallBackDefaultReturn;
end;
function gtktoggledCB( widget: PGtkWidget; data: gPointer): GBoolean; cdecl;
var
Mess : TLMessage;
procedure ChangeCheckbox(AGrayed,AChecked: boolean);
begin
LockOnChange(PgtkObject(Widget),1);
gtk_toggle_button_set_Inconsistent(PGtkToggleButton(Widget), AGrayed);
gtk_toggle_button_set_active(PGtkToggleButton(Widget), AChecked);
LockOnChange(PgtkObject(Widget),-1);
end;
begin
//DebugLn('gtktoggledCB ',DbgSName(TObject(Data)));
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('toggled', data);
{$ENDIF}
if LockOnChange(PgtkObject(Widget),0) > 0 then Exit;
if GtkWidgetIsA(Widget,gtk_toggle_button_get_type) then begin
if TObject(Data) is TCustomCheckbox then begin
if gtk_toggle_button_get_inconsistent(PGtkToggleButton(Widget)) then
ChangeCheckbox(false, true)
else
if TCustomCheckbox(Data).AllowGrayed and
gtk_toggle_button_get_active(PGtkToggleButton(Widget)) then
ChangeCheckbox(true, false);
end;
end;
Mess.Msg := LM_CHANGED;
Mess.Result := 0;
DeliverMessage(Data, Mess);
//DebugLn('gtktoggledCB END ',DbgSName(TObject(Data)));
end;
function gtkExposeEvent(Widget: PGtkWidget; Event : PGDKEventExpose;
Data: gPointer): GBoolean; cdecl;
var
DesignOnlySignal: boolean;
begin
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('ExposeAfter', data);
{$ENDIF}
//DebugLn(['gtkExposeEvent ',GetWidgetDebugReport(Widget),' Event^.Count=',Event^.Count]);
if (Event^.Count > 0) then exit;
if not (csDesigning in TComponent(Data).ComponentState) then begin
DesignOnlySignal:=GetDesignOnlySignalFlag(Widget,dstExposeAfter);
if DesignOnlySignal then exit;
end else begin
{$IFDEF VerboseDesignerDraw}
DebugLn('gtkExpose',
' Widget=',DbgS(Widget),'=',GetWidgetClassName(Widget),
' ',TComponent(Data).Name,
' ',dbgs(Event^.area.x),',',dbgs(Event^.area.y),',',dbgs(Event^.area.width),',',dbgs(Event^.area.height),
'');
{$ENDIF}
end;
//DebugLn(['gtkExposeEvent ',GetWidgetDebugReport(Widget),' ',dbgGRect(@Event^.Area)]);
DeliverGtkPaintMessage(Data, Widget, @Event^.Area, False, False);
end;
function gtkExposeEventAfter(Widget: PGtkWidget; Event : PGDKEventExpose;
Data: gPointer): GBoolean; cdecl;
var
DesignOnlySignal: boolean;
//children: PGList;
begin
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('ExposeAfter', data);
{$ENDIF}
//DebugLn(['gtkExposeEventAfter ',GetWidgetDebugReport(Widget),' Event^.Count=',Event^.Count]);
if (Event^.Count > 0) then exit;
if not (csDesigning in TComponent(Data).ComponentState) then begin
DesignOnlySignal:=GetDesignOnlySignalFlag(Widget,dstExposeAfter);
if DesignOnlySignal then exit;
end else begin
{$IFDEF VerboseDesignerDraw}
DebugLn('gtkExposeAfter',
' Widget=',DbgS(Widget),'=',GetWidgetClassName(Widget),
' ',TComponent(Data).Name,
' ',dbgs(Event^.area.x),',',dbgs(Event^.area.y),',',dbgs(Event^.area.width),',',dbgs(Event^.area.height),
'');
{$ENDIF}
end;
//DebugLn(['gtkExposeEventAfter ',GetWidgetDebugReport(Widget),' ',dbgGRect(@Event^.Area)]);
DeliverGtkPaintMessage(Data,Widget,@Event^.Area,false,true);
end;
procedure ClearTopLevelsFocus(Win: PGtkWidget);
var
List, orgList: PGList;
Window: PGtkWindow;
event: PGdkEvent;
TopLevelWin: PGdkWindow;
begin
if not GTK_IS_WINDOW(win) then
exit;
List := gdk_window_get_toplevels;
orgList:=List;
while List <> nil do
begin
TopLevelWin := PGdkWindow(List^.Data);
if (TopLevelWin<>nil) and (TopLevelWin<>Win^.Window) then
begin
gdk_window_get_user_data(TopLevelWin, Pgpointer(@Window));
if GTK_IS_WINDOW(Window)
and GTK_WIDGET_VISIBLE(Window)
and gtk_window_has_TopLevel_Focus(Window) then
begin
// fake focus-out event
{$IFDEF VerboseFocus}
DebugLn('NOTE: Window with stalled focus found!, faking focus-out event');
{$ENDIF}
event := gdk_event_new(GDK_FOCUS_CHANGE);
event^.focus_change.window := TopLevelWin;
event^.focus_change._type := GDK_FOCUS_CHANGE;
event^.focus_change._in:=0;
gtk_main_do_event(event);
end;
end;
list := g_list_next(list);
end;
if Assigned(orgList) then
g_list_free(orgList);
end;
function gtkfrmactivateAfter({%H-}widget: PGtkWidget; {%H-}Event : PgdkEventFocus;
data: gPointer) : GBoolean; cdecl;
var
Mess: TLMActivate;
Info: PWidgetInfo;
{$IFDEF VerboseFocus}
LCLObject: TObject;
CurFocusWidget: PGtkWidget;
{$ENDIF}
begin
{$IFDEF EventTrace}
EventTrace('activate after', data);
{$ENDIF}
ResetDefaultIMContext;
FillChar(Mess{%H-},SizeOf(Mess),#0);
{$IFDEF VerboseFocus}
LCLObject:=TObject(data);
DebugLnEnter(['gtkfrmActivateAfter INIT Widget=',DbgS(Widget),' Event^.theIn=',Event^._in,
' LCLObject=',dbgsname(LCLObject)]);
CurFocusWidget:=PGtkWidget(GetFocus);
if CurFocusWidget<>nil then begin
LCLObject:=GetNearestLCLObject(CurFocusWidget);
DebugLn(' GetFocus=',DbgS(CurFocusWidget),' ParentLCLObject=',dbgsName(LCLObject));
end;
{$ENDIF}
Info := GetWidgetInfo(Widget);
try
if (Info <> nil) then
Include(Info^.Flags, wwiActivating);
{$IFDEF VerboseIDEDisplayState}
debugln(['gtkfrmactivateAfter ',DbgSName(TObject(Data))]);
{$ENDIF}
Mess.Msg := LM_ACTIVATE;
Mess.Active := WA_ACTIVE;
Mess.Minimized := False;
if GtkWidgetIsA(Widget, gtk_window_get_type) then
Mess.ActiveWindow := HWnd({%H-}PtrUInt(PGTKWindow(Widget)^.focus_widget))
else
Mess.ActiveWindow := 0;
Mess.Result := 0;
DeliverMessage(Data, Mess); // send message directly (not Post)
finally
if Info <> nil then
Exclude(Info^.Flags, wwiActivating);
end;
Result := CallBackDefaultReturn;
{$IFDEF VerboseFocus}
DebugLnExit('gtkfrmActivateAfter DONE');
{$ENDIF}
end;
function gtkfrmdeactivateAfter( {%H-}widget: PGtkWidget; {%H-}Event : PgdkEventFocus;
data: gPointer) : GBoolean; cdecl;
var
Mess: TLMActivate;
Info: PWidgetInfo;
{$IFDEF VerboseFocus}
LCLObject: TControl;
{$ENDIF}
begin
{$IFDEF EventTrace}
EventTrace('deactivate after', data);
{$ENDIF}
{$IFDEF VerboseFocus}
LCLObject := TControl(GetLCLObject(Widget));
DebugLnEnter(['gtkfrmDeactivate INIT Widget=',DbgS(Widget),' ',Event^._in,
' GetFocus=',DbgS(Widget),' LCLObject=', dbgsName(LCLObject)]);
{$ENDIF}
ResetDefaultIMContext;
Info := GetWidgetInfo(Widget);
try
if (Info<>nil) then
Include(Info^.Flags, wwiDeactivating);
FillChar(Mess{%H-}, SizeOf(Mess), #0);
{$IFDEF VerboseIDEDisplayState}
debugln(['gtkfrmdeactivateAfter ',DbgSName(TObject(Data))]);
{$ENDIF}
Mess.Msg := LM_ACTIVATE;
Mess.Active := WA_INACTIVE;
Mess.Minimized := False;
Mess.ActiveWindow := 0;
Mess.Result := 0;
DeliverMessage(Data, Mess);
finally
if Info<>nil then
Exclude(Info^.Flags, wwiDeactivating);
end;
Result := CallBackDefaultReturn;
{$IFDEF VerboseFocus}
DebugLnExit('gtkfrmDeactivate DONE');
{$ENDIF}
end;
function GTKKeyPress(Widget: PGtkWidget; Event: pgdkeventkey; Data: gPointer
): GBoolean; cdecl;
begin
Result := HandleGtkKeyUpDown(Widget,Event,Data,true,True,'key-press-event');
end;
function GTKKeyPressAfter(Widget: PGtkWidget; Event: pgdkeventkey; Data: gPointer): GBoolean; cdecl;
begin
Result := HandleGtkKeyUpDown(Widget,Event,Data,false,True,'key-press-event');
end;
function GTKKeyRelease(Widget: PGtkWidget; Event: pgdkeventkey; Data: gPointer
): GBoolean; cdecl;
begin
Result := HandleGtkKeyUpDown(Widget,Event,Data,true,False,'key-release-event');
end;
function GTKKeyReleaseAfter(Widget: PGtkWidget; Event: pgdkeventkey; Data: gPointer): GBoolean; cdecl;
begin
Result := HandleGtkKeyUpDown(Widget,Event,Data,false,False,'key-release-event');
end;
var
NeedShiftUpdateAfterFocus: Boolean;
procedure UpdateShiftState(const KeyStateList: TFPList; const ShiftState: TShiftState);
procedure UpdateList(const AVKeyCode: Integer; const APressed: Boolean);
begin
if AVKeyCode = 0 then Exit;
if APressed
then begin
if KeyStateList.IndexOf({%H-}Pointer(PtrUInt(AVKeyCode))) < 0
then KeyStateList.Add({%H-}Pointer(PtrUInt(AVKeyCode)));
end
else begin
KeyStateList.Remove({%H-}Pointer(PtrUInt(AVKeyCode)));
end;
end;
const
STATE_MAP: array[0..3] of TShiftStateEnum = (
ssShift,
ssCtrl,
ssAlt,
ssSuper
);
VK_MAP: array[0..3] of array[0..2] of Byte = (
// (Main key, alt key 1, alt key 2) to check
(VK_SHIFT, VK_LSHIFT, VK_RSHIFT),
(VK_CONTROL, VK_LCONTROL, VK_RCONTROL),
(VK_MENU, VK_LMENU, VK_RMENU),
(VK_LWIN, VK_RWIN, 0)
);
var
n: Integer;
InState: Boolean;
begin
for n := 0 to High(STATE_MAP) do
begin
InState := STATE_MAP[n] in ShiftState;
UpdateList(VK_MAP[n][0], InState);
UpdateList(VK_MAP[n][1], InState);
UpdateList(VK_MAP[n][2], InState);
end;
end;
function GTKFocusCB(widget: PGtkWidget; {%H-}event: PGdkEventFocus; data: gPointer): GBoolean; cdecl;
var
Mess : TLMessage;
LCLObject: TObject;
AForm: TCustomForm;
{$IFDEF VerboseFocus}
CurFocusWidget: PGtkWidget;
{$ENDIF}
Mask: TGdkModifierType;
AInfo: PWidgetInfo;
begin
{$IFDEF EventTrace}
EventTrace('focus', data);
{$ENDIF}
//DebugLn('GTKFocusCB ',DbgSName(TObject(Data)),' ',GetWidgetDebugReport(Widget));
LCLObject:=TObject(data);
if (widget<>nil) and GTK_IS_WINDOW(widget) and (LCLObject is TCustomForm) then
begin
// it's a top level window about to be focused, if we are being focused
// because of ALT+TABing to this form, the origin form might be left with
// invalid focus information (ALT+TAB doesn't trigger focus-out event on those)
ClearTopLevelsFocus(Widget);
end;
NeedShiftUpdateAfterFocus := False;
gdk_window_get_pointer(nil, nil, nil, @Mask);
UpdateShiftState(GTK2WidgetSet.KeyStateList, GTKEventStateToShiftState(Word(Mask)));
{$IFDEF VerboseFocus}
DebugLnEnter(['GTKFocusCB INIT Widget=',DbgS(Widget),' Event^.theIn=',Event^._in,
' LCLObject=',dbgsName(LCLObject)]);
CurFocusWidget:=PGtkWidget(GetFocus);
if CurFocusWidget<>nil then begin
LCLObject:=GetNearestLCLObject(CurFocusWidget); // NOTE: overrides argument data !!!
DebugLn('GetFocus=',DbgS(CurFocusWidget), ' ParentLCLFocus=',DbgsName(LCLObject));
{$IFDEF VerboseSizeMsg}
DebugLn(['GTKWindowStateEventCB ',DbgSName(TheForm),
' GTK=',GtkWidth,'x',GtkHeight,
' LCL=',TheForm.Width,'x',TheForm.Height,
' SizeType=',SizeMsg.SizeType-Size_SourceIsInterface,'+Size_SourceIsInterface'
]);
{$ENDIF}
LCLObject := TObject(Data); // restore original LCLObject
end;
{$ENDIF}
if LCLObject is TCustomForm then begin
// a form became active
// it does not mean: the control focus is switch to the form
AForm:=TCustomForm(LCLObject);
//debugln(['GTKFocusCBAfter ',DbgSName(AForm),' ',DbgSName(AForm.ActiveControl)]);
// the PGtkWindow(Data)^.focus_widget shows the last focus call for this
// window. If there is a sub window (e.g. docking), then the focus_widget
// of the parent window was not updated, so it can not be used.
// The gtk will use, if we let it, which will not follow the LCL focus logic.
// Follow the LCL logic:
if AForm.ActiveControl<>nil then begin
Data:=AForm.ActiveControl;
//debugln(['GTKFocusCBAfter ',DbgSName(LCLObject),' send=',event^.send_event,' window=',dbgs(event^.window),' in=',event^._in,' type=',event^._type,' Data=',dbgsname(TObject(Data))]);
end;
end;
// we do not show selection (gtk behaviour) when widget is unfocused,
// so revert back CursorPos.issues #18164,#21897,#23182
if GtkWidgetIsA(Widget, gtk_type_entry) then
begin
AInfo := GetWidgetInfo(Widget);
if AInfo <> nil then
begin
if (AInfo^.LCLObject is TCustomEdit)
and ((AInfo^.CursorPos > 0) or (AInfo^.SelLength > 0)) then
begin
// gtk_entry_set_position(PGtkEntry(Widget), AInfo^.CursorPos);
// gtk_editable_select_region(PGtkEditable(Widget), AInfo^.CursorPos, AInfo^.CursorPos);
// do not trigger signals, only update pos for lcl
PGtkEntry(Widget)^.current_pos := AInfo^.CursorPos;
PGtkEntry(Widget)^.selection_bound := AInfo^.CursorPos + AInfo^.SelLength;
end;
end;
end;
ResetDefaultIMContext;
//TODO: fill in old focus
FillChar(Mess{%H-}, SizeOf(Mess), 0);
Mess.msg := LM_SETFOCUS;
DeliverMessage(Data, Mess);
Result := CallBackDefaultReturn;
{$IFDEF VerboseFocus}
DebugLnExit('GTKFocusCB DONE');
{$ENDIF}
end;
function GTKKillFocusCB({%H-}widget: PGtkWidget; {%H-}event:PGdkEventFocus;
data: gPointer) : GBoolean; cdecl;
{$IFDEF VerboseFocus}
var
LCLObject: TObject;
CurFocusWidget: PGtkWidget;
{$ENDIF}
begin
{$IFDEF EventTrace}
EventTrace('killfocusCB', data);
{$ENDIF}
//DebugLn('GTKKillFocusCB ',DbgSName(TObject(Data)),' ',GetWidgetDebugReport(Widget));
{$IFDEF VerboseFocus}
NeedShiftUpdateAfternFocus := True; // <- JRA: this doesn't look like simply log !!!
LCLObject:=TObject(data);
DebugLn(['GTKillFocusCB Widget=',DbgS(Widget),' Event^.theIn=',Event^._in,
' LCLObject=',dbgsName(LCLobject)]);
CurFocusWidget:=PGtkWidget(GetFocus);
if CurFocusWidget<>nil then begin
LCLObject:=GetNearestLCLObject(CurFocusWidget);
DebugLn('GTKillFocusCB GetFocus=',dbgs(CurFocusWidget),' ParentLCLObject=',dbgsName(LCLObject));
end;
{$ENDIF}
Result:=CallBackDefaultReturn;
end;
function GtkEntryDelayClearCursorPos(AGtkWidget: Pointer): GBoolean; cdecl;
var
Info: PWidgetInfo;
p: Pgchar;
AStart,AEnd: gint;
begin
Result := (AGtkWidget <> nil) and (GTK_IS_WIDGET(AGtkWidget));
if Result then
begin
g_idle_remove_by_data(AGtkWidget);
Info := GetWidgetInfo(AGtkWidget);
if Info <> nil then
begin
gtk_editable_get_selection_bounds(PGtkEditable(AGtkWidget),@AStart, @AEnd);
p := gtk_editable_get_chars(PGtkEditable(AGtkWidget), AStart, AEnd);
if (AStart <> AEnd) then
gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), p, -1);
gtk_editable_select_region(PGtkEditable(AGtkWidget), 0, 0);
g_free(p);
end;
end;
end;
function GTKKillFocusCBAfter({%H-}widget: PGtkWidget; {%H-}event:PGdkEventFocus;
data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
Info: PWidgetInfo;
AStart,AEnd: gint;
AForm: TCustomForm;
{$IFDEF VerboseFocus}
LCLObject: TObject;
CurFocusWidget: PGtkWidget;
{$ENDIF}
begin
{$IFDEF EventTrace}
EventTrace('killfocusCBAfter', data);
{$ENDIF}
//DebugLn('GTKKillFocusCBAfter ',DbgSName(TObject(Data)),' ',GetWidgetDebugReport(Widget));
{$IFDEF VerboseFocus}
NeedShiftUpdateAfternFocus := True; // <- JRA: this doesn't look like simply log !!!
LCLObject:=TObject(data);
DebugLnEnter(['GTKillFocusCBAfter INIT Widget=',DbgS(Widget),' Event^.theIn=',Event^._in,
' LCLObject=',dbgsName(LCLObject)]);
CurFocusWidget:=PGtkWidget(GetFocus);
if CurFocusWidget<>nil then begin
LCLObject:=GetNearestLCLObject(CurFocusWidget);
DebugLn('GetFocus=',DbgS(CurFocusWidget),' ParentLCLFocus=', dbgsName(LCLObject));
end;
{$ENDIF}
ResetDefaultIMContext;
// do not send LM_KILLFOCUS message if widget is destroying itself
if Assigned(Data) and
not ((csDestroying in TComponent(Data).ComponentState) or
(csDestroyingHandle in TControl(Data).ControlState)) then
begin
FillChar(Mess{%H-},SizeOf(Mess),0);
Mess.msg := LM_KILLFOCUS;
// do not release the capture widget here
//TODO: fill in new focus
//DebugLn(Format('Trace:TODO: [gtkkillfocusCB] %s finish', [TObject(Data).ClassName]));
DeliverMessage(Data, Mess);
end;
// do not show selection when widget is unfocused
// issues #18164,#21897,#23182
if GtkWidgetIsA(Widget, gtk_type_entry) and Assigned(Data) then
begin
if (csDestroying in TComponent(Data).ComponentState) or
(csDestroyingHandle in TControl(Data).ControlState) then
begin
// DebugLn('NOTICE: Control is destroying itself, do not add new idle timer for ',dbgsName(TControl(Data)));
exit;
end;
AForm := GetParentForm(TControl(Data));
if Assigned(AForm) and ((csDestroying in AForm.ComponentState) or
(csDestroyingHandle in AForm.ControlState) or
((fsModal in AForm.FormState) and (AForm.ModalResult <> mrNone)) ) then
begin
// DebugLn('NOTICE: Parent form is destroying, do not add new idle timer for ',dbgsName(TControl(Data)));
exit;
end;
g_idle_add(@GtkEntryDelayClearCursorPos, Widget);
//save now CursorPos and SelStart in WidgetInfo
if (Widget <> nil) then
begin
Info := GetWidgetInfo(Widget);
if Info <> nil then
begin
if (Info^.LCLObject is TCustomEdit) then
begin
gtk_editable_get_selection_bounds(PGtkEditable(Widget),@AStart, @AEnd);
Info^.CursorPos := Min(AStart, AEnd);
Info^.SelLength := Abs(AEnd - AStart);
end;
end;
end;
end;
Result:=true;
{$IFDEF VerboseFocus}
DebugLnExit('GTKillFocusCBAfter DONE');
{$ENDIF}
end;
function GTKWindowStateEventCB(widget: PGtkWidget;
state: PGdkEventWindowState; data: gpointer): gboolean; cdecl;
var
TheForm: TCustomForm;
SizeMsg: TLMSize;
GtkWidth: LongInt;
GtkHeight: LongInt;
clientRectFix: TRect;
{$IFDEF HasX}
NetAtom: TGdkAtom;
AtomType: TGdkAtom;
AIndex, ADesktop: Pguchar;
AFormat: gint;
ALength: gint;
{$ENDIF}
begin
Result := CallBackDefaultReturn;
// if iconified in changed then OnIconify...
if GTK_WIDGET_REALIZED(Widget) then
begin
if (GDK_WINDOW_STATE_WITHDRAWN and state^.changed_mask <> 0)
or (GDK_WINDOW_STATE_WITHDRAWN and state^.new_window_state <> 0) then
begin
// visibility changed - this is another message block
Exit;
end;
if TObject(Data) is TCustomForm then
begin
TheForm := TCustomForm(Data);
//DebugLn(['GTKWindowStateEventCB ',DbgSName(TheForm),' new_window_state=',state^.new_window_state,' changed_mask=',state^.changed_mask]);
if TheForm.Parent = nil then begin
// toplevel window
// send a WMSize Message (see TCustomForm.WMSize)
// ToDo: this might be too early to use the Widget^.Allocation
// Either send this message later or find a better way to determine the size (including the client area)
GtkWidth:=Widget^.Allocation.Width;
if GtkWidth<0 then GtkWidth:=0;
GtkHeight:=Widget^.Allocation.Height;
if GtkHeight<0 then GtkHeight:=0;
clientRectFix:= GetWidgetInfo(Widget)^.FormClientRectFix;
inc( GtkWidth, clientRectFix.Width );
inc( GtkHeight, clientRectFix.Height );
//debugln('GTKWindowStateEventCB ',DbgSName(TObject(Data)),' ',dbgs(state^.new_window_state),' ',WidgetFlagsToString(Widget));
if ((GDK_WINDOW_STATE_ICONIFIED and state^.new_window_state)>0) then
begin
{$IFDEF HasX}
NetAtom := gdk_atom_intern('_NET_WM_DESKTOP', True);
if NetAtom > 0 then begin
if gdk_property_get(Widget^.window, NetAtom, XA_CARDINAL,
0, 4, 0, @AtomType, @AFormat, @ALength, @AIndex)
then begin
NetAtom := gdk_atom_intern('_NET_CURRENT_DESKTOP', True);
if gdk_property_get(gdk_get_default_root_window, NetAtom, XA_CARDINAL,0, 4, 0, @AtomType, @AFormat, @ALength, @ADesktop)
then if ADesktop^ <> AIndex^ then begin
// form is not on active desktop => ignore
g_free(ADesktop);
g_free(AIndex);
exit;
end
else begin
g_free(ADesktop);
g_free(AIndex);
end;
end;
end;
{$ENDIF}
SizeMsg.SizeType := SIZE_MINIMIZED;
end
else if (GDK_WINDOW_STATE_MAXIMIZED and state^.new_window_state)>0 then
begin
// it can be both maximized + iconified and just loose iconified state
if (state^.changed_mask and (GDK_WINDOW_STATE_MAXIMIZED or GDK_WINDOW_STATE_ICONIFIED)) = 0 then Exit;
SizeMsg.SizeType := SIZE_MAXIMIZED;
end
else
SizeMsg.SizeType := SIZE_RESTORED;
// don't bother the LCL if nothing changed
case SizeMsg.SizeType of
SIZE_RESTORED: if TheForm.WindowState=wsNormal then exit;
SIZE_MINIMIZED: if TheForm.WindowState=wsMinimized then exit;
SIZE_MAXIMIZED: if TheForm.WindowState=wsMaximized then exit;
SIZE_FULLSCREEN: if TheForm.WindowState=wsFullScreen then exit;
end;
with SizeMsg do
begin
Result := 0;
Msg := LM_SIZE;
SizeType := SizeType+Size_SourceIsInterface;
Width := SmallInt(GtkWidth);
Height := SmallInt(GtkHeight);
end;
{$IFDEF VerboseSizeMsg}
DebugLn(['GTKWindowStateEventCB ',DbgSName(TheForm),
' GTK=',GtkWidth,'x',GtkHeight,
' LCL=',TheForm.Width,'x',TheForm.Height,
' SizeType=',SizeMsg.SizeType-Size_SourceIsInterface,'+Size_SourceIsInterface'
]);
{$ENDIF}
DeliverMessage(TheForm, SizeMsg);
if (gtk_major_version = 2) and (gtk_minor_version <= 8) and
(TheForm.WindowState = wsMaximized) then
gtk_widget_queue_draw({%H-}PGtkWidget(TheForm.Handle));
if (TheForm = Application.MainForm)
and (GDK_WINDOW_STATE_ICONIFIED and state^.changed_mask <> 0) then
begin
if GDK_WINDOW_STATE_ICONIFIED and state^.new_window_state <> 0 then
Application.IntfAppMinimize
else
Application.IntfAppRestore;
end;
end;
end;
end;
end;
function gtkdestroyCB(widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess: TLMessage;
Info: PWidgetInfo;
begin
Result := CallBackDefaultReturn;
//DebugLn(['gtkdestroyCB ',GetWidgetDebugReport(Widget)]);
// Cancel any pending idle callbacks for the widget
while g_idle_remove_by_data(Widget) <> gFALSE do;
Info:=GetWidgetInfo(Widget);
if Info = nil then // this widget is already destroyed
Exit;
if (Data = nil) or (Info^.LCLObject <> TObject(Data)) then // this LCLObject does not use this widget anymore
Exit;
if (TObject(Data) is TWinControl) then
begin
if (not TWinControl(Data).HandleAllocated) then
begin
FreeWidgetInfo(Widget);
Exit;
end else
if ({%H-}PGtkWidget(TWinControl(Data).Handle) <> Widget) then // the TWinControl does not use this widget anymore.
Exit;
end;
{$IFDEF EventTrace}
EventTrace('destroyCB', data);
{$ENDIF}
//DebugLn('gtkdestroyCB Data="',DbgSName(TObject(Data)),'" LCLObject="',DbgSName(Info^.LCLObject),'" ',GetWidgetDebugReport(Widget));
FillChar(Mess{%H-}, SizeOf(Mess), 0);
Mess.msg := LM_DESTROY;
DeliverMessage(Data, Mess);
// NOTE: if the destroy message is posted
// we should post an info destroy message as well
FreeWidgetInfo(Widget);
end;
procedure DestroyWindowFromPointCB(Widget: PGtkWidget; data: gPointer); cdecl;
begin
if {%H-}PGtkWidget(LastWFPResult) <> Widget then Exit;
LastWFPResult := 0;
LastWFPMousePos := Point(High(Integer), High(Integer));
end;
function gtkdeleteCB( {%H-}widget : PGtkWidget; {%H-}event : PGdkEvent;
data : gPointer) : GBoolean; cdecl;
var Mess : TLMessage;
begin
FillChar(Mess{%H-},SizeOf(Mess),0);
Mess.Msg:= LM_CLOSEQUERY;
{ Message results : True - do nothing, False - destroy or hide window }
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkresizeCB( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
//var
// Mess : TLMessage;
begin
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('resize', data);
{$ENDIF}
// Mess.msg := LM_RESIZE;
// TControl(data).WindowProc(TLMessage(Mess));
//DebugLn('Trace:TODO: [gtkresizeCB] fix (or remove) to new LM_SIZE');
//TObject(data).Dispatch(Mess);
end;
function gtkMonthChanged({%H-}Widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess: TLMessage;
begin
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('month changed', data);
{$ENDIF}
FillChar(Mess{%H-},SizeOf(Mess),0);
Mess.Msg := LM_MONTHCHANGED;
DeliverPostMessage(Data, Mess);
Result := CallBackDefaultReturn;
end;
{-------------------------------------------------------------------------------
procedure DeliverMouseMoveMessage(Widget:PGTKWidget; Event: PGDKEventMotion;
AWinControl: TWinControl);
Translate a gdk mouse motion event into a LCL mouse move message and send it.
Mouse coordinate mapping:
Why mapping:
An lcl control can consists of several gtk widgets, and any message to them is
send to the lcl control. The gtk sends the coordinates relative to the
emitting gdkwindow (not relative to the gtkwidget). And the area of a lcl
control can belong to several gdkwindows. Therefore the mouse coordinates must
be mapped.
What the lcl expects:
For Delphi compatibility the mouse coordinates must be relative to the client
area of the control.
That means for example if the mouse is over the top-left pixel of the client
widget (mostly a gtkfixed widget), then 0,0 is send.
If the mouse is on the top-left pixel of the container widget then the
coordinates can be negative, if there is frame around the client area.
-------------------------------------------------------------------------------}
procedure DeliverMouseMoveMessage(Widget: PGTKWidget; Event: PGDKEventMotion;
AWinControl: TWinControl);
var
Msg: TLMMouseMove;
ShiftState: TShiftState;
MappedXY: TPoint;
begin
CheckTransparentWindow({%H-}TLCLIntfHandle(widget), AWinControl);
if (widget=nil) or (AWinControl=nil) then
Exit;
MappedXY := TranslateGdkPointToClientArea(Event^.Window,
Point(TruncToInt(Event^.X), TruncToInt(Event^.Y)),
{%H-}PGtkWidget(AWinControl.Handle));
MappedXY := SubtractScoll({%H-}PGtkWidget(AWinControl.Handle), MappedXY);
ShiftState := GTKEventStateToShiftState(Event^.State);
with Msg do
begin
Msg := LM_MouseMove;
XPos := MappedXY.X;
YPos := MappedXY.Y;
Keys := ShiftStateToKeys(ShiftState);
Result := 0;
end;
// send the message directly to the LCL
// (Posting the message via queue
// has the risk of getting out of sync with the gtk)
NotifyApplicationUserInput(AWinControl, Msg.Msg);
//DebugLn(['DeliverMouseMoveMessage ',dbgsName(AWinControl)]);
DeliverMessage(AWinControl, Msg);
// if dragmanager is started later then inform g_object issue #19914
if GTK_IS_NOTEBOOK({%H-}PGtkWidget(AWinControl.Handle)) and
DragManager.IsDragging and
(g_object_get_data({%H-}PGObject(AWinControl.Handle),'lclnotebookdragging') = nil)
then
g_object_set_data({%H-}PGObject(AWinControl.Handle),
'lclnotebookdragging', gpointer(PtrInt(1)));
end;
procedure FixListViewRubberBand(AWidget: PGtkWidget);
var
Info: PWidgetInfo;
IconView: PGtkIconView;
Priv: _PGtkIconViewPrivate;
begin
Info := GetWidgetInfo(AWidget);
IconView := PGtkIconView(Info^.CoreWidget);
Priv := IconView^.priv;
if Priv^.doing_rubberband then
begin
Priv^.doing_rubberband := False;
gtk_widget_queue_draw(AWidget);
end;
end;
{-------------------------------------------------------------------------------
function ControlGetsMouseMoveBefore(AControl: TControl): boolean;
Returns true, if mouse move event should be sent before the widget itself
reacts.
-------------------------------------------------------------------------------}
function ControlGetsMouseMoveBefore({%H-}AControl: TControl;
const ABefore: Boolean; Event: PGDKEventMotion): boolean;
var
ShiftState: TShiftState;
Widget: PGtkWidget;
MainView: PGtkWidget;
begin
Result := True;
// currently there are no controls, that need after events.
if not ABefore then exit;
// gtk2 column resizing ... issue #21354
if (Event <> nil) and not (csDesigning in AControl.ComponentState) and
(AControl is TListView) and
(TListView(AControl).ViewStyle = vsReport) and
(TListView(AControl).ShowColumnHeaders) then
begin
ShiftState := GTKEventStateToShiftState(Event^.State);
if ssLeft in ShiftState then
begin
Widget := {%H-}PGtkWidget(TWinControl(AControl).Handle);
if GTK_IS_SCROLLED_WINDOW(Widget) then
begin
MainView := gtk_bin_get_child(PGtkBin(Widget));
if GTK_IS_TREE_VIEW(MainView) then
begin
// here we are
if gtk_tree_view_get_bin_window(PGtkTreeView(MainView)) <> Event^.window then
Result := False;
//TODO: queue column resize when x < 0
// gtk_tree_view_column_queue_resize(tree_column: PGtkTreeViewColumn)
end;
end;
end;
end else
if (Event <> nil) and not (csDesigning in AControl.ComponentState) and
(AControl is TWinControl) and
(TWinControl(AControl).FCompStyle = csListView) and
(TListView(AControl).ViewStyle in [vsIcon, vsSmallIcon]) and
TListView(AControl).MultiSelect then
begin
ShiftState := GTKEventStateToShiftState(Event^.State);
// issue #22991 - this fixes rubberbanding
if [ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble] * ShiftState = [] then
FixListViewRubberBand({%H-}PGtkWidget(TWinControl(AControl).Handle));
end;
end;
procedure GTKGetDevicePointer(win: PGdkWindow; dev: PGdkDevice;
x, y: pgdouble; mask: PGdkModifierType);
var
axes: pgdouble;
i: Integer;
begin
axes := g_new(SizeOf(gdouble), dev^.num_axes);
gdk_device_get_state(dev, win, axes, mask);
for i := 0 to dev^.num_axes - 1 do
if (x^ <> 0) and (dev^.axes[i].use = GDK_AXIS_X) then
x^ := axes[i]
else if (y^ <> 0) and (dev^.axes[i].use = GDK_AXIS_Y) then
y^ := axes[i];
g_free(axes);
end;
{-------------------------------------------------------------------------------
GTKMotionNotify
Params: widget: PGTKWidget; event: PGDKEventMotion; data: gPointer
Returns: GBoolean
Called whenever the mouse is moved over a widget.
The gtk event is translated into a lcl MouseMove message.
-------------------------------------------------------------------------------}
function gtkMotionNotify(Widget:PGTKWidget; Event: PGDKEventMotion;
Data: gPointer): GBoolean; cdecl;
const
LastModifierKeys: TShiftState = [];
var
DesignOnlySignal: boolean;
ShiftState: TShiftState;
ACtl: TWinControl;
begin
Result := CallBackDefaultReturn;
if (Event^.is_hint <> 0) and (Event^._type = GDK_MOTION_NOTIFY) then
GTKGetDevicePointer(Event^.window, Event^.device,
@Event^.x, @Event^.y, PGdkModifierType(@Event^.state));
{$IFDEF VerboseMouseBugfix}
DesignOnlySignal:=GetDesignOnlySignalFlag(Widget,dstMouseMotion);
DebugLn('[GTKMotionNotify] ',
DbgSName(TControl(Data)),
' Widget=',DbgS(Widget),
' DSO=',dbgs(DesignOnlySignal),
' Event^.X=',dbgs(TruncToInt(Event^.X)),' Event^.Y=',dbgs(TruncToInt(Event^.Y))
);
{$ENDIF}
ShiftState := GTKEventStateToShiftState(Event^.State);
if (ShiftState*[ssShift, ssCtrl, ssAlt, ssSuper] <> LastModifierKeys) or NeedShiftUpdateAfterFocus
then begin
NeedShiftUpdateAfterFocus := False;
LastModifierKeys := ShiftState*[ssShift, ssCtrl, ssAlt, ssSuper];
//DebugLn(['Adjust KeyStateList in MouseMove',Integer(LastModifierKeys)]);
UpdateShiftState(GTK2WidgetSet.KeyStateList, LastModifierKeys);
end;
{$IFDEF VerboseMouseCapture}
if (MouseCaptureWidget = Widget) and ([ssLeft,ssRight,ssMiddle]*ShiftState=[]) then
DebugLn(['gtkMotionNotify gtk capture without mouse down: ',GetWidgetDebugReport(Widget)]);
{$ENDIF}
if not (csDesigning in TComponent(Data).ComponentState) then
begin
DesignOnlySignal := GetDesignOnlySignalFlag(Widget, dstMouseMotion);
if DesignOnlySignal then exit;
if not ControlGetsMouseMoveBefore(TControl(Data), True, Event) then exit;
end else
begin
// stop the signal, so that the widget does not auto react
g_signal_stop_emission_by_name(PGTKObject(Widget), 'motion-notify-event');
Result := CallBackDefaultReturn; // why not True if we want to stop it?
end;
ACtl := TWinControl(Data);
if not (csDesigning in ACtl.ComponentState) and
not (csCaptureMouse in ACtl.ControlStyle) and
([ssLeft,ssRight,ssMiddle]*ShiftState <> []) and
not (ACtl is TCustomForm) and not (ACtl is TScrollBar)
and not DragManager.IsDragging then
begin
if (Event^.x < 0) or (Event^.y < 0) or
(Event^.x > ACtl.Width) or (Event^.y > ACtl.Height) then
Exit(True);
end;
DeliverMouseMoveMessage(Widget,Event, ACtl);
if ACtl.FCompStyle = csWinControl then
Result := True; // stop signal
end;
{-------------------------------------------------------------------------------
GTKMotionNotifyAfter
Params: widget: PGTKWidget; event: PGDKEventMotion; data: gPointer
Returns: GBoolean
Called whenever the mouse is moved over a widget as last handler.
-------------------------------------------------------------------------------}
function GTKMotionNotifyAfter(widget:PGTKWidget; event: PGDKEventMotion;
data: gPointer): GBoolean; cdecl;
begin
Result := true; // stop event propagation
if (Event^.is_hint <> 0) and (Event^._type = GDK_MOTION_NOTIFY) then
GTKGetDevicePointer(Event^.window, Event^.device,
@Event^.x, @Event^.y, PGdkModifierType(@Event^.state));
{$IFDEF VerboseMouseBugfix}
DebugLn('[GTKMotionNotifyAfter] ',
DbgSName(TControl(Data)));
{$ENDIF}
// stop the signal, so that it is not sent to the parent widgets
g_signal_stop_emission_by_name(PGTKObject(Widget),'motion-notify-event');
if (csDesigning in TComponent(Data).ComponentState) then exit;
if ControlGetsMouseMoveBefore(TControl(Data), True, Event) then exit;
DeliverMouseMoveMessage(Widget,Event, TWinControl(Data));
end;
// restore old column sizing after dblclick. issue #18381
function ReturnColumnSizing(AGtkWidget: Pointer): gboolean; cdecl;
var
AIndex: PtrInt;
ASizing: TGtkTreeViewColumnSizing;
Column: PGtkTreeViewColumn;
ColWidth: gint;
begin
Result := AGtkWidget <> nil;
if Result then
begin
if g_object_get_data(PGObject(AGtkWidget),'lcl-column-resized-dblclick') <> nil then
begin
AIndex := {%H-}PtrInt(g_object_get_data(PGObject(AGtkWidget),'lcl-column-resized-dblclick') - 1);
ASizing := TGtkTreeViewColumnSizing({%H-}PtrUInt(g_object_get_data(PGObject(AGtkWidget),'lcl-column-resized-dblclick-oldsizing')) - 1);
g_object_set_data(PGObject(AGtkWidget),'lcl-column-resized-dblclick', nil);
g_object_set_data(PGObject(AGtkWidget),'lcl-column-resized-dblclick-oldsizing', nil);
if (AIndex >= 0) then
begin
Column := gtk_tree_view_get_column(PGtkTreeView(AGtkWidget), AIndex);
ColWidth := gtk_tree_view_column_get_width(Column);
gtk_tree_view_column_set_sizing(Column, ASizing);
gtk_tree_view_column_set_fixed_width(Column, ColWidth);
end;
end;
g_idle_remove_by_data(AGtkWidget);
end;
end;
{resizes column to like autosize does when dblclicked separator. issue #18381}
function ResizeColumnOnDblClick({%H-}ACtl: TWinControl; ScrolledWin: PGtkWidget;
TreeView: PGtkTreeView; const AMouseCoord: TPoint): Boolean;
var
Adjustment: PGtkAdjustment;
List: PGList;
Column: PGtkTreeViewColumn;
i, Accu: PtrInt;
xoffset: Integer;
Pt: TPoint;
CurSizing: TGtkTreeViewColumnSizing;
ColIndex: Integer;
ColWidth: Integer;
begin
Result := True;
Pt := AMouseCoord;
Accu := 0;
ColIndex := -1;
// think about horizontal scrollbar position too !
Adjustment := gtk_scrolled_window_get_hadjustment(PGtkScrolledWindow(ScrolledWin));
if Adjustment <> nil then
xoffset := Round(gtk_adjustment_get_value(Adjustment))
else
xoffset := 0;
List := gtk_tree_view_get_columns(TreeView);
try
for i := 0 to g_list_length(List) - 1 do
begin
Column := g_list_nth_data(List, i);
if Column = nil then
continue;
if gtk_tree_view_column_get_visible(Column) then
begin
ColWidth := gtk_tree_view_column_get_width(Column);
if (Accu + ColWidth + 3 >= Pt.X + xoffset) then
begin
// DebugLn('NOTICE: GtkTreeView column resizing on dblclick ',i);
CurSizing := gtk_tree_view_column_get_sizing(Column);
if gtk_tree_view_column_get_resizable(Column) and
(CurSizing <> GTK_TREE_VIEW_COLUMN_AUTOSIZE) then
begin
gtk_tree_view_column_set_sizing(Column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
gtk_tree_view_column_set_resizable(Column, True);
ColIndex := i;
// we are adding i + 1 since if i = 0 then ptr is null !
g_object_set_data(PGObject(TreeView),'lcl-column-resized-dblclick', {%H-}GPointer(ColIndex + 1));
Accu := Ord(CurSizing);
// we are adding Accu + 1 since if Accu = 0 then ptr is null !
g_object_set_data(PGObject(TreeView),'lcl-column-resized-dblclick-oldsizing', {%H-}GPointer(Accu + 1));
Accu := g_idle_add(@ReturnColumnSizing, TreeView);
break;
end;
break;
end;
Accu := Accu + ColWidth + 3 {section separator offset};
end;
end;
Result := not (ColIndex >= 0);
finally
g_list_free(List);
end;
end;
{-------------------------------------------------------------------------------
function ControlGetsMouseDownBefore(AControl: TControl): boolean;
Returns true, if mouse down event should be sent before the widget istelf
reacts.
-------------------------------------------------------------------------------}
function ControlGetsMouseDownBefore(AControl: TControl;
{%H-}AWidget: PGtkWidget; Event : PGdkEventButton): boolean;
var
Widget: PGtkWidget;
MainView: PGtkWidget;
Pt: TPoint;
begin
Result := True;
if AControl = nil then exit;
if not (csDesigning in AControl.ComponentState) and
(Event^.button = 1) and
(gdk_event_get_type(Event) = GDK_2BUTTON_PRESS) and
(AControl is TWinControl) and
(TWinControl(AControl).FCompStyle = csListView) and
(TListView(AControl).ViewStyle = vsReport) and
(TListView(AControl).ShowColumnHeaders) then
begin
Widget := {%H-}PGtkWidget(TWinControl(AControl).Handle);
if GTK_IS_SCROLLED_WINDOW(Widget) then
begin
MainView := gtk_bin_get_child(PGtkBin(Widget));
if GTK_IS_TREE_VIEW(MainView) then
begin
if gtk_tree_view_get_bin_window(PGtkTreeView(MainView)) <> Event^.window then
begin
Pt.X := Round(Event^.x_root);
Pt.Y := Round(Event^.y_root);
ScreenToClient(TWinControl(AControl).Handle, Pt);
Result := ResizeColumnOnDblClick(TWinControl(AControl), Widget,
PGtkTreeView(MainView), Pt);
end;
end;
end;
end else
if not (csDesigning in AControl.ComponentState) and
(Event^.button = 1) and
(gdk_event_get_type(Event) = GDK_BUTTON_PRESS) and
(AControl is TWinControl) and
(TWinControl(AControl).FCompStyle = csListView) and
(TListView(AControl).ViewStyle in [vsIcon, vsSmallIcon]) and
TListView(AControl).MultiSelect then
begin
// issue #22991 - this fixes crash after click on listview when modal form
// is closed
FixListViewRubberBand({%H-}PGtkWidget(TWinControl(AControl).Handle));
end;
end;
{-------------------------------------------------------------------------------
We must stop delivery of events from scrollbars of GtkScrollable, otherwise
if we make an double click on scollbar button, and after that click into
our control client area we need 2 click to make it focused.
gtk_signal_connect_after() is used, otherwise our scrollbar won't react on
such event.
-------------------------------------------------------------------------------}
function gtk2ScrollBarMouseBtnPress(widget: PGtkWidget; event: pgdkEventButton;
data: gPointer): GBoolean; cdecl;
begin
Result := True;
end;
{-------------------------------------------------------------------------------
We must stop delivery of events from scrollbars of GtkScrollable, otherwise
if we make an double click on scollbar button, and after that click into
our control client area we need 2 click to make it focused.
gtk_signal_connect_after() is used, otherwise our scrollbar won't react on
such event.
-------------------------------------------------------------------------------}
function gtk2ScrollBarMouseBtnRelease(widget: PGtkWidget; event: pgdkEventButton;
data: gPointer): GBoolean; cdecl;
begin
Result := True;
end;
{-------------------------------------------------------------------------------
gtkMouseBtnPress
Params: widget: PGTKWidget; event: PGDKEventMotion; data: gPointer
Returns: GBoolean
Called whenever the mouse is over a widget and a mouse button is pressed.
-------------------------------------------------------------------------------}
function gtkMouseBtnPress(widget: PGtkWidget; event: pgdkEventButton; data: gPointer): GBoolean; cdecl;
procedure CheckListSelection;
var
List: PGlist;
ListWidget: PGtkList;
R: TRect;
Info: PWinWidgetInfo;
begin
// When in browse mode and a listbox is focused and nothing is selected,
// the first item is focused.
// Clicking with the mouse on this item won't select it.
Info := GetWidgetInfo(Widget);
if Info = nil then Exit;
if Info^.CoreWidget = nil then Exit;
if not GtkWidgetIsA(Info^.CoreWidget, gtk_list_get_type) then
Exit;
ListWidget := PGtkList(Info^.CoreWidget);
// Check mode
if selection_mode(ListWidget^) <> GTK_SELECTION_BROWSE then Exit;
// Check selection
List := ListWidget^.selection;
if (List <> nil) and (List^.data <> nil) then Exit;
// Check if there are children
List := ListWidget^.children;
if List = nil then Exit;
if List^.Data = nil then Exit;
// we need only to check the first
with PGtkWidget(List^.Data)^.allocation do
R := Bounds(X, Y, Width, Height);
if not PtInRect(R, Point(Trunc(event^.X), Trunc(event^.Y))) then Exit;
// Select it
gtk_list_item_select(PGtkListItem(List^.Data));
end;
procedure FixTabControlFocusBehaviour;
var
Info: PWidgetInfo;
begin
{gtk_notebook have weird behaviour when clicked.
if there's active control on page it'll loose it's
focus and trigger OnExit (tab is taking focus).
issue #20493}
Info := GetWidgetInfo(Widget);
if not gtk_widget_is_focus(Widget) then
Include(Info^.Flags, wwiTabWidgetFocusCheck);
end;
var
DesignOnlySignal, StopSignal: boolean;
Path: PGtkTreePath;
x, y: gint;
Column: PGtkTreeViewColumn;
{$IFDEF VerboseMouseBugfix}
AWinControl: TWinControl;
{$ENDIF}
begin
Result := CallBackDefaultReturn;
{$IFDEF VerboseMouseBugfix}
AWinControl := TWinControl(Data);
DebugLn('');
DesignOnlySignal:=GetDesignOnlySignalFlag(Widget,dstMousePress);
DebugLn('[gtkMouseBtnPress] ',
DbgSName(AWinControl),
' Widget=',DbgS(Widget),
' ControlWidget=',DbgS(AWinControl.Handle),
' DSO='+dbgs(DesignOnlySignal),
' '+dbgs(TruncToInt(Event^.X)),',',dbgs(TruncToInt(Event^.Y)),
' Type='+dbgs(gdk_event_get_type(Event)));
{$ENDIF}
{$IFDEF EventTrace}
EventTrace('Mouse button Press', data);
{$ENDIF}
ResetDefaultIMContext;
//debugln('[gtkMouseBtnPress] calling DeliverMouseDownMessage Result=',dbgs(Result));
{$IFDEF Gtk2CallMouseDownBeforeContext}
if DeliverMouseDownMessage(Widget, Event, TWinControl(Data))<>0 then
begin
// Debugln(['[gtkMouseBtnPress] DeliverMouseDownMessage handled, stopping event']);
g_signal_stop_emission_by_name(PGTKObject(Widget), 'button-press-event');
exit(false);
end;
{$ENDIF}
//debugln('[gtkMouseBtnPress] called DeliverMouseDownMessage Result=',dbgs(Result));
StopSignal:=false;
if not (csDesigning in TComponent(Data).ComponentState) then
begin
// fix gtklist selection first
CheckListSelection;
DesignOnlySignal := GetDesignOnlySignalFlag(Widget, dstMousePress);
if DesignOnlySignal then exit;
if not ControlGetsMouseDownBefore(TControl(Data), Widget, Event) then Exit;
if Event^.button = 1 then
begin
//CaptureMouseForWidget(CaptureWidget,mctGTKIntf);
if (TControl(Data) is TCustomTabControl) and
not (csDesigning in TControl(Data).ComponentState) then
FixTabControlFocusBehaviour;
end
else
// if LCL process LM_CONTEXTMENU then stop the event propagation
if (Event^.button = 3) then
begin
Result:=DeliverContextMenuMessage(widget,true);
{emit selection change when right mouse button pressed
otherwise LCL is not updated since ChangeLock = 1 in case
of pressing right mouse button and 'changed' signal never reach lcl.
Issues #16972, #17888. }
if Result and GTK_IS_TREE_VIEW(Widget) and (event^.button = 3) then
begin
Column := gtk_tree_view_get_column(GTK_TREE_VIEW(Widget), 0);
Path:=nil;
if gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(Widget), Round(Event^.x), Round(Event^.y),
Path, Column, @x, @y) then
begin
gtk_tree_view_set_cursor(GTK_TREE_VIEW(Widget), Path, Column, False);
gtk_widget_queue_draw(Widget);
end;
StopSignal:=true;
end;
end;
end else begin
if (event^.Button=1) and
(TControl(Data) is TCustomTabControl) then
begin
// clicks on the tab control should be handled by the gtk (switching page)
end
else
begin
// stop the signal, so that the widget does not auto react
//DebugLn(['gtkMouseBtnPress stop signal']);
StopSignal:=true;
end;
end;
if StopSignal then
g_signal_stop_emission_by_name(PGTKObject(Widget), 'button-press-event');
{$IFDEF Gtk2CallMouseDownBeforeContext}
{$ELSE}
//debugln('[gtkMouseBtnPress] calling DeliverMouseDownMessage Result=',dbgs(Result));
DeliverMouseDownMessage(Widget, Event, TWinControl(Data));
{$ENDIF}
//debugln(['gtkMouseBtnPress END Control=',DbgSName(TObject(Data))]);
end;
{-------------------------------------------------------------------------------
procedure DeliverMouseDownMessage(widget: PGtkWidget; event : pgdkEventButton;
AWinControl: TWinControl);
Translate a gdk mouse press event into a LCL mouse down message and send it.
-------------------------------------------------------------------------------}
function DeliverMouseDownMessage(widget: PGtkWidget; event : pgdkEventButton;
AWinControl: TWinControl): PtrInt;
const
LastModifierKeys: TShiftState = [];
WHEEL_DELTA : array[Boolean] of Integer = (-120, 120);
var
MessI : TLMMouse;
MessE : TLMMouseEvent;
ShiftState: TShiftState;
MappedXY: TPoint;
EventXY: TPoint;
AClip: gtk2.PGtkClipboard;
AClipText: String;
{off $DEFINE VerboseMouseBugfix}
function CheckMouseButtonDown(MouseButton, BtnKey: longint): boolean;
begin
Result := False;
case gdk_event_get_type(Event) of
gdk_2button_press, gdk_3button_press:
// the gtk itself has detected a double/triple click
begin
// they were already detected and sent to the LCL
// -> skip this message
exit;
end;
end;
if (LastMouse.Down) then
if (gdk_event_get_type(Event) in [GDK_BUTTON_PRESS, gdk_2button_press, gdk_3button_press]) then
begin
// After a LastMouse.Down has to follow a LastMouse.Up. Issue #32199
if LastMouse.WinControl <> AWinControl then
Exit;
end
else
begin
{$IFDEF VerboseMouseBugfix}
DebugLn('DeliverMouseDownMessage: NO CLICK: LastMouse.Down=',dbgs(LastMouse.Down),
' Event^.theType=',dbgs(gdk_event_get_type(Event)));
{$ENDIF}
Exit;
end;
if (LastMouse.Down) and (gdk_event_get_type(Event) = GDK_BUTTON_PRESS) and
(csDesigning in AWinControl.ComponentState) then
exit;
MessI.Msg := CheckMouseButtonDownUp({%H-}TLCLIntfHandle(widget), AWinControl,
LastMouse, EventXY, MouseButton, True);
MessI.Keys := MessI.Keys or BtnKey;
if BtnKey in [MK_XBUTTON1, MK_XBUTTON2] then
MessI.Keys := MessI.Keys or BtnKey shl 11;
case LastMouse.ClickCount of
2: MessI.Keys := MessI.Keys or MK_DOUBLECLICK;
3: MessI.Keys := MessI.Keys or MK_TRIPLECLICK;
4: MessI.Keys := MessI.Keys or MK_QUADCLICK;
end;
{$IFDEF VerboseMouseBugfix}
DebugLn(' ClickCount=',dbgs(LastMouse.ClickCount));
{$ENDIF}
//DebugLn('DeliverMouseDownMessage ',DbgSName(AWinControl),' Mapped=',dbgs(MappedXY.X),',',dbgs(MappedXY.Y),' Event=',dbgs(EventXY.X),',',dbgs(EventXY.Y),' ',dbgs(LastMouse.ClickCount));
Result := True;
end;
begin
Result := 0;
if GTK_IS_TREE_VIEW(widget)
and (gtk_tree_view_get_bin_window(PGtkTreeView(widget)) <> Event^.window) then
Exit;
CheckTransparentWindow({%H-}TLCLIntfHandle(widget), AWinControl);
if (widget=nil) or (AWinControl=nil) then
Exit;
EventXY := Point(TruncToInt(Event^.X), TruncToInt(Event^.Y));
ShiftState := GTKEventStateToShiftState(Event^.State);
if ShiftState*[ssShift, ssCtrl, ssAlt, ssSuper] <> LastModifierKeys
then begin
LastModifierKeys := ShiftState*[ssShift, ssCtrl, ssAlt, ssSuper];
//DebugLn(['Adjust KeyStateList in MouseBtnDown',Integer(LastModifierKeys)]);
UpdateShiftState(GTK2WidgetSet.KeyStateList, LastModifierKeys);
end;
MappedXY := TranslateGdkPointToClientArea(Event^.Window, EventXY,
{%H-}PGtkWidget(AWinControl.Handle));
MappedXY := SubtractScoll({%H-}PGtkWidget(AWinControl.Handle), MappedXY);
//DebugLn('DeliverMouseDownMessage ',DbgSName(AWinControl),' Mapped=',dbgs(MappedXY.X),',',dbgs(MappedXY.Y),' Event=',dbgs(EventXY.X),',',dbgs(EventXY.Y));
if event^.Button in [4, 5] then
begin
// this is a mouse wheel event
MessE.Msg := LM_MOUSEWHEEL;
MessE.WheelDelta := WHEEL_DELTA[event^.Button = 4];
MessE.X := MappedXY.X;
MessE.Y := MappedXY.Y;
MessE.State := ShiftState;
MessE.UserData := AWinControl;
MessE.Button := 0;
// send the message directly to the LCL
NotifyApplicationUserInput(AWinControl, MessE.Msg);
Result:=DeliverMessage(AWinControl, MessE);
end
else
if event^.Button in [6, 7] then
begin
// this is a mouse wheel horizontal-scroll event
MessE.Msg := LM_MOUSEHWHEEL;
MessE.WheelDelta := WHEEL_DELTA[event^.Button = 7];
MessE.X := MappedXY.X;
MessE.Y := MappedXY.Y;
MessE.State := ShiftState;
MessE.UserData := AWinControl;
MessE.Button := 0;
// send the message directly to the LCL
NotifyApplicationUserInput(AWinControl, MessE.Msg);
Result:=DeliverMessage(AWinControl, MessE);
end
else
begin
// a normal mouse button is pressed
MessI.Keys := 0;
case event^.Button of
1: if not CheckMouseButtonDown(1, MK_LBUTTON) then Exit;
2: if not CheckMouseButtonDown(3, MK_MBUTTON) then Exit;
3: if not CheckMouseButtonDown(2, MK_RBUTTON) then Exit;
8: if not CheckMouseButtonDown(4, MK_XBUTTON1) then Exit;
9: if not CheckMouseButtonDown(5, MK_XBUTTON2) then Exit;
else
begin
MessI.Msg := LM_NULL;
exit;
end;
end; // case
MessI.XPos := MappedXY.X;
MessI.YPos := MappedXY.Y;
MessI.Keys := MessI.Keys or ShiftStateToKeys(ShiftState);
MessI.Result:=0;
{Gtk2 should always send LM_SETFOCUS first, as qt and win32 does. issues #24308, #31900}
if ((MessI.Msg = LM_LBUTTONDOWN) or (MessI.Msg = LM_RBUTTONDOWN) or
(MessI.Msg = LM_MBUTTONDOWN) or (MessI.Msg = LM_LBUTTONDBLCLK) or
(MessI.Msg = LM_XBUTTONDOWN)) and
not AWinControl.Focused and AWinControl.CanFocus and
not (csDesigning in AWinControl.ComponentState) then
begin
if (AWinControl is TCustomEdit) then
begin
AClipText := '';
AClip := gtk_clipboard_get(GDK_SELECTION_PRIMARY);
if gtk_clipboard_get_owner(AClip) = AClip then
AClip := nil;
if (AClip <> nil) and gtk_clipboard_wait_is_text_available(AClip) then
AClipText := gtk_clipboard_wait_for_text(AClip);
LCLIntf.SetFocus(AWinControl.Handle);
if Assigned(AClip) then
gtk_clipboard_set_text(AClip, PgChar(AClipText), length(AClipText));
end else
begin
if (AWinControl is TCustomTabControl)
or (AWinControl is TCustomPanel) then
// do not grab focus. issue #32237, #32515
else
LCLIntf.SetFocus(AWinControl.Handle);
end;
end;
// send the message directly to the LCL
NotifyApplicationUserInput(AWinControl, MessI.Msg);
Result := DeliverMessage(AWinControl, MessI);
// issue #19914
if (Result = 0) and (Event^.button = 1) and
GTK_IS_NOTEBOOK({%H-}PGtkWidget(AWinControl.Handle)) and
DragManager.IsDragging then
begin
g_object_set_data({%H-}PGObject(AWinControl.Handle),
'lclnotebookdragging', gpointer(PtrInt(1)));
end;
end;
end;
function DeliverContextMenuMessage(Widget: PGtkWidget;
CheckGtkPopupMenu: boolean): gboolean;
var
x, y: gint;
W: PGtkWidget;
Info: PWidgetInfo;
Old: TObject;
Msg: TLMContextMenu;
begin
Result:=CallBackDefaultReturn;
W := Widget;
gdk_display_get_pointer(gtk_widget_get_display(Widget), nil, @x, @y, nil);
Old := nil;
while W <> nil do
begin
Info := GetWidgetInfo(W);
if (Info <> nil) and (Info^.LCLObject <> Old) then
begin
Old := Info^.LCLObject;
FillByte(Msg{%H-}, SizeOf(Msg), 0);
Msg.Msg := LM_CONTEXTMENU;
Msg.hWnd := {%H-}HWND(W);
Msg.XPos := x;
Msg.YPos := y;
Result := DeliverMessage(Old, Msg) <> 0;
if Result then break;
end;
// check if widget has a standard popup menu
if (W = widget) and CheckGtkPopupMenu and Assigned(GTK_WIDGET_GET_CLASS(W)^.popup_menu) then
break;
W := gtk_widget_get_parent(W);
end;
end;
{-------------------------------------------------------------------------------
gtkMouseBtnPressAfter
Params: widget: PGTKWidget; event: PGDKEventMotion; data: gPointer
Returns: GBoolean
Called whenever the mouse is over a widget and a mouse button is pressed.
This is the last handler.
-------------------------------------------------------------------------------}
function gtkMouseBtnPressAfter(widget: PGtkWidget; event : pgdkEventButton;
data: gPointer) : GBoolean; cdecl;
begin
Result := True;
{$IFDEF VerboseMouseBugfix}
debugln('[gtkMouseBtnPressAfter] ',
DbgSName(TObject(Data)),
' Widget=',DbgS(Widget), ' ', GetWidgetClassName(Widget),
' ',dbgs(TruncToInt(Event^.X)),',',dbgs(TruncToInt(Event^.Y)));
{$ENDIF}
ResetDefaultIMContext;
// stop the signal, so that it is not sent to the parent widgets
g_signal_stop_emission_by_name(PGTKObject(Widget), 'button-press-event');
if (csDesigning in TComponent(Data).ComponentState) then exit;
if ControlGetsMouseDownBefore(TControl(Data),Widget, Event) then exit;
//debugln('[gtkMouseBtnPressAfter] calling DeliverMouseDownMessage');
DeliverMouseDownMessage(Widget, Event, TWinControl(Data));
end;
{-------------------------------------------------------------------------------
function ControlGetsMouseUpBefore(AControl: TControl): boolean;
Returns true, if mouse up event should be sent before the widget istelf
reacts.
-------------------------------------------------------------------------------}
function ControlGetsMouseUpBefore({%H-}AControl: TControl): boolean;
begin
{$NOTE ControlGetsMouseUpBefore always return true. See if still necessary}
Result:=true;
end;
{-------------------------------------------------------------------------------
procedure DeliverMouseUpMessage(widget: PGtkWidget; event : pgdkEventButton;
AWinControl: TWinControl);
Translate a gdk mouse release event into a LCL mouse up message and send it.
returns true, if message was handled by LCL
-------------------------------------------------------------------------------}
function DeliverMouseUpMessage(widget: PGtkWidget; event: pgdkEventButton;
AWinControl: TWinControl): boolean;
var
MessI : TLMMouse;
ShiftState: TShiftState;
MappedXY: TPoint;
EventXY: TPoint;
function CheckMouseButtonUp(MouseButton: longint; BtnShiftState: TShiftStateEnum): boolean;
begin
MessI.Msg := CheckMouseButtonDownUp({%H-}TLCLIntfHandle(widget),
AWinControl, LastMouse, EventXY, MouseButton, False);
Include(ShiftState, BtnShiftState);
Result := True;
end;
begin
Result := False;
CheckTransparentWindow({%H-}TLCLIntfHandle(widget), AWinControl);
if (widget=nil) or (AWinControl=nil) then
Exit;
EventXY := Point(TruncToInt(Event^.X), TruncToInt(Event^.Y));
MappedXY := TranslateGdkPointToClientArea(Event^.Window,
EventXY,
{%H-}PGtkWidget(AWinControl.Handle));
MappedXY := SubtractScoll({%H-}PGtkWidget(AWinControl.Handle), MappedXY);
//DebugLn(['DeliverMouseUpMessage ',GetWidgetDebugReport(Widget),' ',dbgsName(AWinControl),' ',dbgs(MappedXY)]);
ShiftState := GTKEventStateToShiftState(Event^.State);
case event^.Button of
1: if not CheckMouseButtonUp(1, ssLeft) then Exit;
2: if not CheckMouseButtonUp(3, ssMiddle) then Exit;
3: if not CheckMouseButtonUp(2, ssRight) then Exit;
8: if not CheckMouseButtonUp(4, ssExtra1) then Exit;
9: if not CheckMouseButtonUp(5, ssExtra2) then Exit;
else
begin
MessI.Msg := LM_NULL;
Exit;
end;
end; // case
MessI.XPos := MappedXY.X;
MessI.YPos := MappedXY.Y;
// do not send button in shiftstate on mouse up.issue #20916
case event^.Button of
1: ShiftState := ShiftState - [ssLeft];
2: ShiftState := ShiftState - [ssMiddle];
3: ShiftState := ShiftState - [ssRight];
end;
MessI.Keys := ShiftStateToKeys(ShiftState);
case LastMouse.ClickCount of
2: MessI.Keys := MessI.Keys or MK_DOUBLECLICK;
3: MessI.Keys := MessI.Keys or MK_TRIPLECLICK;
4: MessI.Keys := MessI.Keys or MK_QUADCLICK;
end;
if MessI.Msg <> LM_NULL then
begin
// send the message directly to the LCL
// (Posting the message via queue
// has the risk of getting out of sync with the gtk)
if event^.button in [8,9] then
MessI.Keys := MessI.Keys or ((event^.button - 7) shl 16);
MessI.Result := 0;
NotifyApplicationUserInput(AWinControl, MessI.Msg);
DeliverMessage(AWinControl, MessI);
if not AWinControl.HandleAllocated then
Result := True
else if MessI.Result <> 0 then
begin
// issue #19914
if GTK_IS_NOTEBOOK(Widget) then
begin
if g_object_get_data({%H-}PGObject(AWinControl.Handle),'lclnotebookdragging') <> nil then
begin
g_object_steal_data({%H-}PGObject(AWinControl.Handle),'lclnotebookdragging');
exit;
end;
end;
// handled by the LCL
//DebugLn(['DeliverMouseUpMessage msg was handled by the LCL, Stopping signal ...']);
g_signal_stop_emission_by_name(PGTKObject(Widget), 'button-release-event');
Result := True;
end;
end;
end;
{-------------------------------------------------------------------------------
gtkMouseBtnRelease
Params: widget: PGTKWidget; event: PGDKEventMotion; data: gPointer
Returns: GBoolean
Called whenever the mouse is over a widget and a mouse button is released.
-------------------------------------------------------------------------------}
function gtkMouseBtnRelease(widget: PGtkWidget; event : pgdkEventButton;
data: gPointer) : GBoolean; cdecl;
var
DesignOnlySignal: boolean;
AForm: TCustomForm;
begin
Result := CallBackDefaultReturn;
{$IFDEF VerboseMouseBugfix}
DesignOnlySignal:=GetDesignOnlySignalFlag(Widget,dstMouseRelease);
DebugLn(['[gtkMouseBtnRelease] A ',DbgSName(TObject(Data)),' ',
' Widget=',DbgS(Widget),
' Event.time=',event^.time,
' DSO=',DesignOnlySignal,
' ',TruncToInt(Event^.X),',',TruncToInt(Event^.Y),' Btn=',event^.Button]);
{$ENDIF}
//DebugLn('EEE1 MouseRelease Widget=',DbgS(Widget),
//' EventMask=',DbgS(gdk_window_get_events(Widget^.Window)),
//' GDK_BUTTON_RELEASE_MASK=',DbgS(GDK_BUTTON_RELEASE_MASK));
ResetDefaultIMContext;
if not (csDesigning in TComponent(Data).ComponentState) then
begin
DesignOnlySignal := GetDesignOnlySignalFlag(Widget, dstMouseRelease);
if (TControl(Data) is TCustomListView) and
(TListView(Data).ViewStyle in [vsIcon, vsSmallIcon]) and
TListView(Data).MultiSelect then
begin
// fixed crash.See issue #22778
FixListViewRubberBand(Widget);
end;
if DesignOnlySignal or (not ControlGetsMouseUpBefore(TControl(Data))) then
begin
ReleaseMouseCapture;
Exit;
end;
end else
begin
// stop the signal, so that the widget does not auto react
if not (TControl(Data) is TCustomTabControl) then
begin
g_signal_stop_emission_by_name(PGTKObject(Widget), 'button-release-event');
Result := not CallBackDefaultReturn;
end;
end;
if DeliverMouseUpMessage(Widget, Event, TWinControl(Data)) then
begin
ReleaseMouseCapture;
if not DragManager.IsDragging then
Result := not CallBackDefaultReturn
else
begin
// workaround for gtk2 bug where "clicked" isn't triggered
// because of Result=TRUE and we started modal form from OnDropDown event.
// see issue http://bugs.freepascal.org/view.php?id=14318 for details.
if GTK_IS_BUTTON(Widget) then
begin
AForm := GetParentForm(TWinControl(Data));
if (AForm <> nil) and (fsModal in AForm.FormState) then
gtk_button_clicked(PGtkButton(Widget));
end;
end;
end else
ReleaseMouseCapture;
if (csDesigning in TComponent(Data).ComponentState) then
begin
if (Event^.button = 3) then
begin
Result:=DeliverContextMenuMessage(widget,false);
end;
end;
end;
{-------------------------------------------------------------------------------
gtkMouseBtnReleaseAfter
Params: widget: PGTKWidget; event: PGDKEventMotion; data: gPointer
Returns: GBoolean
Called whenever the mouse is over a widget and a mouse button is released.
This is the last handler.
-------------------------------------------------------------------------------}
function gtkMouseBtnReleaseAfter(widget: PGtkWidget; event : pgdkEventButton;
data: gPointer) : GBoolean; cdecl;
begin
Result := True;
{$IFDEF VerboseMouseBugfix}
DebugLn('[gtkMouseBtnReleaseAfter] ',DbgSName(TObject(Data)),' ',
' Widget=',DbgS(Widget),
' ',dbgs(TruncToInt(Event^.X)),',',dbgs(TruncToInt(Event^.Y)),' Btn=',dbgs(event^.Button));
{$ENDIF}
// stop the signal, so that it is not sent to the parent widgets
{$NOTE See if is necessary to stop emission. Return true is enough to avoid propagation}
g_signal_stop_emission_by_name(PGTKObject(Widget),'button-release-event');
ResetDefaultIMContext;
if (csDesigning in TComponent(Data).ComponentState) then
else if ControlGetsMouseUpBefore(TControl(Data)) then
else
DeliverMouseUpMessage(Widget,Event,TWinControl(Data));
end;
function gtkMouseWheelCB(widget: PGtkWidget; event: PGdkEventScroll;
data: gPointer): GBoolean; cdecl;
var
AWinControl: TWinControl;
EventXY: TPoint;
ShiftState: TShiftState;
MappedXY: TPoint;
MessE : TLMMouseEvent;
begin
Result := False;
AWinControl:=TWinControl(Data);
CheckTransparentWindow({%H-}TLCLIntfHandle(widget), AWinControl);
if (widget=nil) or (AWinControl=nil) then
Exit;
EventXY:=Point(TruncToInt(Event^.X),TruncToInt(Event^.Y));
ShiftState := GTKEventStateToShiftState(Event^.State);
MappedXY:=TranslateGdkPointToClientArea(Event^.Window,EventXY,
{%H-}PGtkWidget(AWinControl.Handle));
MappedXY := SubtractScoll({%H-}PGtkWidget(AWinControl.Handle), MappedXY);
//DebugLn('gtkMouseWheelCB ',DbgSName(AWinControl),' Mapped=',dbgs(MappedXY.X),',',dbgs(MappedXY.Y),' Event=',dbgs(EventXY.X),',',dbgs(EventXY.Y));
// this is a mouse wheel event
FillChar(MessE{%H-},SizeOf(MessE),0);
case event^.direction of
GDK_SCROLL_UP: begin MessE.Msg := LM_MOUSEWHEEL; MessE.WheelDelta := 120; end;
GDK_SCROLL_DOWN: begin MessE.Msg := LM_MOUSEWHEEL; MessE.WheelDelta := -120; end;
GDK_SCROLL_LEFT: begin MessE.Msg := LM_MOUSEHWHEEL; MessE.WheelDelta := -120; end;
GDK_SCROLL_RIGHT: begin MessE.Msg := LM_MOUSEHWHEEL; MessE.WheelDelta := 120; end;
else
exit;
end;
MessE.X := MappedXY.X;
MessE.Y := MappedXY.Y;
MessE.State := ShiftState;
MessE.UserData := AWinControl;
MessE.Button := 0;
// send the message directly to the LCL
NotifyApplicationUserInput(AWinControl, MessE.Msg);
if DeliverMessage(AWinControl, MessE) <> 0 then
Result := True; // message handled by LCL, stop processing
end;
function gtkclickedCB(widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess: TLMessage;
begin
Result := CallBackDefaultReturn;
//DebugLn('[gtkclickedCB] ',TObject(Data).ClassName);
EventTrace('clicked', data);
if (LockOnChange(PgtkObject(Widget),0)>0) then exit;
Mess.Msg := LM_CLICKED;
DeliverMessage(Data, Mess);
end;
function gtkEnterCB(widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('enter', data);
{$ENDIF}
if csDesigning in TControl(Data).ComponentState then begin
// stop the signal, so that the widget does not auto react
g_signal_stop_emission_by_name(PGTKObject(Widget),'enter');
end;
Mess.msg := LM_MOUSEENTER;
DeliverMessage(Data, Mess);
end;
function gtkLeaveCB(widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('leave', data);
{$ENDIF}
if csDesigning in TControl(Data).ComponentState then begin
// stop the signal, so that the widget does not auto react
g_signal_stop_emission_by_name(PGTKObject(Widget),'leave');
end;
Mess.msg := LM_MOUSELEAVE;
DeliverMessage(Data, Mess);
end;
function gtksize_allocateCB(widget: PGtkWidget; size: pGtkAllocation;
data: gPointer) : GBoolean; cdecl;
{$IFDEF VerboseSizeMsg}
var
w, h: Gint;
{$ENDIF}
begin
Result := CallBackDefaultReturn;
EventTrace('size-allocate', data);
//with Size^ do DebugLn(Format('Trace:[gtksize_allocateCB] %s --> X: %d, Y: %d, Width: %d, Height: %d', [TObject(data).ClassName, X, Y, Width, Height]));
if not (TObject(Data) is TControl) then begin
// owner is not TControl -> ignore
DebugLn('WARNING: gtksize_allocateCB: Data is not TControl. Data=',
DbgS(Data),' ',GetWidgetClassName(Widget));
if Data<>nil then
DebugLn(' Data=',TObject(Data).ClassName);
RaiseGDBException('');
exit;
end;
{$IFDEF VerboseSizeMsg}
w:=0; h:=0;
if Widget^.window<>nil then
gdk_window_get_size(Widget^.window,@w,@h);
DebugLn(['gtksize_allocateCB: ',
DbgSName(TControl(Data)),
' widget=',GetWidgetDebugReport(Widget),
' fixwidget=',DbgS(GetFixedWidget(Widget)),
' NewSize=',Size^.Width,',',Size^.Height,
' GtkPos=',Widget^.allocation.x,',',Widget^.allocation.y,
',',Widget^.allocation.width,'x',Widget^.allocation.height,
' LCLPos=',TControl(Data).Left,',',TControl(Data).Top,
',',TControl(Data).Width,'x',TControl(Data).Height,
' gdkwindow=',w,'x',h]);
{$ENDIF}
{$IFDEF VerboseFormPositioning}
if TControl(Data) is TCustomForm then
DebugLn('VFP gtksize_allocateCB: ',TControl(Data).ClassName,' ',dbgs(Size^.X),',',dbgs(Size^.Y));
{$ENDIF}
SendSizeNotificationToLCL(Widget);
end;
function gtksize_allocate_client({%H-}widget: PGtkWidget; {%H-}size: pGtkAllocation;
data: gPointer): GBoolean; cdecl;
var
MainWidget: PGtkWidget;
ClientWidget: PGtkWidget;
begin
Result := CallBackDefaultReturn;
if (TObject(Data) is TWinControl) then
begin
{$IFDEF VerboseSizeMsg}
DebugLn('gtksize_allocate_client: ',
TControl(Data).Name,':',TControl(Data).ClassName,
' widget=',DbgS(Widget),
' NewSize=',dbgs(Size^.Width),',',dbgs(Size^.Height),
' Allocation='+dbgs(widget^.Allocation.Width)+'x'+dbgs(Widget^.Allocation.Height),
' Requisiton='+dbgs(widget^.Requisition.Width)+'x'+dbgs(Widget^.Requisition.Height)
);
{$ENDIF}
if not TWinControl(Data).HandleAllocated then begin
exit;
end;
MainWidget:={%H-}PGtkWidget(TWinControl(Data).Handle);
ClientWidget:=GetFixedWidget(MainWidget);
if GTK_WIDGET_REALIZED(ClientWidget) then begin
// the gtk resizes bottom to top, that means the
// inner widget (client area) is resized before the outer widget
// is resized. Because the LCL reads both sizes, keep this message back.
SaveClientSizeNotification(ClientWidget);
end;
end else begin
// owner is not TWinControl -> ignore
DebugLn('WARNING: gtksize_allocate_client: Data is not TWinControl. Data=',
DbgS(Data));
exit;
end;
end;
function gtkconfigureevent( widget: PGtkWidget; event : PgdkEventConfigure;
data: gPointer) : GBoolean; cdecl;
var
Allocation : TGtkAllocation;
begin
{ This signal is emitted for top level controls only, i.e. only controls
that are not children. Thus, we register this event only for forms.
This event is fired when the form is sized, moved or changes Z order.
}
FillChar(Allocation{%H-},SizeOf(TGtkAllocation),0);
with Allocation do begin
X:= Event^.X;
Y:= Event^.Y;
Width:= Event^.Width;
Height:= Event^.Height;
end;
Result:= gtksize_allocateCB( Widget, @Allocation, Data);
end;
function gtkInsertText(widget: PGtkWidget; char : pChar;
NewTextLength : Integer; Position : pgint; data: gPointer) : GBoolean; cdecl;
var
Memo: TCustomMemo;
CurrLength, CutLength: integer;
begin
Result := CallBackDefaultReturn;
{ GTK does not provide its own max. length for memos
we have to do our own. }
if TObject(Data) is TCustomMemo then begin
if (NewTextLength = 1) and (char^ = #13) and (LineEnding = #10) then
char^ := #10;
Memo:= TCustomMemo(Data);
if Memo.MaxLength <= 0 then Exit;
CurrLength:= gtk_text_get_length(PGtkText(widget));
if CurrLength + NewTextLength <= Memo.MaxLength then Exit;
CutLength:= CurrLength + NewTextLength - Memo.MaxLength;
if NewTextLength - CutLength > 0 then
gtk_editable_insert_text(PGtkEditable(widget), char,
NewTextLength - CutLength, Position);
g_signal_stop_emission_by_name(PGtkObject(widget), 'insert_text');
end;
if TObject(Data) is TCustomEdit then
if (NewTextLength = 1) and (char^ = #13) then
g_signal_stop_emission_by_name(PGtkObject(widget), 'insert_text');
end;
function gtkSetEditable( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Set Editable', data);
Mess.msg := LM_SETEDITABLE;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkMoveWord( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Move Word', data);
Mess.msg := LM_MOVEWORD;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkMovePage( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Move Page', data);
Mess.msg := LM_MOVEPAGE;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkMoveToRow( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Move To Row!!', data);
Mess.msg := LM_MOVETOROW;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkMoveToColumn( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('MoveToColumn', data);
Mess.msg := LM_MOVETOCOLUMN;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkKillChar( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Kill Char', data);
Mess.msg := LM_KILLCHAR;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkKillWord( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Kill Word', data);
Mess.msg := LM_KILLWORD;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkKillLine( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Kill Line', data);
Mess.msg := LM_KILLLINE;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkCutToClip( widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Cut to clip', data);
Mess.msg := LM_CUT;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkCopyToClip( {%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Copy to Clip', data);
Mess.msg := LM_COPY;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkPasteFromClip( widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
EventTrace('Paste from clip', data);
Mess.msg := LM_PASTE;
Result:= DeliverMessage(Data, Mess) = 0;
end;
function gtkValueChanged({%H-}widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
Mess : TLMessage;
begin
Result := CallBackDefaultReturn;
EventTrace('Value changed', data);
Mess.msg := LM_CHANGED;
DeliverMessage(Data, Mess);
end;
{------------------------------------------------------------------------------
Method: gtkTimerCB
Params: Data - pointer TGtkITimerInfo structure
Returns: 1 - 1 tells gtk to restart the timer
0 - 0 will stop the gtk timer
Callback for gtk timer.
WARNING: There seems to be a bug in gtk-1.2.x which breaks
gtk_timeout_remove so we have to dispose data here & return 0
(s.a. KillTimer).
------------------------------------------------------------------------------}
function gtkTimerCB(Data: gPointer): gBoolean; cdecl;
var
TimerInfo: PGtkITimerinfo;
begin
EventTrace ('TimerCB', nil);
Result := GdkFalse; // assume: timer will stop
TimerInfo:=PGtkITimerinfo(Data);
if (FTimerData=nil) or (FTimerData.IndexOf(Data)<0) then begin
{$IFDEF VerboseTimer}
DebugLn('gtkTimerCB Timer was killed: TimerInfo=',DbgS(TimerInfo));
{$ENDIF}
// timer was killed
Result:=GdkFalse; // stop timer
end else begin
{$IFDEF VerboseTimer}
DebugLn('gtkTimerCB Timer Event: TimerInfo=',DbgS(TimerInfo));
{$ENDIF}
if TimerInfo^.TimerFunc <> nil
then begin
// Call users timer function
//DebugLn(['gtkTimerCB ']);
TimerInfo^.TimerFunc;
Result:=GdkTrue; // timer will go on
end
else begin
Result := GdkFalse; // stop timer
end;
end;
if (Result<>GdkFalse) and (FTimerData.IndexOf(Data)<0) then begin
// timer was killed
// -> stop timer
Result:=GdkFalse;
end;
if Result=GdkFalse then begin
{$IFDEF VerboseTimer}
DebugLn('gtkTimerCB Timer was STOPPED: TimerInfo=',DbgS(TimerInfo));
{$ENDIF}
// timer will be stopped
// -> free timer data, if not already done
if (FTimerData<>nil) and (FTimerData.IndexOf(Data)>=0) then begin
FTimerData.Remove(Data);
Dispose (TimerInfo); // free memory with timer data
end;
end;
end;
function gtkFocusInNotifyCB (widget : PGtkWidget; {%H-}event : PGdkEvent;
data : gpointer) : GBoolean; cdecl;
var
MessI : TLMEnter;
begin
Result := CallBackDefaultReturn;
//DebugLn('[gtkFocusInNotifyCB] ',TControl(data).Name,':',TObject(data).ClassName);
{$IFDEF EventTrace}
EventTrace ('FocusInNotify (alias Enter)', data);
{$ENDIF}
if csDesigning in TControl(Data).ComponentState then begin
// stop the signal, so that the widget does not auto react
g_signal_stop_emission_by_name(PGTKObject(Widget),'focus-in-event');
end;
MessI.msg := LM_Enter;
DeliverMessage(Data, MessI);
end;
function gtkFocusOutNotifyCB (widget : PGtkWidget; {%H-}event : PGdkEvent;
data : gpointer) : GBoolean; cdecl;
var
MessI : TLMExit;
begin
Result := CallBackDefaultReturn;
//DebugLn('[gtkFocusOutNotifyCB] ',TControl(data).Name,':',TObject(data).ClassName);
{$IFDEF EventTrace}
EventTrace ('FocusOutNotify (alias Exit)', data);
{$ENDIF}
if csDesigning in TControl(Data).ComponentState then begin
// stop the signal, so that the widget does not auto react
g_signal_stop_emission_by_name(PGTKObject(Widget),'focus-out-event');
end;
MessI.msg := LM_Exit;
DeliverMessage(Data, MessI);
end;
function get_gtk_scroll_type(range: PGTKRange): TGtkScrollType;
type
TUnOpaqueTimer=record
timeout_id: guint;
ScrollType: TGTkScrollType;
end;
PUnOpaqueTimer=^TUnopaqueTimer;
begin
if (gtk_major_version=2) and (gtk_minor_version<6) and
(Range^.Timer<>nil) then
{ gtk2 pre gtk2.6 ONLY, tested gtk2.0.
Later versions (gtk2.6+) have a change-value signal that includes scrolltype anyways }
Result := PUnOpaqueTimer(Range^.Timer)^.ScrollType
else
Result := GTK_SCROLL_NONE;
end;
{$IFDEF VerboseGtkScrollbars}
procedure DebugScrollStyle(Scroll: LongInt);
begin
DbgOut('TYPE=');
case Scroll of
GTK_SCROLL_NONE: DbgOut('GTK_SCROLL_NONE ');
GTK_SCROLL_STEP_BACKWARD: DbgOut('GTK_SCROLL_STEP_BACKWARD ');
GTK_SCROLL_STEP_FORWARD: DbgOut('GTK_SCROLL_STEP_FORWARD ');
GTK_SCROLL_PAGE_BACKWARD: DbgOut('GTK_SCROLL_PAGE_BACKWARD ');
GTK_SCROLL_PAGE_FORWARD: DbgOut('GTK_SCROLL_PAGE_FORWARD ');
GTK_SCROLL_JUMP: DbgOut('GTK_SCROLL_JUMP ');
GTK_SCROLL_STEP_UP: DbgOut('GTK_SCROLL_STEP_UP');
GTK_SCROLL_STEP_DOWN: DbgOut('GTK_SCROLL_STEP_DOWN');
GTK_SCROLL_PAGE_UP: DbgOut('GTK_SCROLL_PAGE_UP');
GTK_SCROLL_PAGE_DOWN: DbgOut('GTK_SCROLL_PAGE_DOWN');
GTK_SCROLL_STEP_LEFT: DbgOut('GTK_SCROLL_STEP_LEFT');
GTK_SCROLL_STEP_RIGHT: DbgOut('GTK_SCROLL_STEP_RIGHT');
GTK_SCROLL_PAGE_LEFT: DbgOut('GTK_SCROLL_PAGE_LEFT');
GTK_SCROLL_PAGE_RIGHT: DbgOut('GTK_SCROLL_PAGE_RIGHT');
GTK_SCROLL_START: DbgOut('GTK_SCROLL_START');
GTK_SCROLL_END: DbgOut('GTK_SCROLL_END');
else
DbgOut(IntToStr(Scroll), '->?');
end;
end;
{$ENDIF VerboseGtkScrollbars}
function ScrollTypeToSbCode(IsVertSB: boolean; ScrollType: TGtkScrollType;
UpdatePolicy: TGtkUpdateType): Integer;
begin
case ScrollType of
GTK_SCROLL_STEP_BACKWARD:
if IsVertSB then
Result := SB_LINEUP
else
Result := SB_LINELEFT;
GTK_SCROLL_STEP_FORWARD:
if IsVertSB then
Result := SB_LINEDOWN
else
Result := SB_LINERIGHT;
GTK_SCROLL_PAGE_BACKWARD:
if IsVertSB then
Result := SB_PAGEUP
else
Result := SB_PAGELEFT;
GTK_SCROLL_PAGE_FORWARD:
if IsVertSB then
Result := SB_PAGEDOWN
else
Result := SB_PAGERIGHT;
GTK_SCROLL_STEP_UP:
Result := SB_LINEUP;
GTK_SCROLL_STEP_DOWN:
Result := SB_LINEDOWN;
GTK_SCROLL_PAGE_UP:
Result := SB_PAGEUP;
GTK_SCROLL_PAGE_DOWN:
Result := SB_PAGEDOWN;
GTK_SCROLL_STEP_LEFT:
Result := SB_LINELEFT;
GTK_SCROLL_STEP_RIGHT:
Result := SB_LINERIGHT;
GTK_SCROLL_PAGE_LEFT:
Result := SB_PAGELEFT;
GTK_SCROLL_PAGE_RIGHT:
Result := SB_PAGERIGHT;
GTK_SCROLL_START:
if IsVertSB then
Result := SB_TOP
else
Result := SB_LEFT;
GTK_SCROLL_END:
if IsVertSB then
Result := SB_BOTTOM
else
Result := SB_RIGHT;
else
begin
{$IFDEF VerboseGtkScrollbars}
debugln('ScrollTypeToSbCode: Scroll_type=', IntToStr(ScrollType));
{$Endif}
if UpdatePolicy=GTK_UPDATE_CONTINUOUS then
Result := SB_THUMBTRACK
else
Result := SB_THUMBPOSITION;
end;
end;
end;
function GTKHScrollCB(Adjustment: PGTKAdjustment; data: GPointer): GBoolean; cdecl;
var
Msg: TLMHScroll;
Scroll: PGtkRange;
ScrollType: TGtkScrollType;
begin
Result := CallBackDefaultReturn;
//DebugLn(Format('Trace:[GTKHScrollCB] Value: %d', [RoundToInt(Adjustment^.Value)]));
Scroll := PgtkRange(g_object_get_data(PGObject(Adjustment), odnScrollBar));
if Scroll<>nil then begin
Msg.Msg := LM_HSCROLL;
with Msg do begin
Pos := Round(Adjustment^.Value);
if Pos < High(SmallPos)
then SmallPos := Pos
else SmallPos := High(SmallPos);
ScrollBar := HWND({%H-}PtrUInt(Scroll));
ScrollType := get_gtk_scroll_type(Scroll);
ScrollCode := ScrollTypeToSbCode(False, ScrollType,
gtk_range_get_update_policy(Scroll));
end;
DeliverMessage(Data, Msg);
end;
end;
function GTKVScrollCB(Adjustment: PGTKAdjustment; data: GPointer): GBoolean; cdecl;
var
Msg: TLMVScroll;
Scroll: PGtkRange;
ScrollType: TGtkScrollType;
begin
//TODO: implement SB_THUMBPOSITION message after track is finished
Result := CallBackDefaultReturn;
{$IFDEF SYNSCROLLDEBUG}
DebugLn(Format('Trace:[GTKVScrollCB] Value: %d', [RoundToInt(Adjustment^.Value)]));
{$ENDIF}
Scroll := PgtkRange(g_object_get_data(PGObject(Adjustment), odnScrollBar));
if Scroll<>nil then begin
Msg.Msg := LM_VSCROLL;
with Msg do begin
Pos := Round(Adjustment^.Value);
if Pos < High(SmallPos)
then SmallPos := Pos
else SmallPos := High(SmallPos);
//DebugLn('GTKVScrollCB A Adjustment^.Value=',dbgs(Adjustment^.Value),' SmallPos=',dbgs(SmallPos));
ScrollBar := HWND({%H-}PtrUInt(Scroll));
ScrollType := get_gtk_scroll_type(Scroll);
// GTK1 has a bug with wheel mouse. It sometimes gives the wrong direction.
ScrollCode := ScrollTypeToSbCode(True, ScrollType,
gtk_range_get_update_policy(Scroll));
//DebugLn('GTKVScrollCB B Adjustment^.Value=',dbgs(Adjustment^.Value),' ScrollCode=',dbgs(ScrollCode),' ScrollType=',dbgs(ScrollType));
end;
DeliverMessage(Data, Msg);
end;
end;
function Gtk2RangeScrollCB(ARange: PGtkRange; AScrollType: TGtkScrollType;
AValue: gdouble; AWidgetInfo: PWidgetInfo): gboolean; cdecl;
var
Msg: TLMVScroll;
MaxValue: gdouble;
Widget: PGTKWidget;
begin
Result := CallBackDefaultReturn;
Widget:=PGTKWidget(ARange);
{$IFDEF SYNSCROLLDEBUG}
DebugLn(Format('Trace:[Gtk2RangeScrollCB] Value: %d', [RoundToInt(AValue)]));
{$ENDIF}
if G_OBJECT_TYPE(ARange) = gtk_hscrollbar_get_type then
Msg.Msg := LM_HSCROLL
else
Msg.Msg := LM_VSCROLL;
if ARange^.adjustment^.page_size > 0 then
MaxValue := ARange^.adjustment^.upper - ARange^.adjustment^.page_size
else
MaxValue := ARange^.adjustment^.upper;
if (AValue > MaxValue) then
AValue := MaxValue
else if (AValue < ARange^.adjustment^.lower) then
AValue := ARange^.adjustment^.lower;
with Msg do
begin
Pos := Round(AValue);
if Pos < High(SmallPos) then
SmallPos := Pos
else
SmallPos := High(SmallPos);
ScrollBar := HWND({%H-}PtrUInt(ARange));
ScrollCode := GtkScrollTypeToScrollCode(AScrollType);
end;
DeliverMessage(AWidgetInfo^.LCLObject, Msg);
if Msg.Scrollcode=SB_THUMBTRACK then
begin
if Widget^.state = 0 then
begin
Msg.ScrollCode := SB_THUMBPOSITION;
DeliverMessage(AWidgetInfo^.LCLObject, Msg);
Msg.ScrollCode:=SB_ENDSCROLL;
DeliverMessage(AWidgetInfo^.LCLObject, Msg);
end;
end
else Widget^.state := 1;
if (AWidgetInfo^.LCLObject is TScrollingWinControl) and
((Msg.ScrollCode=SB_LINEUP) or (Msg.ScrollCode=SB_LINEDOWN)) then
Result:=True;
end;
procedure Gtk2RangeValueChanged(ARange: PGTKRange; data: GPointer); cdecl;
var
Msg: TLMVScroll;
MaxValue: gdouble;
Widget: PGTKWidget;
AValue: gDouble;
begin
// used only by TScrollingWinControl ! #issue #25479
Widget := PGTKWidget(ARange);
if G_OBJECT_TYPE(ARange) = gtk_hscrollbar_get_type then
Msg.Msg := LM_HSCROLL
else
Msg.Msg := LM_VSCROLL;
AValue := gtk_range_get_value(ARange);
if ARange^.adjustment^.page_size > 0 then
MaxValue := ARange^.adjustment^.upper - ARange^.adjustment^.page_size
else
MaxValue := ARange^.adjustment^.upper;
if (AValue > MaxValue) then
AValue := MaxValue
else if (AValue < ARange^.adjustment^.lower) then
AValue := ARange^.adjustment^.lower;
with Msg do
begin
Pos := Round(AValue);
if Pos < High(SmallPos) then
SmallPos := Pos
else
SmallPos := High(SmallPos);
ScrollBar := HWND({%H-}PtrUInt(ARange));
ScrollCode := SB_THUMBTRACK;
end;
// do not send messages to LCL if we are already synced.
if (Msg.Msg = LM_VSCROLL) and
(TScrollingWinControl(PWidgetInfo(Data)^.LCLObject).VertScrollBar.Position = Msg.Pos) then
exit
else
if (Msg.Msg = LM_HSCROLL) and
(TScrollingWinControl(PWidgetInfo(Data)^.LCLObject).HorzScrollBar.Position = Msg.Pos) then
exit;
DeliverMessage(PWidgetInfo(Data)^.LCLObject, Msg);
if Msg.Scrollcode=SB_THUMBTRACK then
begin
if Widget^.state = 0 then
begin
Msg.ScrollCode := SB_THUMBPOSITION;
DeliverMessage(PWidgetInfo(Data)^.LCLObject, Msg);
Msg.ScrollCode:=SB_ENDSCROLL;
DeliverMessage(PWidgetInfo(Data)^.LCLObject, Msg);
end;
end
else Widget^.state := 1;
end;
function Gtk2RangeScrollPressCB(Widget: PGtkWidget;
Event: PGdkEventButton; Data: gPointer): gboolean; cdecl;
begin
Widget^.state := 2;
Result := CallBackDefaultReturn;;
end;
function Gtk2RangeScrollReleaseCB(Widget: PGtkWidget;
Event: PGdkEventButton; Data: gPointer): gboolean; cdecl;
var
Avalue: gdouble;
WidgetInfo: PWidgetInfo;
begin
AValue:=PGtkRange(Widget)^.adjustment^.value;
WidgetInfo:=GetWidgetInfo(Widget);
if not Assigned(WidgetInfo) then
WidgetInfo:=GetWidgetInfo(Widget^.parent);
if Assigned(WidgetInfo) and (Widget^.state = 1) then
Gtk2RangeScrollCB(PGtkRange(Widget), 0, AValue, WidgetInfo);
Widget^.state := 0;
Result := CallBackDefaultReturn;
end;
function Gtk2RangeUbuntuScrollCB(Adjustment: PGTKAdjustment; data: GPointer): GBoolean; cdecl;
var
Msg: TLMVScroll;
AWidgetInfo: PWidgetInfo;
Scroll: PGtkRange;
ScrollType: TGtkScrollType;
LastPos: PtrInt;
begin
Result := CallBackDefaultReturn;
AWidgetInfo:=PWidgetInfo(Data);
//debugln(['Gtk2RangeUbuntuScrollCB ',DbgSName(AWidgetInfo^.LCLObject)]);
Scroll := PgtkRange(g_object_get_data(PGObject(Adjustment), odnScrollBar));
if Scroll<>nil then begin
FillByte(Msg{%H-},SizeOf(Msg),0);
if Scroll^.orientation=GTK_ORIENTATION_VERTICAL then
Msg.Msg := LM_VSCROLL
else
Msg.Msg := LM_HSCROLL;
with Msg do begin
Pos := Round(Adjustment^.Value);
if Pos < High(SmallPos)
then SmallPos := Pos
else SmallPos := High(SmallPos);
LastPos:={%H-}PtrInt(g_object_get_data(PGObject(Adjustment), odnScrollBarLastPos));
if LastPos=Pos then begin
//debugln(['Gtk2RangeUbuntuScrollCB duplicate message => skip']);
exit;
end;
g_object_set_data(PGObject(Adjustment), odnScrollBarLastPos, {%H-}gpointer(Pos));
//DebugLn('Gtk2RangeUbuntuScrollCB A Adjustment^.Value=',dbgs(Adjustment^.Value),' SmallPos=',dbgs(SmallPos));
ScrollBar := HWND({%H-}PtrUInt(Scroll));
ScrollType := get_gtk_scroll_type(Scroll);
ScrollCode := ScrollTypeToSbCode(True, ScrollType,
gtk_range_get_update_policy(Scroll));
//DebugLn('Gtk2RangeUbuntuScrollCB B Adjustment^.Value=',dbgs(Adjustment^.Value),' ScrollCode=',dbgs(ScrollCode),' ScrollType=',dbgs(ScrollType));
end;
DeliverMessage(AWidgetInfo^.LCLObject, Msg);
Result:=true;
end;
end;
function Gtk2ScrolledWindowScrollCB(AScrollWindow: PGtkScrolledWindow; AEvent: PGdkEventScroll; AWidgetInfo: PWidgetInfo): gboolean; cdecl;
var
Msg: TLMVScroll;
AValue: Double;
Range: PGtkRange;
begin
{$IFDEF SYNSCROLLDEBUG}
debugln(['Gtk2ScrolledWindowScrollCB ']);
{$ENDIF}
case AEvent^.direction of
GDK_SCROLL_UP,
GDK_SCROLL_DOWN: Msg.Msg := LM_VSCROLL;
GDK_SCROLL_LEFT,
GDK_SCROLL_RIGHT: Msg.Msg := LM_HSCROLL;
end;
case Msg.Msg of
LM_VSCROLL: Range := GTK_RANGE(AScrollWindow^.vscrollbar);
LM_HSCROLL: Range := GTK_RANGE(AScrollWindow^.hscrollbar);
end;
AValue := power(Range^.adjustment^.page_size, 2 / 3);
if (AEvent^.direction = GDK_SCROLL_UP) or
(AEvent^.direction = GDK_SCROLL_LEFT)
then
AValue := -AValue;
AValue := gtk_range_get_value(Range) + AValue;
AValue := Max(AValue, Range^.adjustment^.lower);
AValue := Min(AValue, Range^.adjustment^.upper - Range^.adjustment^.page_size);
with Msg do
begin
Pos := Round(AValue);
if Pos < High(SmallPos) then
SmallPos := Pos
else
SmallPos := High(SmallPos);
ScrollBar := HWND({%H-}PtrUInt(Range));
ScrollCode := SB_THUMBPOSITION;
end;
Result := DeliverMessage(AWidgetInfo^.LCLObject, Msg) <> 0;
end;
{------------------------------------------------------------------------------
Function: GTKKeySnooper
Params: Widget: The widget for which this event is fired
Event: The keyevent data
FuncData: the user parameter passed when the snooper was installed
Returns: True if other snoopers shouldn't handled
Keeps track of which keys are pressed. The keycode is casted to a pointer and
if it exists in the KeyStateList, it is pressed.
------------------------------------------------------------------------------}
function GTKKeySnooper(Widget: PGtkWidget; Event: PGdkEventKey;
FuncData: gPointer): gInt; cdecl;
var
KeyStateList: TFPList absolute FuncData;
procedure UpdateToggleList(const AVKeyCode: Integer);
begin
// Check for a toggle
// If the remove was successfull, the key was on
// else it was off so we should set the toggle flag
if KeyStateList.Remove({%H-}Pointer(PtrUInt(AVKeyCode or KEYMAP_TOGGLE))) < 0
then KeyStateList.Add({%H-}Pointer(PtrUInt(AVKeyCode or KEYMAP_TOGGLE)));
end;
procedure UpdateList(const AVKeyCode: Integer; const APressed: Boolean);
begin
if AVKeyCode = 0 then Exit;
if APressed
then begin
if KeyStateList.IndexOf({%H-}Pointer(PtrUInt(AVKeyCode))) < 0
then KeyStateList.Add({%H-}Pointer(PtrUInt(AVKeyCode)));
end
else begin
KeyStateList.Remove({%H-}Pointer(PtrUInt(AVKeyCode)));
end;
end;
const
STATE_MAP: array[0..3] of TShiftStateEnum = (
ssShift,
ssCtrl,
ssAlt,
ssSuper
);
VK_MAP: array[0..3] of array[0..2] of Byte = (
// (Main key, alt key 1, alt key 2) to check
(VK_SHIFT, VK_LSHIFT, VK_RSHIFT),
(VK_CONTROL, VK_LCONTROL, VK_RCONTROL),
(VK_MENU, VK_LMENU, VK_RMENU),
(VK_LWIN, VK_RWIN, 0)
);
var
KeyCode: Word;
KCInfo: TKeyCodeInfo;
VKey: Byte;
Pressed, InState: Boolean;
n: Integer;
ShiftState: TShiftState;
begin
Result := 0;
// TODO: Remove when KeyStateList is obsolete
case gdk_event_get_type(Event) of
GDK_KEY_PRESS: Pressed := True;
GDK_KEY_RELEASE: Pressed := False;
else
// not interested
Exit;
end;
KeyCode := Event^.hardware_keycode;
//DebugLn('GTKKeySnooper: KeyCode=%u -> %s', [KeyCode, Event^._String ]);
if KeyCode > High(MKeyCodeInfo)
then begin
if Pressed
then DebugLn('[WARNING] Key pressed with keycode (%u) larger than expected: K=0x%x S="%s"', [
KeyCode,
Event^.KeyVal,
Event^._String
]);
Exit;
end;
KCInfo := MKeyCodeInfo[KeyCode];
if KCInfo.VKey1 = 0
then begin
if Pressed
then DebugLn('[WARNING] Key pressed without VKey: K=0x%x S="%s"', [
Event^.KeyVal,
Event^._String
]);
Exit;
end;
if KeyStateList = nil then exit;
ShiftState := GTKEventStateToShiftState(Event^.State);
if (KCInfo.Flags and KCINFO_FLAG_SHIFT_XOR_NUM <> 0)
and ((ssShift in ShiftState) xor (ssNum in ShiftState))
then VKey := KCInfo.VKey2
else VKey := KCInfo.VKey1;
UpdateList(VKey, Pressed);
if Pressed then
UpdateToggleList(VKey);
// Add special left and right codes
case Event^.KeyVal of
GDK_Key_Shift_L: UpdateList(VK_LSHIFT, Pressed);
GDK_Key_Shift_R: UpdateList(VK_RSHIFT, Pressed);
GDK_Key_Control_L: UpdateList(VK_LCONTROL, Pressed);
GDK_Key_Control_R: UpdateList(VK_RCONTROL, Pressed);
GDK_Key_Alt_L: UpdateList(VK_LMENU, Pressed);
GDK_Key_Alt_R: UpdateList(VK_RMENU, Pressed);
end;
// Recheck the list against the modifiers
for n := 0 to High(STATE_MAP) do
begin
// Skip our current key, since the state is updated after the event
if VKey = VK_MAP[n][0] then Continue;
if VKey = VK_MAP[n][1] then Continue;
if VKey = VK_MAP[n][2] then Continue;
InState := STATE_MAP[n] in ShiftState;
UpdateList(VK_MAP[n][0], InState);
UpdateList(VK_MAP[n][1], InState);
UpdateList(VK_MAP[n][2], InState);
end;
// if the VKey has multiple VK_codes then SHIFT distinguishes between them
// In that case SHIFT is not pressed
// On the next event the shift flag will be restored based on modifiers
if Pressed and ((KCInfo.Flags and KCINFO_FLAG_SHIFT_XOR_NUM) <> 0)
then begin
UpdateList(VK_SHIFT, False);
UpdateList(VK_LSHIFT, False);
UpdateList(VK_RSHIFT, False);
end;
end;
function gtkYearChanged({%H-}Widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
var
MSG: TLMessage;
begin
Result := CallBackDefaultReturn;
EventTrace('year changed', data);
MSG.Msg := LM_YEARCHANGED;
DeliverPostMessage(Data, MSG);
end;
procedure GtkDragDataReceived(widget:PGtkWidget; context:PGdkDragContext;
x:gint; y:gint; selection_data:PGtkSelectionData; info:guint; time:guint; Data: gPointer);cdecl;
var
S: TStringList;
I: Integer;
FileName, DecodedFileName: String;
Files: Array of String;
Form: TControl;
Result: Boolean;
U: TURI;
begin
//DebugLn('GtkDragDataReceived ' + PChar(selection_data^.data));
Result := False;
if selection_data^.data <> nil then // data is list of uri
try
SetLength(Files{%H-}, 0);
S := TStringList.Create;
try
S.Text := PChar(selection_data^.data);
for I := 0 to S.Count - 1 do
begin
FileName := S[I];
if FileName = '' then Continue;
// uri = protocol://hostname/file name
U := ParseURI(FileName);
if (SameText(U.Host, 'localhost') or (U.Host = '')) and SameText(U.Protocol, 'file')
and URIToFileName(FileName, DecodedFileName) then // convert uri of local files to file name
begin
FileName := DecodedFileName;
end;
// otherwise: protocol and hostname are preserved!
if FileName = '' then Continue;
SetLength(Files, Length(Files) + 1);
Files[High(Files)] := FileName;
//DebugLn('GtkDragDataReceived ' + DbgS(I) + ': ' + PChar(FileName));
end;
finally
S.Free;
end;
if Length(Files) > 0 then
begin
Form := nil;
if (TObject(Data) is TWinControl) then
Form := TWinControl(Data).IntfGetDropFilesTarget;
if Form is TCustomForm then
TCustomForm(Form).IntfDropFiles(Files)
else
if (Application <> nil) and (Application.MainForm <> nil) then
Application.MainForm.IntfDropFiles(Files);
if Application <> nil then
Application.IntfDropFiles(Files);
Result := True;
end;
except
Application.HandleException(nil);
end;
gtk_drag_finish(Context, Result, false, time);
end;
{------------------------------------------------------------------------------
ClipboardSelectionReceivedHandler
This handler is called whenever a clipboard owner sends data. Because the LCL
caches all requests, this is typically data from another application.
Copy the received selection data record and buffer to
internal record and buffer (ClipboardSelectionData)
------------------------------------------------------------------------------}
procedure ClipboardSelectionReceivedHandler({%H-}TargetWidget: PGtkWidget;
SelectionData: PGtkSelectionData; TimeID: guint32; {%H-}Data: Pointer); cdecl;
var TempBuf: Pointer;
c: PClipboardEventData;
i: integer;
begin
// at any time there can be several requests
// find the request with the correct TimeID
i:=ClipboardSelectionData.Count-1;
while (i>=0) do begin
c:=PClipboardEventData(ClipboardSelectionData[i]);
if c^.TimeID=TimeID then break;
dec(i);
end;
{$IFDEF DEBUG_CLIPBOARD}
DebugLn('[ClipboardSelectionReceivedHandler] A TimeID=',dbgs(TimeID),' RequestIndex=',dbgs(i),
' selection=',dbgs(SelectionData^.selection)+'='+GdkAtomToStr(SelectionData^.selection),
' target=',dbgs(SelectionData^.Target)+'='+GdkAtomToStr(SelectionData^.Target),
' theType=',dbgs(SelectionData^._type)+'='+GdkAtomToStr(SelectionData^._type),
' format=',dbgs(SelectionData^.format),
' len=',dbgs(SelectionData^.length)
);
{$ENDIF}
if i<0 then exit;
// free old data
if (c^.Data.Data<>nil) then FreeMem(c^.Data.Data);
// copy the information
c^.Data:=SelectionData^;
// copy the raw data to an internal buffer (the gtk buffer will be destroyed
// right after this event)
{$IFDEF DEBUG_CLIPBOARD}
DebugLn('[ClipboardSelectionReceivedHandler] B DataLen=',dbgs(c^.Data.Length));
{$ENDIF}
if (c^.Data.Data<>nil)
and (c^.Data.Length>0) then begin
GetMem(TempBuf,c^.Data.Length);
Move(c^.Data.Data^,TempBuf^,c^.Data.Length);
c^.Data.Data:=TempBuf;
{$IFDEF DEBUG_CLIPBOARD}
DebugLn('[ClipboardSelectionReceivedHandler] C FirstCharacter=',dbgs(ord(PChar(c^.Data.Data)[0])));
{$ENDIF}
end else begin
{if (SelectionData^.Target <> GDK_TARGET_STRING)
and (SelectionData^.length<0) then begin
if gtk_selection_convert (TargetWidget, SelectionData^.selection,
GDK_TARGET_STRING, TimeID)<>GdkFalse
then begin
DebugLn('[ClipboardSelectionReceivedHandler] D TimeID=',dbgs(TimeID),' RequestIndex=',dbgs(i),
' selection=',dbgs(SelectionData^.selection)+'='+GdkAtomToStr(SelectionData^.selection),
' target=',dbgs(SelectionData^.Target)+'='+GdkAtomToStr(SelectionData^.Target),
' theType=',dbgs(SelectionData^.theType)+'='+GdkAtomToStr(SelectionData^.theType),
' format=',dbgs(SelectionData^.format),
' len=',dbgs(SelectionData^.length)
);
end;
end;}
c^.Data.Data:=nil;
end;
end;
// Helper procedures for ClipboardSelectionRequestHandler :
procedure Transform2CompoundText(SelectionData: PGtkSelectionData; MemStream: TMemoryStream);
var
Buffer: Pointer;
BufLength: integer;
P: PChar;
begin
BufLength:=integer(MemStream.Size);
P:=StrAlloc(BufLength);
MemStream.Read(P^,BufLength);
Buffer:=nil;
BufLength:=0;
gdk_string_to_compound_text(P,
@SelectionData^._Type,
@SelectionData^.Format,ppguchar(@Buffer),@BufLength);
StrDispose(P);
gtk_selection_data_set(SelectionData,SelectionData^.Target,
SelectionData^.Format,Buffer,BufLength);
gdk_free_compound_text(Buffer);
end;
procedure SetData(SelectionData: PGtkSelectionData; MemStream: TMemoryStream);
var
Buffer: Pointer;
BufLength: integer;
begin
BufLength:=integer(MemStream.Size);
GetMem(Buffer,BufLength);
Assert(Assigned(Buffer) and (BufLength>0), 'SetData: GTK2 clipboard Buffer=Nil or empty.');
MemStream.Read(Buffer^,BufLength);
gtk_selection_data_set(SelectionData,SelectionData^.Target,8,Buffer,BufLength);
FreeMem(Buffer);
end;
{------------------------------------------------------------------------------
ClipboardSelectionRequestHandler
This signal is emitted if someone requests the clipboard data.
Since the lcl clipboard caches all requests this will typically be another
application.
------------------------------------------------------------------------------}
procedure ClipboardSelectionRequestHandler({%H-}TargetWidget: PGtkWidget;
SelectionData: PGtkSelectionData; {%H-}Info: cardinal; {%H-}TimeID: cardinal;
{%H-}Data: Pointer); cdecl;
var
ClipboardType: TClipboardType;
FormatID: cardinal;
MemStream: TMemoryStream;
begin
{$IFDEF DEBUG_CLIPBOARD}
DebugLn('*** [ClipboardSelectionRequestHandler] START');
{$ENDIF}
if SelectionData^.Target=0 then exit;
for ClipboardType:=Low(TClipboardType) to High(TClipboardType) do
begin
if SelectionData^.Selection<>ClipboardTypeAtoms[ClipboardType] then
Continue;
if ClipboardHandler[ClipboardType]=nil then
break;
// handler found for this type of clipboard
// now create a stream and find a supported format
{$IFDEF DEBUG_CLIPBOARD}
DebugLn('[ClipboardSelectionRequestHandler] "',ClipboardTypeName[ClipboardType],'" Format=',GdkAtomToStr(SelectionData^.Target),' ID=',dbgs(SelectionData^.Target));
{$ENDIF}
MemStream:=TMemoryStream.Create;
try
// the gtk-interface provides automatically some formats, that the lcl
// does not know. Wrapping them to lcl formats ...
FormatID:=SelectionData^.Target;
if ((FormatID=gdk_atom_intern('UTF8_STRING',GdkTrue))
and (ClipboardExtraGtkFormats[ClipboardType][gfUTF8_STRING]))
or ((FormatID=gdk_atom_intern('COMPOUND_TEXT',GdkTrue))
and (ClipboardExtraGtkFormats[ClipboardType][gfCOMPOUND_TEXT]))
or ((FormatID=gdk_atom_intern('STRING',GdkTrue))
and (ClipboardExtraGtkFormats[ClipboardType][gfSTRING]))
or ((FormatID=gdk_atom_intern('TEXT',GdkTrue))
and (ClipboardExtraGtkFormats[ClipboardType][gfTEXT]))
then
FormatID:=gdk_atom_intern('text/plain',GdkTrue);
{$IFDEF DEBUG_CLIPBOARD}
DebugLn('[ClipboardSelectionRequestHandler] FormatID=',dbgs(FormatID),'=',GdkAtomToStr(FormatID),' ',dbgs(ClipboardExtraGtkFormats[ClipboardType][gfCOMPOUND_TEXT]));
{$ENDIF}
// get the requested data by calling a handler for this clipboard type
// and build data for gtk
ClipboardHandler[ClipboardType](FormatID,MemStream);
if MemStream.Size=0 then
break;
MemStream.Position:=0;
if (FormatID=gdk_atom_intern('text/plain',GdkTrue))
and (SelectionData^.Target=gdk_atom_intern('COMPOUND_TEXT',GdkTrue)) then
// if the format was wrapped, transform text/plain back to COMPOUND_TEXT
Transform2CompoundText(SelectionData, MemStream)
else
SetData(SelectionData, MemStream);
finally
MemStream.Free;
end;
break;
end; // for
end;
{------------------------------------------------------------------------------
ClipboardSelectionLostOwnershipHandler
This signal is emitted if another application gets the clipboard ownership.
------------------------------------------------------------------------------}
function ClipboardSelectionLostOwnershipHandler({%H-}TargetWidget: PGtkWidget;
EventSelection: PGdkEventSelection; {%H-}Data: Pointer): cardinal; cdecl;
var ClipboardType: TClipboardType;
begin
//DebugLn('*** [ClipboardSelectionLostOwnershipHandler] ',DbgS(targetwidget));
for ClipboardType:=Low(TClipboardType) to High(TClipboardType) do
if EventSelection^.Selection=ClipboardTypeAtoms[ClipboardType] then begin
{$IFDEF DEBUG_CLIPBOARD}
DebugLn('*** [ClipboardSelectionLostOwnershipHandler] ',ClipboardTypeName[ClipboardType]);
{$ENDIF}
if (ClipboardWidget<>nil)
and (gdk_selection_owner_get(ClipboardTypeAtoms[ClipboardType])
<> GetControlWindow(ClipboardWidget))
and Assigned(ClipboardHandler[ClipboardType]) then begin
// handler found for this type of clipboard
{$IFDEF DEBUG_CLIPBOARD}
DebugLn('[ClipboardSelectionLostOwnershipHandler] ',ClipboardTypeName[ClipboardType]);
{$ENDIF}
ClipboardHandler[ClipboardType](0,nil);
ClipboardHandler[ClipboardType]:=nil;
end;
break;
end;
Result:=1;
end;
{-------------------------------------------------------------------------------
procedure GTKStyleChanged(Widget: PGtkWidget; previous_style : PGTKStyle;
Data: Pointer); cdecl;
Handler for style changes. For example the user changes the theme.
But also called on every widget realize, so it should not release all styles
everytime.
-------------------------------------------------------------------------------}
{$IFDEF EventTrace}
procedure GTKStyleChanged({%H-}Widget: PGtkWidget; {%H-}previous_style : PGTKStyle;
{%H-}Data: Pointer); cdecl;
begin
EventTrace('style-set', nil);
//ReleaseAllStyles;
end;
procedure GTKStyleChangedAfter({%H-}Widget: PGtkWidget; {%H-}previous_style : PGTKStyle;
{%H-}Data: Pointer); cdecl;
begin
EventTrace('style-set', nil);
{ Note:
This event is called for many widgets but not for all.
It is called on creation and called when the theme changes.
The resizing should occur when all styles were updated by the gtk.
The gtk itself uses size messages via queue.
Maybe the solution is to use g_idle_add for the top level form and resize.
}
//debugln('style-set after ',DbgSName(TWinControl(Data)));
//LCLObject.InvalidateClientRectCache(False);
end;
{$ENDIF}
function gtkListBoxSelectionChangedAfter(widget: PGtkWidget; data: gPointer
): GBoolean; cdecl;
var
Mess: TLMessage;
GtkList: PGtkList;
begin
Result := CallBackDefaultReturn;
{$IFDEF EventTrace}
EventTrace('gtkListSelectionChangedAfter', data);
{$ENDIF}
GtkList:=PGtkList(widget);
if (GtkList^.selection = nil) or (LockOnChange(PGtkObject(widget),0) > 0) then
Exit;
FillChar(Mess{%H-},SizeOf(Mess),0);
Mess.msg := LM_SelChange;
if gtkListGetSelectionMode(GtkList)=GTK_SELECTION_SINGLE then
gtk_list_set_selection_mode(GtkList,GTK_SELECTION_BROWSE);
DeliverMessage(Data, Mess);
end;
//DRAG CALLBACK FUNCTIONS
function edit_drag_data_received(widget: pgtkWidget; Context: pGdkDragContext;
X: Integer; Y: Integer; seldata: pGtkSelectionData; info: Integer;
time: Integer; data: pointer): GBoolean; cdecl;
Var
Texts : String;
Begin
Result:=false;
//DebugLn('Trace:***********Drag Data Received*******************');
if Seldata^.Length > 0 then
Begin
Texts := StrPas(PChar(SelData^.data));
//DebugLn('Trace:' + Texts);
//DebugLn('Trace:0');
TCustomEdit(Data).Caption := Texts;
//DebugLn('Trace:1');
end;
gtk_drag_finish(Context,false,false,time);
end;
function edit_source_drag_data_get({%H-}widget : pgtkWidget;
{%H-}Context : pGdkDragContext;
Selection_data : pGtkSelectionData;
info : Integer;
{%H-}time : Integer;
data : pointer) : GBoolean; cdecl;
var
strTemp : PChar;
Texts : String;
Begin
Result:=false;
if (info = TARGET_ROOTWIN) then begin
//DebugLn('Trace:I WAS DROPPED ON THE ROOTWIN')
end
else Begin
//DebugLn('Trace:*********Setting Data************');
Texts := TCustomEdit(data).Text;
//DebugLn('Trace:0');
strTemp := StrAlloc(length(Texts) + 1);
try
StrPCopy(strTemp, Texts);
//DebugLn('Trace:1');
gtk_selection_data_set(selection_data,selection_data^.target,
8,
PGUChar(StrTemp),
length(Texts)+1);
//DebugLn('Trace:2');
finally
strDispose(strTemp);
end;
//DebugLn('Trace:3');
end;
end;
function Edit_source_drag_data_delete(widget: pGtkWidget;
context: pGdkDragContext; data: gpointer): gBoolean; cdecl;
begin
//DebugLn('Trace:***************');
//DebugLn('Trace:DELETE THE DATA');
Result:=false;
end;
// combobox callbacks
function gtkComboBoxShowAfter({%H-}widget: PGtkWidget; data: gPointer): GBoolean; cdecl;
var
Mess : TLMCommand;
AComboBox: TCustomComboBox absolute data;
begin
Result := True;
EventTrace('ComboBoxShowAfter', data);
AComboBox.IntfGetItems;
FillChar(Mess{%H-},SizeOf(Mess),0);
Mess.Msg := CN_Command;
Mess.NotifyCode := CBN_DROPDOWN;
Result := DeliverMessage(Data, Mess) = 0;
end;
function gtkComboBoxHideAfter({%H-}widget: PGtkWidget; data: gPointer): GBoolean; cdecl;
var
Mess : TLMCommand;
begin
Result := True;
EventTrace('ComboBoxHideAfter', data);
FillChar(Mess{%H-},SizeOf(Mess),0);
Mess.Msg := CN_Command;
Mess.NotifyCode := CBN_CLOSEUP;
Result := DeliverMessage(Data, Mess) = 0;
end;
// notebook callbacks
procedure DrawNotebookPageIcon(Page: TCustomPage; Widget: PGtkWidget);
var
NoteBook: TCustomTabControl;
NoteBookWidget: PGtkWidget;
PageWidget: PGtkWidget;
TabWidget: PGtkWidget;
ImageIndex: Integer;
begin
NoteBook := Page.Parent as TCustomTabControl;
ImageIndex := NoteBook.GetImageIndex(Page.PageIndex);
if (NoteBook.Images = nil) or (ImageIndex < 0)
or (Page.ImageIndex >= NoteBook.Images.Count)
or (not NoteBook.HandleAllocated)
or (not Page.HandleAllocated)
then exit;
NoteBookWidget := {%H-}PGtkWidget(NoteBook.Handle);
PageWidget := {%H-}PGtkWidget(Page.Handle);
// get the tab container and the tab icon widget
TabWidget := gtk_notebook_get_tab_label(PGtkNoteBook(NotebookWidget),
PageWidget);
if TabWidget = nil then exit;
{$IFDEF VerboseGtkToDos}{$note reimplement}{$ENDIF}
DrawImageListIconOnWidget(NoteBook.Images.ResolutionForPPI[NoteBook.ImagesWidth, NoteBook.Font.PixelsPerInch, NoteBook.GetCanvasScaleFactor],
ImageIndex, Widget);
end;
function PageIconWidgetExposeAfter(Widget: PGtkWidget; Event: PGDKEventExpose;
Data: gPointer): GBoolean; cdecl;
var
ThePage: TCustomPage absolute Data;
begin
Result := false;
//DebugLn('PageIconWidgetExposeAfter ',DbgS(Widget));
EventTrace('PageIconWidgetExposeAfter', Data);
if (Event^.Count > 0) then exit;
DrawNotebookPageIcon(ThePage, Widget);
end;