mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-17 12:29:27 +02:00
10140 lines
318 KiB
PHP
10140 lines
318 KiB
PHP
{%MainUnit gtkproc.pp}
|
|
|
|
{******************************************************************************
|
|
Misc Support Functs
|
|
******************************************************************************
|
|
used by:
|
|
GTKObject
|
|
GTKWinAPI
|
|
GTKCallback
|
|
******************************************************************************
|
|
*****************************************************************************
|
|
* *
|
|
* This file is part of the Lazarus Component Library (LCL) *
|
|
* *
|
|
* See the file COPYING.modifiedLGPL.txt, included in this distribution, *
|
|
* for details about the copyright. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* *
|
|
*****************************************************************************
|
|
}
|
|
|
|
{off $DEFINE VerboseAccelerator}
|
|
{off $DEFINE VerboseUpdateSysColorMap}
|
|
|
|
{$IFOPT C-}
|
|
// Uncomment for local trace
|
|
//{$C+}
|
|
//{$DEFINE ASSERT_IS_ON}
|
|
{$ENDIF}
|
|
|
|
function gtk_widget_get_xthickness(Style : PGTKStyle) : gint;
|
|
begin
|
|
If (Style <> nil) then begin
|
|
{$IfNDef GTK2}
|
|
If (Style^.klass = nil) then
|
|
result := 0
|
|
else
|
|
{$EndIf}
|
|
result := Style^.{$IfNDef GTK2}klass^.{$EndIF}xthickness
|
|
end else
|
|
result := 0;
|
|
end;
|
|
|
|
function gtk_widget_get_ythickness(Style : PGTKStyle) : gint;
|
|
begin
|
|
If (Style <> nil) then begin
|
|
{$IfNDef GTK2}
|
|
If (Style^.klass = nil) then
|
|
result := 0
|
|
else
|
|
{$EndIf}
|
|
result := Style^.{$IfNDef GTK2}klass^.{$EndIF}ythickness
|
|
end else
|
|
result := 0;
|
|
end;
|
|
|
|
function gtk_widget_get_xthickness(Widget : PGTKWidget) : gint; overload;
|
|
begin
|
|
result := gtk_widget_get_xthickness(gtk_widget_get_style(Widget));
|
|
end;
|
|
|
|
function gtk_widget_get_ythickness(Widget : PGTKWidget) : gint; overload;
|
|
begin
|
|
result := gtk_widget_get_ythickness(gtk_widget_get_style(Widget));
|
|
end;
|
|
|
|
function GetGtkContainerBorderWidth(Widget: PGtkContainer): gint;
|
|
begin
|
|
Result:=(Widget^.flag0 and bm_TGtkContainer_border_width)
|
|
shr bp_TGtkContainer_border_width;
|
|
end;
|
|
|
|
procedure gdk_event_key_get_string(Event : PGDKEventKey; var theString : Pointer);
|
|
begin
|
|
{$IfDef GTK2}
|
|
theString := Pointer(Event^._String);
|
|
{$Else}
|
|
theString := Pointer(Event^.TheString);
|
|
{$EndIF}
|
|
end;
|
|
|
|
procedure gdk_event_key_set_string(Event: PGDKEventKey; const NewString: PChar
|
|
);
|
|
var
|
|
OldString: PChar;
|
|
begin
|
|
{$IfDef GTK2}
|
|
OldString := Pointer(Event^._String);
|
|
{$Else}
|
|
OldString := Pointer(Event^.TheString);
|
|
{$EndIF}
|
|
// MG: should we set Event^.length := 0; or is this used for mem allocation?
|
|
if (OldString<>nil) then begin
|
|
if (NewString<>nil) then
|
|
OldString[0]:=NewString[0]
|
|
else
|
|
OldString[0]:=#0;
|
|
end;
|
|
end;
|
|
|
|
function gdk_event_get_type(Event : Pointer) : TGdkEventType;
|
|
begin
|
|
{$IfDef GTK2}
|
|
result := PGdkEvent(Event)^._type;
|
|
{$Else}
|
|
result := PGdkEvent(Event)^.TheType;
|
|
{$EndIF}
|
|
end;
|
|
|
|
procedure RememberKeyEventWasHandledByLCL(Event: PGdkEventKey;
|
|
BeforeEvent: boolean);
|
|
var
|
|
HandledEvent: TLCLHandledKeyEvent;
|
|
EventList: TFPList;
|
|
begin
|
|
if KeyEventWasHandledByLCL(Event,BeforeEvent) then exit;
|
|
if BeforeEvent then begin
|
|
if LCLHandledKeyEvents=nil then
|
|
LCLHandledKeyEvents:=TFPList.Create;
|
|
EventList:=LCLHandledKeyEvents;
|
|
end else begin
|
|
if LCLHandledKeyAfterEvents=nil then
|
|
LCLHandledKeyAfterEvents:=TFPList.Create;
|
|
EventList:=LCLHandledKeyAfterEvents;
|
|
end;
|
|
HandledEvent:=TLCLHandledKeyEvent.Create(Event);
|
|
EventList.Add(HandledEvent);
|
|
while EventList.Count>10 do begin
|
|
HandledEvent:=TLCLHandledKeyEvent(EventList[0]);
|
|
HandledEvent.Free;
|
|
EventList.Delete(0);
|
|
end;
|
|
end;
|
|
|
|
function KeyEventWasHandledByLCL(Event: PGdkEventKey; BeforeEvent: boolean
|
|
): boolean;
|
|
var
|
|
i: Integer;
|
|
HandledEvent: TLCLHandledKeyEvent;
|
|
EventList: TFPList;
|
|
begin
|
|
Result:=false;
|
|
if BeforeEvent then
|
|
EventList:=LCLHandledKeyEvents
|
|
else
|
|
EventList:=LCLHandledKeyAfterEvents;
|
|
if EventList=nil then exit;
|
|
for i:=0 to EventList.Count-1 do begin
|
|
HandledEvent:=TLCLHandledKeyEvent(EventList[i]);
|
|
if HandledEvent.IsEqual(Event) then begin
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
{$Ifdef GTK2}
|
|
function gtk_class_get_type(aclass : Pointer) : TGtkType;
|
|
begin
|
|
If (aclass <> nil) then
|
|
result := PGtkTypeClass(aclass)^.g_Type
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
function gtk_object_get_class(anobject : Pointer) : Pointer;
|
|
begin
|
|
If (anobject <> nil) then
|
|
result := PGtkTypeObject(anobject)^.g_Class
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function gtk_window_get_modal(window:PGtkWindow):gboolean;
|
|
begin
|
|
if assigned(Window) then
|
|
result := GTK2.gtk_window_get_modal(window)
|
|
else
|
|
result := False;
|
|
end;
|
|
|
|
function gdk_region_union_with_rect(region:PGdkRegion; rect:PGdkRectangle) : PGdkRegion;
|
|
begin
|
|
result := gdk_region_copy(region);
|
|
GDK2.gdk_region_union_with_rect(result, rect);
|
|
end;
|
|
|
|
function gdk_region_intersect(source1:PGdkRegion; source2:PGdkRegion) : PGdkRegion;
|
|
begin
|
|
result := gdk_region_copy(source1);
|
|
GDK2.gdk_region_intersect(result, source2);
|
|
end;
|
|
|
|
function gdk_region_union(source1:PGdkRegion; source2:PGdkRegion) : PGdkRegion;
|
|
begin
|
|
result := gdk_region_copy(source1);
|
|
GDK2.gdk_region_union(result, source2);
|
|
end;
|
|
|
|
function gdk_region_subtract(source1:PGdkRegion; source2:PGdkRegion) : PGdkRegion;
|
|
begin
|
|
result := gdk_region_copy(source1);
|
|
GDK2.gdk_region_subtract(result, source2);
|
|
end;
|
|
|
|
function gdk_region_xor(source1:PGdkRegion; source2:PGdkRegion) : PGdkRegion;
|
|
begin
|
|
result := gdk_region_copy(source1);
|
|
GDK2.gdk_region_xor(result, source2);
|
|
end;
|
|
|
|
Procedure gdk_text_extents(TheFont: TGtkIntfFont;
|
|
Str: PChar; StrLength: integer;
|
|
lbearing, rbearing, width, ascent, descent: Pgint);
|
|
var
|
|
Layout : PPangoLayout;
|
|
Extents : TPangoRectangle;
|
|
begin
|
|
//DebugLn(['gdk_text_extents Str="',Str,'" StrLength=',StrLength,' lbearing=',lbearing<>nil,' rbearing=',rbearing<>Nil,' width=',width<>nil,' ascent=',ascent<>nil,' descent=',descent<>Nil,' ',TheFont<>Nil]);
|
|
Layout:=TheFont;
|
|
pango_layout_set_single_paragraph_mode(Layout, TRUE);
|
|
pango_layout_set_width(Layout, -1);
|
|
pango_layout_set_text(Layout, Str, StrLength);
|
|
if Assigned(width) then
|
|
pango_layout_get_pixel_size(Layout, width, nil);
|
|
if Assigned(lbearing) or Assigned(rbearing)
|
|
or Assigned(ascent) or Assigned(descent) then begin
|
|
pango_layout_get_extents(Layout, nil, @Extents);
|
|
|
|
if Assigned(lbearing) then
|
|
lbearing^ := PANGO_LBEARING(extents) div PANGO_SCALE;
|
|
|
|
if Assigned(rbearing) then
|
|
rBearing^ := PANGO_RBEARING(extents) div PANGO_SCALE;
|
|
|
|
if Assigned(ascent) then
|
|
ascent^ := PANGO_ASCENT(extents) div PANGO_SCALE;
|
|
|
|
if Assigned(descent) then
|
|
descent^ := PANGO_DESCENT(extents) div PANGO_SCALE;
|
|
end;
|
|
end;
|
|
|
|
{$EndIf Gtk2}
|
|
|
|
procedure BeginGDKErrorTrap;
|
|
begin
|
|
Inc(GdkTrapCalls);
|
|
if GdkTrapIsSet then
|
|
exit;
|
|
|
|
gdk_error_trap_push; //try to prevent GDK Bad Drawable/X Windows Errors
|
|
// from killing us...
|
|
|
|
{$IfDef GDK_ERROR_TRAP_FLUSH}
|
|
gdk_flush; //only for debugging purposes DO NOT enable by default.
|
|
// slows things down intolerably for actual use, if we ever
|
|
// have a real need for it, it should be called from that
|
|
// specific function, since this gets called constantly during
|
|
// drawing.
|
|
{$EndIf}
|
|
|
|
GdkTrapIsSet:=true;
|
|
end;
|
|
|
|
procedure EndGDKErrorTrap;
|
|
var
|
|
Xerror : gint;
|
|
begin
|
|
Dec(GdkTrapCalls);
|
|
if (not GdkTrapIsSet) then
|
|
RaiseGDBException('EndGDKErrorTrap without BeginGDKErrorTrap');
|
|
if (GdkTrapCalls > 0) then
|
|
exit;
|
|
|
|
Xerror := gdk_error_trap_pop;
|
|
|
|
GdkTrapIsSet:=false;
|
|
|
|
{$IFDEF VerboseGtkToDos}{$note TODO: enable standard error_log handling}{$ENDIF}
|
|
{$IfDef REPORT_GDK_ERRORS}
|
|
If (Xerror<>0) then
|
|
RaiseGDBException('A GDK/X Error occured, this is normally fatal. The error code was : ' + IntToStr(Xerror));
|
|
{$EndIf}
|
|
end;
|
|
|
|
function dbgGRect(const ARect: PGDKRectangle): string;
|
|
begin
|
|
if ARect=nil then begin
|
|
Result:='nil';
|
|
end else begin
|
|
Result:='x='+dbgs(ARect^.x)+',y='+dbgs(ARect^.y)
|
|
+',w='+dbgs(ARect^.width)+',h='+dbgs(ARect^.height);
|
|
end;
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------
|
|
function CreatePChar(const s: string): PChar;
|
|
|
|
Allocates a new PChar
|
|
------------------------------------------------------------------------------}
|
|
function CreatePChar(const s: string): PChar;
|
|
begin
|
|
Result:=StrAlloc(length(s) + 1);
|
|
StrPCopy(Result, s);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function ComparePChar(P1, P2: PChar): boolean;
|
|
|
|
Checks if P1 and P2 have the same content.
|
|
------------------------------------------------------------------------------}
|
|
function ComparePChar(P1, P2: PChar): boolean;
|
|
begin
|
|
if (P1<>P2) then begin
|
|
if (P1<>nil) and (P2<>nil) then begin
|
|
while (P1^=P2^) do begin
|
|
if P1^<>#0 then begin
|
|
inc(P1);
|
|
inc(P2);
|
|
end else begin
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
Result:=false;
|
|
end else begin
|
|
Result:=true;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function FindChar(c: char; p:PChar; Max: integer): integer;
|
|
------------------------------------------------------------------------------}
|
|
function FindChar(c: char; p:PChar; Max: integer): integer;
|
|
begin
|
|
Result:=0;
|
|
while (Result<Max) do begin
|
|
if p[Result]<>c then
|
|
inc(Result)
|
|
else
|
|
exit;
|
|
end;
|
|
Result:=-1;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function FindLineLen(p: PChar; Max: integer): integer;
|
|
|
|
Find line end
|
|
------------------------------------------------------------------------------}
|
|
function FindLineLen(p: PChar; Max: integer): integer;
|
|
begin
|
|
Result:=0;
|
|
while (Result<Max) do begin
|
|
if not (p[Result] in [#10,#13]) then
|
|
inc(Result)
|
|
else
|
|
exit;
|
|
end;
|
|
Result:=-1;
|
|
end;
|
|
|
|
function RectFromGdkRect(AGdkRect: TGdkRectangle): TRect;
|
|
begin
|
|
with Result, AGdkRect do
|
|
begin
|
|
Left := x;
|
|
Top := y;
|
|
Right := Width + x;
|
|
Bottom := Height + y;
|
|
end;
|
|
end;
|
|
|
|
function GdkRectFromRect(R: TRect): TGdkRectangle;
|
|
begin
|
|
with Result, R do
|
|
begin
|
|
x := Left;
|
|
y := Top;
|
|
width := Right-Left;
|
|
height := Bottom-Top;
|
|
end;
|
|
end;
|
|
|
|
function AlignToGtkAlign(Align: TAlignment): gfloat;
|
|
begin
|
|
case Align of
|
|
taLeftJustify : AlignToGtkAlign := 0.0;
|
|
taCenter : AlignToGtkAlign := 0.5;
|
|
taRightJustify: AlignToGtkAlign := 1.0;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef gtk2}
|
|
function GtkScrollTypeToScrollCode(ScrollType: TGtkScrollType): LongWord;
|
|
begin
|
|
case ScrollType of
|
|
GTK_SCROLL_NONE : Result := SB_ENDSCROLL;
|
|
GTK_SCROLL_JUMP : Result := SB_THUMBPOSITION;
|
|
GTK_SCROLL_STEP_BACKWARD : Result := SB_LINELEFT;
|
|
GTK_SCROLL_STEP_FORWARD : Result := SB_LINERIGHT;
|
|
GTK_SCROLL_PAGE_BACKWARD : Result := SB_PAGELEFT;
|
|
GTK_SCROLL_PAGE_FORWARD : 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 : Result := SB_TOP;
|
|
GTK_SCROLL_END : Result := SB_BOTTOM;
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
{------------------------------------------------------------------------------
|
|
function GtkWidgetIsA(Widget: PGtkWidget; AType: TGtkType): boolean;
|
|
|
|
The GTK_IS_XXX macro functions in the fpc gtk1.x bindings are not correct.
|
|
They just test the highest level.
|
|
This function checks as the real C macros.
|
|
------------------------------------------------------------------------------}
|
|
function GtkWidgetIsA(Widget: PGtkWidget; AType: TGtkType): boolean;
|
|
begin
|
|
Result:=(Widget<>nil)
|
|
and (gtk_object_get_class(Widget)<>nil)
|
|
and gtk_type_is_a(gtk_class_get_type(gtk_object_get_class(Widget)), AType);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function GetWidgetClassName(Widget: PGtkWidget): string;
|
|
|
|
Returns the gtk class name of Widget.
|
|
------------------------------------------------------------------------------}
|
|
function GetWidgetClassName(Widget: PGtkWidget): string;
|
|
var
|
|
AType: TGtkType;
|
|
ClassPGChar: Pgchar;
|
|
ClassLen: Integer;
|
|
begin
|
|
Result:='';
|
|
if Widget=nil then begin
|
|
Result:='nil';
|
|
exit;
|
|
end;
|
|
if (gtk_object_get_class(Widget)=nil) then begin
|
|
Result:='<Widget without class>';
|
|
exit;
|
|
end;
|
|
AType:=gtk_class_get_type(gtk_object_get_class(Widget));
|
|
ClassPGChar:=gtk_type_name(AType);
|
|
if ClassPGChar=nil then begin
|
|
Result:='<Widget without classname>';
|
|
exit;
|
|
end;
|
|
ClassLen:=strlen(ClassPGChar);
|
|
SetLength(Result,ClassLen);
|
|
if ClassLen>0 then
|
|
Move(ClassPGChar[0],Result[1],ClassLen);
|
|
end;
|
|
|
|
function GetWidgetDebugReport(Widget: PGtkWidget): string;
|
|
var
|
|
LCLObject: TObject;
|
|
AWinControl: TWinControl;
|
|
MainWidget: PGtkWidget;
|
|
WinWidgetInfo: PWinWidgetInfo;
|
|
FixedWidget: PGTKWidget;
|
|
begin
|
|
if Widget = nil
|
|
then begin
|
|
Result := 'nil';
|
|
exit;
|
|
end;
|
|
Result := Format('%p=%s %s', [Pointer(Widget), GetWidgetClassName(Widget), WidgetFlagsToString(Widget)]);
|
|
LCLObject:=GetNearestLCLObject(Widget);
|
|
Result := Result + Format(' LCLObject=%p', [Pointer(LCLObject)]);
|
|
if LCLObject=nil then exit;
|
|
if LCLObject is TControl then
|
|
Result:=Result+'='+TControl(LCLObject).Name+':'+LCLObject.ClassName
|
|
else
|
|
Result:=Result+'='+LCLObject.ClassName;
|
|
if LCLObject is TWinControl then begin
|
|
AWinControl:=TWinControl(LCLObject);
|
|
if AWinControl.HandleAllocated then begin
|
|
MainWidget:=PGTKWidget(AWinControl.Handle);
|
|
if MainWidget=Widget
|
|
then Result:=Result+'<Is MainWidget>'
|
|
else Result:=Result+Format('<MainWidget=%p=%s>', [Pointer(MainWidget), GetWidgetClassName(MainWidget)]);
|
|
FixedWidget:=GetFixedWidget(MainWidget);
|
|
if FixedWidget=Widget then
|
|
Result:=Result+'<Is FixedWidget>';
|
|
WinWidgetInfo:=GetWidgetInfo(MainWidget,false);
|
|
if WinWidgetInfo<>nil then begin
|
|
if WinWidgetInfo^.CoreWidget = Widget then
|
|
Result:=Result+'<Is CoreWidget>';
|
|
end;
|
|
end
|
|
else begin
|
|
Result:=Result+'<NOT HandleAllocated>'
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function GetWindowDebugReport(AWindow: PGDKWindow): string;
|
|
var
|
|
p: gpointer;
|
|
Widget: PGtkWidget;
|
|
WindowType: TGdkWindowType;
|
|
Width: Integer;
|
|
Height: Integer;
|
|
{$ifdef gtk1}
|
|
Visual: PGdkVisual;
|
|
{$endif}
|
|
TypeAsStr: String;
|
|
begin
|
|
Result := DbgS(AWindow);
|
|
if AWindow = nil then Exit;
|
|
|
|
// window type
|
|
WindowType := gdk_window_get_type(AWindow);
|
|
case WindowType of
|
|
GDK_WINDOW_ROOT: TypeAsStr := 'Root';
|
|
GDK_WINDOW_TOPLEVEL: TypeAsStr := 'TopLvl';
|
|
GDK_WINDOW_CHILD: TypeAsStr := 'Child';
|
|
GDK_WINDOW_DIALOG: TypeAsStr := 'Dialog';
|
|
GDK_WINDOW_TEMP: TypeAsStr := 'Temp';
|
|
{$ifdef gtk1}
|
|
GDK_WINDOW_PIXMAP: TypeAsStr := 'Pixmap';
|
|
{$endif gtk1}
|
|
GDK_WINDOW_FOREIGN: TypeAsStr := 'Foreign';
|
|
else
|
|
TypeAsStr := 'Unknown';
|
|
end;
|
|
Result:=Result + ' Type=' + TypeAsStr;
|
|
|
|
DebugLn(Result);
|
|
// user data
|
|
if WindowType in [GDK_WINDOW_ROOT,GDK_WINDOW_TOPLEVEL,GDK_WINDOW_CHILD, GDK_WINDOW_DIALOG] then
|
|
begin
|
|
p := nil;
|
|
gdk_window_get_user_data(AWindow, @p);
|
|
if GtkWidgetIsA(PGTKWidget(p), gtk_widget_get_type) then
|
|
begin
|
|
Widget := PGTKWidget(p);
|
|
Result := Result + '<Widget[' + GetWidgetDebugReport(Widget) + ']>';
|
|
end
|
|
else
|
|
Result := Result + '<UserData=' + DbgS(p) + ']>';
|
|
end;
|
|
|
|
// size
|
|
gdk_window_get_size(AWindow, @Width, @Height);
|
|
Result := Result + ' Size=' + IntToStr(Width) + 'x' + IntToStr(Height);
|
|
|
|
{$ifdef gtk1}
|
|
// visual
|
|
Visual := gdk_window_get_visual(AWindow);
|
|
if Visual <> nil then
|
|
if WindowType in [GDK_WINDOW_PIXMAP] then
|
|
Result := Result + ' Depth=' + IntToStr(Visual^.bits_per_rgb);
|
|
{$endif gtk1}
|
|
end;
|
|
|
|
function GetStyleDebugReport(AStyle: PGTKStyle): string;
|
|
begin
|
|
Result:='[';
|
|
if AStyle=nil then
|
|
Result:=Result+'nil'
|
|
else begin
|
|
Result:=Result+'FG[N]:='+GdkColorToStr(@AStyle^.fg[GTK_STATE_NORMAL])+' ';
|
|
Result:=Result+'BG[N]:='+GdkColorToStr(@AStyle^.bg[GTK_STATE_NORMAL])+' ';
|
|
Result:=Result+'Base[N]:='+GdkColorToStr(@AStyle^.base[GTK_STATE_NORMAL])+' ';
|
|
Result:=Result+'BG_Pixmap[N]:='+DbgS(AStyle^.bg_pixmap[GTK_STATE_NORMAL])+' ';
|
|
Result:=Result+'rc_style='+GetRCStyleDebugReport(AStyle^.rc_style);
|
|
end;
|
|
Result:=Result+']';
|
|
end;
|
|
|
|
function GetRCStyleDebugReport(AStyle: PGtkRcStyle): string;
|
|
begin
|
|
Result:='[';
|
|
if AStyle=nil then
|
|
Result:=Result+'nil'
|
|
else begin
|
|
Result:=Result+'name="'+AStyle^.name+'" ';
|
|
{$IFDEF GTK1}
|
|
Result:=Result+'font_name="'+AStyle^.font_name+'" ';
|
|
Result:=Result+'fontset_name="'+AStyle^.fontset_name+'" ';
|
|
Result:=Result+'FG[N]='+GdkColorToStr(@AStyle^.fg[GTK_STATE_NORMAL])+' ';
|
|
Result:=Result+'BG[N]='+GdkColorToStr(@AStyle^.bg[GTK_STATE_NORMAL])+' ';
|
|
Result:=Result+'Base[N]='+GdkColorToStr(@AStyle^.base[GTK_STATE_NORMAL])+' ';
|
|
Result:=Result+'flagi='+intTostr(AStyle^.color_flags[GTK_STATE_NORMAL])+' ';
|
|
{$ELSE GTK2}
|
|
Result:=Result+'font_desc=['+GetPangoDescriptionReport(AStyle^.font_desc)+'] ';
|
|
{$ENDIF GTK2}
|
|
Result:=Result+'bg_pixmap_name[N]="'+AStyle^.bg_pixmap_name[GTK_STATE_NORMAL]+'" ';
|
|
{$IFDEF GTK1}
|
|
Result:=Result+'engine='+DbgS(AStyle^.engine);
|
|
{$ENDIF GTK1}
|
|
end;
|
|
Result:=Result+']';
|
|
end;
|
|
|
|
{$IFDEF Gtk2}
|
|
function GetPangoDescriptionReport(Desc: PPangoFontDescription): string;
|
|
begin
|
|
if Desc=nil then begin
|
|
Result:='nil';
|
|
end else begin
|
|
Result:='family='+pango_font_description_get_family(Desc);
|
|
Result:=Result+' size='+IntToStr(pango_font_description_get_size(Desc));
|
|
Result:=Result+' weight='+IntToStr(pango_font_description_get_weight(Desc));
|
|
Result:=Result+' variant='+IntToStr(pango_font_description_get_variant(Desc));
|
|
Result:=Result+' style='+IntToStr(pango_font_description_get_style(Desc));
|
|
Result:=Result+' stretch='+IntToStr(pango_font_description_get_stretch(Desc));
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function WidgetFlagsToString(Widget: PGtkWidget): string;
|
|
begin
|
|
Result:='[';
|
|
if Widget=nil then
|
|
Result:=Result+'nil'
|
|
else begin
|
|
if GTK_WIDGET_REALIZED(Widget) then
|
|
Result:=Result+'R';
|
|
if GTK_WIDGET_MAPPED(Widget) then
|
|
Result:=Result+'M';
|
|
if GTK_WIDGET_VISIBLE(Widget) then
|
|
Result:=Result+'V';
|
|
if GTK_WIDGET_DRAWABLE(Widget) then
|
|
Result:=Result+'D';
|
|
if GTK_WIDGET_CAN_FOCUS(Widget) then
|
|
Result:=Result+'F';
|
|
if GTK_WIDGET_RC_STYLE(Widget) then
|
|
Result:=Result+'St';
|
|
if GTK_WIDGET_PARENT_SENSITIVE(Widget) then
|
|
Result:=Result+'Pr';
|
|
{$IFDEF Gtk2}
|
|
if GTK_WIDGET_NO_WINDOW(Widget) then
|
|
Result:=Result+'Nw';
|
|
if GTK_WIDGET_COMPOSITE_CHILD(Widget) then
|
|
Result:=Result+'Cc';
|
|
if GTK_WIDGET_APP_PAINTABLE(Widget) then
|
|
Result:=Result+'Ap';
|
|
if GTK_WIDGET_DOUBLE_BUFFERED(Widget) then
|
|
Result:=Result+'Db';
|
|
{$ENDIF}
|
|
end;
|
|
Result:=Result+']';
|
|
end;
|
|
|
|
function GdkColorToStr(Color: PGDKColor): string;
|
|
begin
|
|
if Color=nil then
|
|
Result:='nil'
|
|
else
|
|
Result:='R'+HexStr(Color^.Red,4)+'G'+HexStr(Color^.Green,4)
|
|
+'B'+HexStr(Color^.Blue,4);
|
|
end;
|
|
|
|
function GetWidgetStyleReport(Widget: PGtkWidget): string;
|
|
var
|
|
AStyle: PGtkStyle;
|
|
ARCStyle: PGtkRcStyle;
|
|
begin
|
|
Result:='';
|
|
if Widget=nil then exit;
|
|
AStyle:=gtk_widget_get_style(Widget);
|
|
if AStyle=nil then begin
|
|
Result:='nil';
|
|
exit;
|
|
end;
|
|
Result:=Result+'attach_count='+dbgs(AStyle^.attach_count);
|
|
ARCStyle:=AStyle^.rc_style;
|
|
if ARCStyle=nil then begin
|
|
Result:=Result+' rc_style=nil';
|
|
end else begin
|
|
Result:=Result+' rc_style=[';
|
|
{$IFDEF GTK1}
|
|
Result:=Result+ARCStyle^.font_name+',';
|
|
Result:=Result+ARCStyle^.fontset_name+',';
|
|
{$ELSE GTK1}
|
|
Result:=Result+GetPangoDescriptionReport(AStyle^.font_desc);
|
|
{$ENDIF GTK1}
|
|
Result:=Result+']';
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function WidgetIsDestroyingHandle(Widget: PGtkWidget): boolean;
|
|
|
|
Tests if Destruction Mark is set.
|
|
------------------------------------------------------------------------------}
|
|
function WidgetIsDestroyingHandle(Widget: PGtkWidget): boolean;
|
|
begin
|
|
Result:=gtk_object_get_data(PGtkObject(Widget),'LCLDestroyingHandle')<>nil;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure SetWidgetIsDestroyingHandle(Widget: PGtkWidget);
|
|
|
|
Marks widget for destruction.
|
|
------------------------------------------------------------------------------}
|
|
procedure SetWidgetIsDestroyingHandle(Widget: PGtkWidget);
|
|
begin
|
|
gtk_object_set_data(PGtkObject(Widget),'LCLDestroyingHandle',Widget);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function ComponentIsDestroyingHandle(AWinControl: TWinControl): boolean;
|
|
|
|
Tests if Destruction Mark is set.
|
|
------------------------------------------------------------------------------}
|
|
function ComponentIsDestroyingHandle(AWinControl: TWinControl): boolean;
|
|
begin
|
|
Result:=
|
|
(AWinControl<>nil) and (AWinControl is TWinControl)
|
|
and (AWinControl.HandleAllocated)
|
|
and WidgetIsDestroyingHandle(PGtkWidget(AWinControl.Handle));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function LockOnChange(GtkObject: PGtkObject; LockOffset: integer): integer;
|
|
|
|
Adds LockOffset to the OnChangeLock and returns the result.
|
|
------------------------------------------------------------------------------}
|
|
function LockOnChange(GtkObject: PGtkObject; LockOffset: integer): integer;
|
|
var
|
|
Info: PWidgetInfo;
|
|
begin
|
|
Info := GetWidgetInfo(GtkObject, True);
|
|
if Info = nil
|
|
then begin
|
|
Result := 0;
|
|
Exit;
|
|
end;
|
|
|
|
Inc(Info^.ChangeLock, LockOffset);
|
|
Result := Info^.ChangeLock;
|
|
end;
|
|
|
|
procedure SetFormShowInTaskbar(AForm: TCustomForm;
|
|
const AValue: TShowInTaskbar);
|
|
var
|
|
Enable: boolean;
|
|
Widget: PGtkWidget;
|
|
begin
|
|
if (AForm.Parent <> nil) or
|
|
(AForm.ParentWindow <> 0) or
|
|
not (AForm.HandleAllocated) then Exit;
|
|
|
|
Widget := PGtkWidget(AForm.Handle);
|
|
// if widget not yet realized then exit
|
|
if Widget^.Window = nil then
|
|
Exit;
|
|
|
|
Enable := AValue <> stNever;
|
|
{if (AValue = stDefault)
|
|
and (Application<>nil) and (Application.MainForm <> nil)
|
|
and (Application.MainForm <> AForm) then
|
|
Enable := false;}
|
|
|
|
//debugln('SetGtkWindowShowInTaskbar ',DbgSName(AForm),' ',dbgs(Enable));
|
|
// The button reappears in some (still unknown) situations, but has the
|
|
//'skip-taskbar-hint' property still set to True, so invoking the function
|
|
//doesn't have an effect. Resetting the property makes it work.
|
|
{$IFNDEF GTK1}
|
|
if (not Enable) and gtk_window_get_skip_taskbar_hint(PGtkWindow(Widget)) then
|
|
gtk_window_set_skip_taskbar_hint(PGtkWindow(Widget), False);
|
|
{$ENDIF}
|
|
SetGtkWindowShowInTaskbar(PGtkWindow(Widget), Enable);
|
|
end;
|
|
|
|
procedure SetGtkWindowShowInTaskbar(AGtkWindow: PGtkWindow; Value: boolean);
|
|
begin
|
|
{$IFDEF GTK1}
|
|
if PgtkWidget(AGtkWindow)^.Window=nil then begin
|
|
// widget not yet realized
|
|
exit;
|
|
end;
|
|
GDK_WINDOW_SHOW_IN_TASKBAR(PGdkWindowPrivate(PGtkWidget(AGtkWindow)^.Window),
|
|
Value);
|
|
{$ELSE}
|
|
//DebugLn(['SetGtkWindowShowInTaskbar ',GetWidgetDebugReport(PGtkWidget(AGtkWindow)),' ',Value]);
|
|
gtk_window_set_skip_taskbar_hint(AGtkWindow, not Value);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure SetWindowFullScreen(AForm: TCustomForm; const AValue: Boolean);
|
|
{$IFDEF GTK1}
|
|
var
|
|
XDisplay: PDisplay;
|
|
XScreen: PScreen;
|
|
XRootWindow,
|
|
XWindow: TWindow;
|
|
XEvent: TXClientMessageEvent;
|
|
_NET_WM_STATE: Integer;
|
|
//_NET_WM_STATE_MODAL: Integer;
|
|
//_NET_WM_STATE_ABOVE: Integer;
|
|
//_NET_WM_STATE_FULLSCREEN: Integer;
|
|
_NET_WM_STATE_ATOMS: array [0..2] of Integer;
|
|
I: Integer;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF GTK2}
|
|
If AValue then
|
|
GTK_Window_FullScreen(PGTKWindow(AForm.Handle))
|
|
else
|
|
GTK_Window_UnFullScreen(PGTKWindow(AForm.Handle));
|
|
{$ENDIF}
|
|
{$IFDEF GTK1}
|
|
XDisplay := gdk_display;
|
|
XScreen := XDefaultScreenOfDisplay(xdisplay);
|
|
XRootWindow := XRootWindowOfScreen(xscreen);
|
|
XWindow := FormToX11Window(AForm);
|
|
|
|
_NET_WM_STATE := XInternAtom(xdisplay, '_NET_WM_STATE', false);
|
|
//_NET_WM_STATE_MODAL := XInternAtom(xdisplay, '_NET_WM_STATE_MODAL', false);
|
|
//_NET_WM_STATE_ABOVE := XInternAtom(xdisplay, '_NET_WM_STATE_ABOVE', false);
|
|
//_NET_WM_STATE_FULLSCREEN := XInternAtom(xdisplay, '_NET_WM_STATE_FULLSCREEN', false);
|
|
_NET_WM_STATE_ATOMS[0] := XInternAtom(xdisplay, '_NET_WM_STATE_MODAL', false);
|
|
_NET_WM_STATE_ATOMS[1] := XInternAtom(xdisplay, '_NET_WM_STATE_ABOVE', false);
|
|
_NET_WM_STATE_ATOMS[2] := XInternAtom(xdisplay, '_NET_WM_STATE_FULLSCREEN', false);
|
|
|
|
for I := 0 to 2 do begin
|
|
XEvent._type := ClientMessage;
|
|
XEvent.window := XWindow;
|
|
XEvent.message_type := _NET_WM_STATE;
|
|
XEvent.format := 32;
|
|
XEvent.data.l[0] := Ord(AValue);// 0=Remove 1=Add 2=Toggle
|
|
XEvent.data.l[1] := _NET_WM_STATE_ATOMS[I];
|
|
|
|
XSendEvent(XDisplay, XRootWindow, False, SubstructureNotifyMask, PXEvent(@XEvent));
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure GrabKeyBoardToForm(AForm: TCustomForm);
|
|
begin
|
|
{$IFDEF HasX}
|
|
XGrabKeyboard(gdk_display, FormToX11Window(AForm), true, GrabModeASync,
|
|
GrabModeASync, CurrentTime);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure ReleaseKeyBoardFromForm(AForm: TCustomForm);
|
|
begin
|
|
{$IFDEF HasX}
|
|
XUngrabKeyboard(gdk_display, CurrentTime);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure GrabMouseToForm(AForm: TCustomForm);
|
|
{$IFDEF HasX}
|
|
var
|
|
eventMask: LongInt;
|
|
begin
|
|
eventMask := ButtonPressMask or ButtonReleaseMask
|
|
or PointerMotionMask or PointerMotionHintMask;
|
|
|
|
XGrabPointer(gdk_display, FormToX11Window(AForm), true,
|
|
eventMask, GrabModeASync, GrabModeAsync, FormToX11Window(AForm),
|
|
None, CurrentTime);
|
|
end;
|
|
{$ELSE}
|
|
begin
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure ReleaseMouseFromForm(AForm: TCustomForm);
|
|
begin
|
|
{$IFDEF HasX}
|
|
XUngrabPointer(gdk_display, CurrentTime);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{$IFDEF HasX}
|
|
function FormToX11Window(const AForm: TCustomForm): X.TWindow;
|
|
var
|
|
Widget: PGtkWidget;
|
|
begin
|
|
Result:=0;
|
|
if (AForm=nil) or (not AForm.HandleAllocated) then exit;
|
|
Widget:=PGtkWidget(AForm.Handle);
|
|
if Widget^.window = nil then exit;
|
|
{$ifdef gtk1}
|
|
Result := PGdkWindowPrivate(Widget^.window)^.xwindow;
|
|
{$else}
|
|
Result := gdk_window_xwindow(Widget^.window);
|
|
{$endif}
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure SetLabelAlignment(LabelWidget: PGtkLabel;
|
|
const NewAlignment: TAlignment);
|
|
const
|
|
cLabelAlignX : array[TAlignment] of gfloat = (0.0, 1.0, 0.5);
|
|
cLabelAlignY : array[TTextLayout] of gfloat = (0.0, 0.5, 1.0);
|
|
cLabelAlign : array[TAlignment] of TGtkJustification =
|
|
(GTK_JUSTIFY_LEFT, GTK_JUSTIFY_RIGHT, GTK_JUSTIFY_CENTER);
|
|
begin
|
|
gtk_label_set_justify(LabelWidget, cLabelAlign[NewAlignment]);
|
|
gtk_misc_set_alignment(GTK_MISC(LabelWidget), cLabelAlignX[NewAlignment],
|
|
cLabelAlignY[tlTop]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function GtkPaintMessageToPaintMessage(var GtkPaintMsg: TLMGtkPaint;
|
|
FreeGtkPaintMsg: boolean): TLMPaint;
|
|
|
|
Converts a LM_GTKPAINT message to a LM_PAINT message
|
|
------------------------------------------------------------------------------}
|
|
function GtkPaintMessageToPaintMessage(var GtkPaintMsg: TLMGtkPaint;
|
|
FreeGtkPaintMsg: boolean): TLMPaint;
|
|
var
|
|
PS : PPaintStruct;
|
|
Widget: PGtkWidget;
|
|
begin
|
|
FillByte(Result,SizeOf(Result),0);
|
|
Result.Msg := LM_PAINT;
|
|
New(PS);
|
|
FillChar(PS^, SizeOf(TPaintStruct), 0);
|
|
Widget := GtkPaintMsg.Data.Widget;
|
|
If GtkPaintMsg.Data.RepaintAll then
|
|
PS^.rcPaint := Rect(0, 0, Widget^.Allocation.Width, Widget^.Allocation.Height)
|
|
else
|
|
PS^.rcPaint := GtkPaintMsg.Data.Rect;
|
|
|
|
Result.DC := BeginPaint(THandle(PtrUInt(Widget)), PS^);
|
|
Result.PaintStruct := PS;
|
|
Result.Result := 0;
|
|
if FreeGtkPaintMsg then
|
|
FreeThenNil(GtkPaintMsg.Data);
|
|
end;
|
|
|
|
procedure FinalizePaintMessage(Msg: PLMessage);
|
|
var
|
|
PS: PPaintStruct;
|
|
DC: TGtkDeviceContext;
|
|
begin
|
|
if (Msg^.Msg = LM_PAINT) then
|
|
begin
|
|
if Msg^.LParam <> 0 then
|
|
begin
|
|
PS := PPaintStruct(Msg^.LParam);
|
|
if Msg^.WParam <> 0 then
|
|
DC := TGtkDeviceContext(Msg^.WParam)
|
|
else
|
|
DC := TGtkDeviceContext(PS^.hdc);
|
|
EndPaint(THandle(PtrUInt(DC.Widget)), PS^);
|
|
Dispose(PS);
|
|
Msg^.LParam:=0;
|
|
Msg^.WParam:=0;
|
|
end
|
|
else
|
|
if Msg^.WParam<>0 then
|
|
begin
|
|
ReleaseDC(0, Msg^.WParam);
|
|
Msg^.WParam := 0;
|
|
end;
|
|
end else
|
|
if Msg^.Msg = LM_GTKPAINT then
|
|
FreeThenNil(TLMGtkPaintData(Msg^.WParam));
|
|
end;
|
|
|
|
procedure FinalizePaintTagMsg(Msg: PMsg);
|
|
var
|
|
PS: PPaintStruct;
|
|
DC: TGtkDeviceContext;
|
|
begin
|
|
if (Msg^.Message = LM_PAINT) then
|
|
begin
|
|
if Msg^.LParam <> 0 then
|
|
begin
|
|
PS := PPaintStruct(Msg^.LParam);
|
|
if Msg^.WParam<>0 then
|
|
DC := TGtkDeviceContext(Msg^.WParam)
|
|
else
|
|
DC := TGtkDeviceContext(PS^.hdc);
|
|
EndPaint(THandle(PtrUInt(DC.Widget)), PS^);
|
|
Dispose(PS);
|
|
Msg^.LParam:=0;
|
|
Msg^.WParam:=0;
|
|
end else
|
|
if Msg^.WParam<>0 then
|
|
begin
|
|
ReleaseDC(0, Msg^.WParam);
|
|
Msg^.WParam:=0;
|
|
end;
|
|
end else
|
|
if Msg^.Message = LM_GTKPAINT then
|
|
FreeThenNil(TObject(Msg^.WParam));
|
|
end;
|
|
|
|
procedure SetGCRasterOperation(TheGC: PGDKGC; Rop: Cardinal);
|
|
begin
|
|
case ROP of
|
|
WHITENESS,
|
|
BLACKNESS,
|
|
SRCCOPY :
|
|
gdk_gc_set_function(TheGC, GDK_Copy);
|
|
SRCPAINT :
|
|
gdk_gc_set_function(TheGC, GDK_NOOP);
|
|
SRCAND :
|
|
gdk_gc_set_function(TheGC, GDK_Clear);
|
|
SRCINVERT :
|
|
gdk_gc_set_function(TheGC, GDK_XOR);
|
|
SRCERASE :
|
|
gdk_gc_set_function(TheGC, GDK_AND);
|
|
NOTSRCCOPY :
|
|
gdk_gc_set_function(TheGC, GDK_OR_REVERSE);
|
|
NOTSRCERASE :
|
|
gdk_gc_set_function(TheGC, GDK_AND);
|
|
MERGEPAINT :
|
|
gdk_gc_set_function(TheGC, GDK_Copy_Invert);
|
|
DSTINVERT :
|
|
gdk_gc_set_function(TheGC, GDK_INVERT);
|
|
else begin
|
|
gdk_gc_set_function(TheGC, GDK_COPY);
|
|
DebugLn('WARNING: [SetRasterOperation] Got unknown/unsupported CopyMode!!');
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure MergeClipping(DestinationDC: TGtkDeviceContext; DestinationGC: PGDKGC;
|
|
X,Y,Width,Height: integer; ClipMergeMask: PGdkBitmap;
|
|
ClipMergeMaskX, ClipMergeMaskY: integer;
|
|
var NewClipMask: PGdkBitmap);
|
|
// merge ClipMergeMask into the destination clipping mask at the
|
|
// destination rectangle
|
|
var
|
|
temp_gc : PGDKGC;
|
|
temp_color : TGDKColor;
|
|
RGNType : Longint;
|
|
OffsetXY: TPoint;
|
|
//ClipMergeMaskWidth, ClipMergeMaskHeight: integer;
|
|
begin
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('MergeClipping START DestinationDC=',DbgS(DestinationDC),
|
|
' DestinationGC=',DbgS(DestinationGC),
|
|
' X='+dbgs(X),' Y='+dbgs(Y),' Width='+dbgs(Width),' Height='+dbgs(Height),
|
|
' ClipMergeMask=',DbgS(ClipMergeMask),
|
|
' ClipMergeMaskX=',dbgs(ClipMergeMaskX),' ClipMergeMaskY=',dbgs(ClipMergeMaskY));
|
|
{$ENDIF}
|
|
|
|
// activate clipping region of destination
|
|
DestinationDC.SelectRegion;
|
|
NewClipMask := nil;
|
|
if (ClipMergeMask = nil) then exit;
|
|
|
|
BeginGDKErrorTrap;
|
|
// create temporary mask with the size of the destination rectangle
|
|
NewClipMask := PGdkBitmap(gdk_pixmap_new(nil, width, height, 1));
|
|
// create temporary GC for combination mask
|
|
temp_gc := gdk_gc_new(NewClipMask);
|
|
gdk_gc_set_clip_region(temp_gc, nil); // no default clipping
|
|
gdk_gc_set_clip_rectangle(temp_gc, nil);
|
|
|
|
// clear mask
|
|
temp_color.pixel := 0;
|
|
gdk_gc_set_foreground(temp_gc, @temp_color);
|
|
gdk_draw_rectangle(NewClipMask, temp_gc, 1, 0, 0, width+1, height+1);
|
|
|
|
// copy the destination clipping mask into the temporary mask
|
|
with DestinationDC do begin
|
|
If (ClipRegion <> nil) then begin
|
|
RGNType := RegionType(ClipRegion^.GDIRegionObject);
|
|
If (RGNType <> ERROR) and (RGNType <> NULLREGION) then begin
|
|
// destination has a clipping mask
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('MergeClipping Destination has clipping mask -> apply to temp GC');
|
|
{$ENDIF}
|
|
// -> copy the destination clipping mask to the temporary mask
|
|
// The X,Y coordinate in the destination relates to
|
|
// 0,0 in the temporary mask.
|
|
// The clip region of dest is always at 0,0 in dest
|
|
OffsetXY:=Point(-X,-Y);
|
|
// 1. Move the region
|
|
gdk_region_offset(ClipRegion^.GDIRegionObject,OffsetXY.X,OffsetXY.Y);
|
|
// 2. Apply region to temporary mask
|
|
gdk_gc_set_clip_region(temp_gc, ClipRegion^.GDIRegionObject);
|
|
// 3. Undo moving the region
|
|
gdk_region_offset(ClipRegion^.GDIRegionObject,-OffsetXY.X,-OffsetXY.Y);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// merge the source clipping mask into the temporary mask
|
|
//gdk_window_get_size(ClipMergeMask,@ClipMergeMaskWidth,@ClipMergeMaskHeight);
|
|
//DebugLn('MergeClipping A MergeMask Size=',ClipMergeMaskWidth,',',ClipMergeMaskHeight);
|
|
gdk_draw_pixmap(NewClipMask, temp_gc,
|
|
ClipMergeMask, ClipMergeMaskX, ClipMergeMaskY, 0, 0, -1, -1);
|
|
|
|
// free the temporary GC
|
|
gdk_gc_destroy(temp_gc);
|
|
|
|
// apply the new mask to the destination GC
|
|
// The new mask has only the size of the destination rectangle, not of
|
|
// the whole destination. Apply it to destination and move it to the right
|
|
// position.
|
|
gdk_gc_set_clip_mask(DestinationGC, NewClipMask);
|
|
gdk_gc_set_clip_origin(DestinationGC, x, y);
|
|
EndGDKErrorTrap;
|
|
end;
|
|
|
|
function CreatePixbufFromImageAndMask(ASrc: PGdkDrawable; ASrcX, ASrcY, ASrcWidth,
|
|
ASrcHeight: integer; ASrcColorMap: PGdkColormap; ASrcMask: PGdkBitmap): PGdkPixbuf;
|
|
|
|
procedure Warn(const AText: String);
|
|
begin
|
|
DebugLn('[WARNING] ScalePixmapAndMask: ' + AText);
|
|
end;
|
|
|
|
procedure ApplyMask(APixels, AMask: pguchar);
|
|
type
|
|
TPixbufPixel = record
|
|
R,G,B,A: Byte;
|
|
end;
|
|
var
|
|
RGBA: ^TPixbufPixel absolute APixels;
|
|
Mask: ^TPixbufPixel absolute AMask;
|
|
n: Integer;
|
|
begin
|
|
for n := 0 to (ASrcHeight * ASrcWidth) - 1 do
|
|
begin
|
|
if (Mask^.B = 0) and (Mask^.G = 0) and (Mask^.R = 0)
|
|
then RGBA^.A := 0;
|
|
inc(RGBA);
|
|
inc(Mask);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
Msk: PGdkPixbuf;
|
|
FullSrcWidth, FullSrcHeight: integer;
|
|
begin
|
|
Result := nil;
|
|
if ASrc = nil then Exit;
|
|
|
|
gdk_window_get_size(PGDKWindow(ASrc), @FullSrcWidth, @FullSrcHeight);
|
|
if ASrcX + ASrcWidth > FullSrcWidth
|
|
then begin
|
|
Warn('ASrcX+ASrcWidth>FullSrcWidth');
|
|
end;
|
|
if ASrcY + ASrcHeight > FullSrcHeight
|
|
then begin
|
|
Warn('ASrcY+ASrcHeight>FullSrcHeight');
|
|
end;
|
|
|
|
// Creating PixBuf from pixmap
|
|
Result := CreatePixbufFromDrawable(ASrc, ASrcColorMap, ASrcMask <> nil, ASrcX, ASrcY, 0, 0, ASrcWidth, ASrcHeight);
|
|
if Result = nil
|
|
then begin
|
|
Warn('Result=nil');
|
|
Exit;
|
|
end;
|
|
//DbgDumpPixbuf(Result, 'Pixbuf from Source');
|
|
|
|
// Apply mask if present
|
|
if ASrcMask <> nil
|
|
then begin
|
|
if gdk_pixbuf_get_rowstride(Result) <> ASrcWidth shl 2
|
|
then begin
|
|
Warn('rowstride <> 4*width');
|
|
gdk_pixbuf_unref(Result);
|
|
Result := nil;
|
|
Exit;
|
|
end;
|
|
|
|
Msk := CreatePixbufFromDrawable(ASrcMask, nil, True, ASrcX, ASrcY, 0, 0, ASrcWidth, ASrcHeight);
|
|
ApplyMask(gdk_pixbuf_get_pixels(Result), gdk_pixbuf_get_pixels(Msk));
|
|
gdk_pixbuf_unref(Msk);
|
|
end;
|
|
end;
|
|
|
|
function ScalePixmapAndMask(AScaleGC: PGDKGC; AScaleMethod: TGdkInterpType;
|
|
ASrc: PGdkPixmap; ASrcX, ASrcY, ASrcWidth, ASrcHeight: integer;
|
|
ASrcColorMap: PGdkColormap; ASrcMask: PGdkBitmap;
|
|
ADstWidth, ADstHeight: Integer; FlipHorz, FlipVert: Boolean;
|
|
out ADst, ADstMask: PGdkPixmap) : Boolean;
|
|
|
|
procedure Warn(const AText: String);
|
|
begin
|
|
DebugLn('[WARNING] ScalePixmapAndMask: ' + AText);
|
|
end;
|
|
|
|
var
|
|
ScaleSrc, ScaleDst: PGdkPixbuf;
|
|
begin
|
|
Result := False;
|
|
ADst:=nil;
|
|
ADstMask:=nil;
|
|
|
|
// Creating PixBuf from pixmap
|
|
ScaleSrc := CreatePixbufFromImageAndMask(ASrc, ASrcX, ASrcY, ASrcWidth, ASrcHeight,
|
|
ASrcColorMap, ASrcMask);
|
|
|
|
// Scaling PixBuf
|
|
ScaleDst := gdk_pixbuf_scale_simple(ScaleSrc, ADstWidth, ADstHeight, AScaleMethod);
|
|
gdk_pixbuf_unref(ScaleSrc);
|
|
if ScaleDst = nil
|
|
then begin
|
|
Warn('ScaleDst=nil');
|
|
exit;
|
|
end;
|
|
|
|
// flip if needed
|
|
if FlipHorz then
|
|
begin
|
|
{$IFNDEF GTK1}
|
|
ScaleSrc := ScaleDst;
|
|
ScaleDst := gdk_pixbuf_flip(ScaleSrc, True);
|
|
gdk_pixbuf_unref(ScaleSrc);
|
|
if ScaleDst = nil
|
|
then begin
|
|
Warn('ScaleDst=nil');
|
|
exit;
|
|
end;
|
|
{$ELSE}
|
|
// TODO: implement flipping for gtk1
|
|
{$ENDIF}
|
|
end;
|
|
|
|
if FlipVert then
|
|
begin
|
|
{$IFNDEF GTK1}
|
|
ScaleSrc := ScaleDst;
|
|
ScaleDst := gdk_pixbuf_flip(ScaleSrc, False);
|
|
gdk_pixbuf_unref(ScaleSrc);
|
|
if ScaleDst = nil
|
|
then begin
|
|
Warn('ScaleDst=nil');
|
|
exit;
|
|
end;
|
|
{$ELSE}
|
|
// TODO: implement flipping for gtk1
|
|
{$ENDIF}
|
|
end;
|
|
|
|
// BeginGDKErrorTrap;
|
|
|
|
// Creating pixmap from scaled pixbuf
|
|
gdk_pixbuf_render_pixmap_and_mask(ScaleDst, ADst, ADstMask, $80);
|
|
|
|
// EndGDKErrorTrap;
|
|
gdk_pixbuf_unref(ScaleDst);
|
|
Result := True;
|
|
end;
|
|
|
|
{$IFDEF VerboseGtkToDos}{$note remove when gtk native imagelist will be ready}{$ENDIF}
|
|
procedure DrawImageListIconOnWidget(ImgList: TCustomImageList;
|
|
Index: integer; AEffect: TGraphicsDrawEffect; DestWidget: PGTKWidget;
|
|
CenterHorizontally, CenterVertically: boolean;
|
|
DestLeft, DestTop: integer);
|
|
// draw icon of imagelist centered on gdkwindow
|
|
var
|
|
Bitmap: TBitmap;
|
|
ImageWidth: Integer;
|
|
ImageHeight: Integer;
|
|
WindowWidth, WindowHeight: integer;
|
|
DestDC: HDC;
|
|
Offset: TPoint;
|
|
{$ifdef gtk2}
|
|
FixedWidget: PGtkWidget;
|
|
{$ENDIF}
|
|
begin
|
|
if ImgList=nil then exit;
|
|
if (Index<0) or (Index>=ImgList.Count) then exit;
|
|
if (DestWidget=nil) then exit;
|
|
ImageWidth:=ImgList.Width;
|
|
ImageHeight:=ImgList.Height;
|
|
Bitmap := TBitmap.Create;
|
|
ImgList.GetBitmap(Index, Bitmap, AEffect);
|
|
if (ImageWidth<1) or (ImageHeight<1) then exit;
|
|
|
|
WindowWidth := DestWidget^.allocation.width;
|
|
WindowHeight := DestWidget^.allocation.height;
|
|
|
|
Offset := Point(0, 0);
|
|
{$ifdef gtk2}
|
|
// if our widget is placed on non-window fixed then we should substract its allocation here
|
|
// since in GetDC we will get this difference in offset
|
|
FixedWidget := GetFixedWidget(DestWidget);
|
|
if (FixedWidget <> nil) and GTK_WIDGET_NO_WINDOW(FixedWidget) then
|
|
Offset := Point(FixedWidget^.allocation.x, FixedWidget^.allocation.y);
|
|
{$endif}
|
|
|
|
if CenterHorizontally then
|
|
DestLeft := DestWidget^.allocation.x - Offset.x + ((WindowWidth-ImageWidth) div 2);
|
|
if CenterVertically then
|
|
DestTop := DestWidget^.allocation.y - Offset.y + ((WindowHeight-ImageHeight) div 2);
|
|
DestDC := GetDC(HDC(PtrUInt(DestWidget)));
|
|
|
|
//DebugLn('DrawImageListIconOnWidget B DestXY=',DestLeft,',',DestTop,
|
|
// ' DestWindowSize=',WindowWidth,',',WindowWidth,
|
|
// ' SrcRect=',ImageRect.Left,',',ImageRect.Top,',',ImageWidth,'x',ImageHeight);
|
|
StretchBlt(DestDC, DestLeft, DestTop, ImageWidth, ImageHeight,
|
|
Bitmap.Canvas.Handle, 0, 0, ImageWidth, ImageHeight,
|
|
SRCCOPY);
|
|
ReleaseDC(HDC(PtrUInt(DestWidget)),DestDC);
|
|
Bitmap.Free;
|
|
end;
|
|
|
|
procedure DrawImageListIconOnWidget(ImgList: TCustomImageList;
|
|
Index: integer; DestWidget: PGTKWidget);
|
|
begin
|
|
DrawImageListIconOnWidget(ImgList, Index, gdeNormal, DestWidget, true, true, 0, 0);
|
|
end;
|
|
|
|
function GetGdkImageBitsPerPixel(Image: PGdkImage): cardinal;
|
|
begin
|
|
Result:=Image^.bpp;
|
|
if Result<Image^.Depth then
|
|
Result:=Result*8;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: CreateGtkBitmapMask
|
|
Params: AImageMask: Then internal gtkBitmap for imagemask
|
|
AMask: External gtkbitmap
|
|
Returns: A GdkBitmap
|
|
|
|
This function returns a bitmap based on the internal alpha bitmap and the
|
|
maskhandle passed.
|
|
If both internal mask and the given mask is valid, then a new bitmap is created
|
|
else either internal mask or given mask (with increased reference)
|
|
------------------------------------------------------------------------------}
|
|
function CreateGdkMaskBitmap(AImageMask, AMask: PGdkBitmap): PGdkBitmap;
|
|
var
|
|
W, H: Integer;
|
|
GC: PGdkGc;
|
|
begin
|
|
Result := nil;
|
|
if (AImageMask = nil) and (AMask = nil) then Exit;
|
|
|
|
if AMask = nil
|
|
then begin
|
|
Result := AImageMask;
|
|
gdk_pixmap_ref(Result);
|
|
Exit;
|
|
end;
|
|
|
|
if AImageMask = nil
|
|
then begin
|
|
Result := AMask;
|
|
gdk_pixmap_ref(Result);
|
|
Exit;
|
|
end;
|
|
|
|
// if we are here we need a combination (=AND) of both masks
|
|
gdk_window_get_size(AImageMask, @W, @H);
|
|
Result := gdk_pixmap_new(nil, W, H, 1);
|
|
GC := gdk_gc_new(Result);
|
|
// copy image mask
|
|
gdk_draw_pixmap(Result, GC, AImageMask, 0, 0, 0, 0, -1, -1);
|
|
// and with mask
|
|
gdk_gc_set_function(GC, GDK_AND);
|
|
gdk_draw_pixmap(Result, GC, AMask, 0, 0, 0, 0, -1, -1);
|
|
gdk_gc_unref(GC);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: CreateGdkMaskBitmap
|
|
Params: AImage: Handle to the (LCL) bitmap image
|
|
AMask: Handle to the (LCL) bitmap mask
|
|
Returns: A GdkBitmap
|
|
|
|
This function returns a bitmap based on the internal alpha bitmap of the
|
|
image handle and the maskhandle passed.
|
|
If only internal mask is valid, then that one is returned (with increased reference)
|
|
Otherwise a new bitmap is created.
|
|
------------------------------------------------------------------------------}
|
|
function CreateGdkMaskBitmap(AImage, AMask: HBITMAP): PGdkBitmap;
|
|
var
|
|
GdiImage: PGdiObject absolute AImage;
|
|
GdiMask: PGdiObject absolute AMask;
|
|
W, H: Integer;
|
|
GC: PGdkGc;
|
|
begin
|
|
Result := nil;
|
|
if (AImage = 0) and (AMask = 0) then Exit;
|
|
|
|
if GdiMask = nil
|
|
then begin
|
|
if GdiImage^.GDIBitmapType = gbPixmap
|
|
then Result := GdiImage^.GDIPixmapObject.Mask;
|
|
if Result <> nil
|
|
then gdk_pixmap_ref(Result);
|
|
// DbgDumpBitmap(Result, 'CreateGdkMaskBitmap - Internal mask');
|
|
Exit;
|
|
end;
|
|
|
|
if GdiMask^.GDIBitmapType <> gbBitmap
|
|
then begin
|
|
DebugLN('[WARNING] CreateGtkBitmapMask: GDIBitmapType <> dbBitmap');
|
|
Exit;
|
|
end;
|
|
|
|
if (GdiImage = nil)
|
|
or (GdiImage^.GDIBitmapType <> gbPixmap)
|
|
or (GdiImage^.GDIPixmapObject.Mask = nil)
|
|
then begin
|
|
gdk_window_get_size(GdiMask^.GDIBitmapObject, @W, @H);
|
|
Result := gdk_pixmap_new(nil, W, H, 1);
|
|
GC := gdk_gc_new(Result);
|
|
gdk_gc_set_function(GC, {$ifdef gtk1}11{$else}GDK_COPY_INVERT{$endif});
|
|
gdk_draw_pixmap(Result, GC, GdiMask^.GDIBitmapObject, 0, 0, 0, 0, -1, -1);
|
|
gdk_gc_unref(GC);
|
|
|
|
//DbgDumpBitmap(Result, 'CreateGdkMaskBitmap - Mask');
|
|
Exit;
|
|
end;
|
|
|
|
// if we are here we need a combination (=AND) of both masks
|
|
gdk_window_get_size(GdiImage^.GDIPixmapObject.Mask, @W, @H);
|
|
Result := gdk_pixmap_new(nil, W, H, 1);
|
|
GC := gdk_gc_new(Result);
|
|
// copy image mask
|
|
gdk_draw_pixmap(Result, GC, GdiImage^.GDIPixmapObject.Mask, 0, 0, 0, 0, -1, -1);
|
|
// and with mask
|
|
gdk_gc_set_function(GC, {$ifdef gtk1}6{$else}GDK_AND_INVERT{$endif});
|
|
gdk_draw_pixmap(Result, GC, GdiMask^.GDIBitmapObject, 0, 0, 0, 0, -1, -1);
|
|
gdk_gc_unref(GC);
|
|
|
|
// DbgDumpBitmap(Result, 'CreateGdkMaskBitmap - Combi');
|
|
end;
|
|
|
|
function ExtractGdkBitmap(Bitmap: PGdkBitmap; const SrcRect: TRect): PGdkBitmap;
|
|
var
|
|
MaxRect: TRect;
|
|
SourceRect: TRect;
|
|
SrcWidth: Integer;
|
|
SrcHeight: Integer;
|
|
GC: PGdkGC;
|
|
begin
|
|
Result:=nil;
|
|
if Bitmap=nil then exit;
|
|
MaxRect:=Rect(0,0,0,0);
|
|
gdk_window_get_size(Bitmap,@MaxRect.Right,@MaxRect.Bottom);
|
|
IntersectRect(SourceRect,SrcRect,MaxRect);
|
|
SrcWidth:=SourceRect.Right-SourceRect.Left;
|
|
SrcHeight:=SourceRect.Bottom-SourceRect.Top;
|
|
DebugLn('ExtractGdkBitmap SourceRect=',dbgs(SourceRect));
|
|
if (SrcWidth<1) or (SrcHeight<1) then exit;
|
|
Result:= gdk_pixmap_new(nil, SrcWidth, SrcHeight, 1);
|
|
GC := GDK_GC_New(Result);
|
|
gdk_window_copy_area(Result,GC,0,0,Bitmap,
|
|
SourceRect.Left,SourceRect.Top,SrcWidth,SrcHeight);
|
|
GDK_GC_Unref(GC);
|
|
end;
|
|
|
|
procedure CheckGdkImageBitOrder(AImage: PGdkImage; AData: PByte; ADataCount: Integer);
|
|
var
|
|
b, count: Byte;
|
|
c: Cardinal;
|
|
|
|
{$ifdef hasx}
|
|
XImage: XLib.PXimage;
|
|
{$endif}
|
|
begin
|
|
{$ifdef hasx}
|
|
if AImage = nil then Exit;
|
|
|
|
XImage := gdk_x11_image_get_ximage(AImage);
|
|
if XImage^.bitmap_bit_order = LSBFirst then Exit;
|
|
{$endif}
|
|
|
|
// on windows or bigendian servers the bits need to be swapped
|
|
|
|
// align dataptr first
|
|
count := PtrUint(AData) and 3;
|
|
if count > ADataCount then count := ADataCount;
|
|
Dec(ADataCount, Count);
|
|
|
|
while (Count > 0) do
|
|
begin
|
|
// reduce dereferences
|
|
b := AData^;
|
|
b := ((b shr 4) and $0F) or ((b shl 4) and $F0);
|
|
b := ((b shr 2) and $33) or ((b shl 2) and $CC);
|
|
AData^ := ((b shr 1) and $55) or ((b shl 1) and $AA);
|
|
|
|
Dec(Count);
|
|
Inc(AData);
|
|
end;
|
|
|
|
// get remainder
|
|
Count := ADataCount and 3;
|
|
|
|
// now swap bits with 4 in a row
|
|
ADataCount := ADataCount shr 2;
|
|
while (ADataCount > 0) do
|
|
begin
|
|
// reduce dereferences
|
|
c := PCardinal(AData)^;
|
|
c := ((c shr 4) and $0F0F0F0F) or ((c shl 4) and $F0F0F0F0);
|
|
c := ((c shr 2) and $33333333) or ((c shl 2) and $CCCCCCCC);
|
|
PCardinal(AData)^ := ((c shr 1) and $55555555) or ((c shl 1) and $AAAAAAAA);
|
|
|
|
Dec(ADataCount);
|
|
Inc(AData, 4);
|
|
end;
|
|
|
|
// process remainder
|
|
while (Count > 0) do
|
|
begin
|
|
// reduce dereferences
|
|
b := AData^;
|
|
b := ((b shr 4) and $0F) or ((b shl 4) and $F0);
|
|
b := ((b shr 2) and $33) or ((b shl 2) and $CC);
|
|
AData^ := ((b shr 1) and $55) or ((b shl 1) and $AA);
|
|
|
|
Dec(Count);
|
|
Inc(AData);
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: AllocGDKColor
|
|
Params: AColor: A RGB color (TColor)
|
|
Returns: an Allocated GDKColor
|
|
|
|
Allocated a GDKColor from a winapi color
|
|
------------------------------------------------------------------------------}
|
|
function AllocGDKColor(const AColor: LongInt): TGDKColor;
|
|
begin
|
|
with Result do
|
|
begin
|
|
Red := ((AColor shl 8) and $00FF00) or ((AColor ) and $0000FF);
|
|
Green := ((AColor ) and $00FF00) or ((AColor shr 8 ) and $0000FF);
|
|
Blue := ((AColor shr 8) and $00FF00) or ((AColor shr 16) and $0000FF);
|
|
end;
|
|
{$IFDEF DebugGDK}
|
|
BeginGDKErrorTrap;
|
|
{$ENDIF}
|
|
gdk_colormap_alloc_color(gdk_colormap_get_system, @Result, False, True);
|
|
{$IFDEF DebugGDK}
|
|
EndGDKErrorTrap;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
|
|
function RegionType(RGN: PGDKRegion) : Longint;
|
|
var
|
|
aRect : TGDKRectangle;
|
|
SimpleRGN: PGdkRegion;
|
|
begin
|
|
{$IFDEF DebugGDK}
|
|
BeginGDKErrorTrap;
|
|
{$ENDIF}
|
|
If RGN = nil then
|
|
Result := ERROR
|
|
else
|
|
If gdk_region_empty(RGN) then
|
|
Result := NULLREGION
|
|
else begin
|
|
gdk_region_get_clipbox(RGN,@aRect);
|
|
SimpleRGN := gdk_region_rectangle(@aRect);
|
|
if gdk_region_equal(SimpleRGN, RGN) then
|
|
Result := SIMPLEREGION
|
|
else
|
|
Result := COMPLEXREGION;
|
|
gdk_region_destroy(SimpleRGN);
|
|
end;
|
|
{$IFDEF DebugGDK}
|
|
EndGDKErrorTrap;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
|
|
function GDKRegionAsString(RGN: PGDKRegion): string;
|
|
var
|
|
aRect: TGDKRectangle;
|
|
begin
|
|
Result:=DbgS(RGN);
|
|
BeginGDKErrorTrap;
|
|
gdk_region_get_clipbox(RGN,@aRect);
|
|
EndGDKErrorTrap;
|
|
Result:=Result+'(x='+IntToStr(Integer(aRect.x))+',y='+IntToStr(Integer(aRect.y))+',w='
|
|
+IntToStr(aRect.Width)+',h='+IntToStr(aRect.Height)+' '
|
|
+'Type='+IntToStr(RegionType(RGN))+')';
|
|
end;
|
|
|
|
function CreateRectGDKRegion(const ARect: TRect): PGDKRegion;
|
|
var
|
|
GDkRect: TGDKRectangle;
|
|
begin
|
|
GDkRect.x:=ARect.Left;
|
|
GDkRect.y:=ARect.Top;
|
|
GDkRect.Width:=ARect.Right-ARect.Left;
|
|
GDkRect.Height:=ARect.Bottom-ARect.Top;
|
|
{$IFDEF DebugGDK}
|
|
BeginGDKErrorTrap;
|
|
{$ENDIF}
|
|
Result:=gdk_region_rectangle(@GDKRect);
|
|
{$IFDEF DebugGDK}
|
|
EndGDKErrorTrap;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
Procedure FreeGDIColor(GDIColor: PGDIColor);
|
|
begin
|
|
if (cfColorAllocated in GDIColor^.ColorFlags) then begin
|
|
if (GDIColor^.Colormap <> nil) then begin
|
|
BeginGDKErrorTrap;
|
|
gdk_colormap_free_colors(GDIColor^.Colormap,@(GDIColor^.Color), 1);
|
|
EndGDKErrorTrap;
|
|
end;
|
|
//GDIColor.Color.Pixel := -1;
|
|
Exclude(GDIColor^.ColorFlags,cfColorAllocated);
|
|
end;
|
|
end;
|
|
|
|
procedure SetGDIColorRef(var GDIColor: TGDIColor; NewColorRef: TColorRef);
|
|
begin
|
|
if GDIColor.ColorRef=NewColorRef then exit;
|
|
FreeGDIColor(@GDIColor);
|
|
GDIColor.ColorRef:=NewColorRef;
|
|
end;
|
|
|
|
Procedure AllocGDIColor(DC: hDC; GDIColor: PGDIColor);
|
|
var
|
|
RGBColor : Longint;
|
|
begin
|
|
if DC=0 then ;
|
|
if not (cfColorAllocated in GDIColor^.ColorFlags) then begin
|
|
RGBColor := ColorToRGB(GDIColor^.ColorRef);
|
|
|
|
With GDIColor^.Color do begin
|
|
Red := gushort(GetRValue(RGBColor)) shl 8;
|
|
Green := gushort(GetGValue(RGBColor)) shl 8;
|
|
Blue := gushort(GetBValue(RGBColor)) shl 8;
|
|
Pixel := 0;
|
|
end;
|
|
|
|
{with TGtkDeviceContext(DC) do
|
|
If CurrentPalette <> nil then
|
|
GDIColor.Colormap := CurrentPalette^.PaletteColormap
|
|
else}
|
|
GDIColor^.Colormap := GDK_Colormap_get_system;
|
|
|
|
gdk_colormap_alloc_color(GDIColor^.Colormap, @(GDIColor^.Color),True,True);
|
|
|
|
Include(GDIColor^.ColorFlags,cfColorAllocated);
|
|
end;
|
|
end;
|
|
|
|
procedure BuildColorRefFromGDKColor(var GDIColor: TGDIColor);
|
|
begin
|
|
GDIColor.ColorRef:=TGDKColorToTColor(GDIColor.Color);
|
|
Include(GDIColor.ColorFlags,cfColorAllocated);
|
|
end;
|
|
|
|
procedure EnsureGCColor(DC: hDC; ColorType: TDevContextsColorType;
|
|
IsSolidBrush, AsBackground: Boolean);
|
|
var
|
|
GC: PGDKGC;
|
|
GDIColor: PGDIColor;
|
|
|
|
procedure WarnAllocFailed(const foreground : TGdkColor);
|
|
begin
|
|
DebugLn('NOTE: EnsureGCColor.EnsureAsGCValues gdk_colormap_alloc_color failed ',
|
|
' Foreground=',
|
|
DbgS(Foreground.red),',',
|
|
DbgS(Foreground.green),',',
|
|
DbgS(Foreground.blue),
|
|
' GDIColor^.ColorRef=',DbgS(GDIColor^.ColorRef)
|
|
);
|
|
end;
|
|
|
|
procedure EnsureAsGCValues;
|
|
var
|
|
AllocFG : Boolean;
|
|
SysGCValues: TGdkGCValues;
|
|
begin
|
|
FreeGDIColor(GDIColor);
|
|
SysGCValues:=GetSysGCValues(GDIColor^.ColorRef,
|
|
TGtkDeviceContext(DC).Widget);
|
|
{$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
|
|
with SysGCValues do
|
|
begin
|
|
AllocFG := Foreground.Pixel = 0;
|
|
if AllocFG then
|
|
if not gdk_colormap_alloc_color(GDK_Colormap_get_system, @Foreground,
|
|
True, True) then
|
|
WarnAllocFailed(Foreground);
|
|
gdk_gc_set_fill(GC, fill);
|
|
if AsBackground then
|
|
gdk_gc_set_background(GC, @foreground)
|
|
else
|
|
gdk_gc_set_foreground(GC, @foreground);
|
|
case Fill of
|
|
GDK_TILED :
|
|
if Tile <> nil then
|
|
begin
|
|
gdk_gc_set_ts_origin(GC, ts_x_origin, ts_y_origin);
|
|
gdk_gc_set_tile(GC, Tile);
|
|
end;
|
|
GDK_STIPPLED,
|
|
GDK_OPAQUE_STIPPLED:
|
|
if stipple <> nil then
|
|
begin
|
|
gdk_gc_set_background(GC, @background);
|
|
gdk_gc_set_ts_origin(GC, ts_x_origin, ts_y_origin);
|
|
gdk_gc_set_stipple(GC, stipple);
|
|
end;
|
|
end;
|
|
if AllocFG then
|
|
gdk_colormap_free_colors(GDK_Colormap_get_system, @Foreground,1);
|
|
end;
|
|
{$IFDEF DebugGDK}EndGDKErrorTrap;{$ENDIF}
|
|
end;
|
|
|
|
procedure EnsureAsColor;
|
|
begin
|
|
AllocGDIColor(DC, GDIColor);
|
|
//DebugLn('EnsureAsColor ',DbgS(GDIColor^.ColorRef),' AsBackground=',AsBackground);
|
|
{$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
|
|
if AsBackground then
|
|
gdk_gc_set_background(GC, @(GDIColor^.Color))
|
|
else
|
|
begin
|
|
gdk_gc_set_fill(GC, GDK_SOLID);
|
|
gdk_gc_set_foreground(GC, @(GDIColor^.Color));
|
|
end;
|
|
{$IFDEF DebugGDK}EndGDKErrorTrap;{$ENDIF}
|
|
end;
|
|
|
|
begin
|
|
GC:=TGtkDeviceContext(DC).GC;
|
|
GDIColor:=nil;
|
|
with TGtkDeviceContext(DC) do
|
|
begin
|
|
case ColorType of
|
|
dccCurrentBackColor: GDIColor:=@CurrentBackColor;
|
|
dccCurrentTextColor: GDIColor:=@CurrentTextColor;
|
|
dccGDIBrushColor : GDIColor:=@(GetBrush^.GDIBrushColor);
|
|
dccGDIPenColor : GDIColor:=@(GetPen^.GDIPenColor);
|
|
end;
|
|
end;
|
|
if GDIColor=nil then exit;
|
|
|
|
// FPC bug workaround:
|
|
// clScrollbar = $80000000 can't be used in case statements
|
|
if TColor(GDIColor^.ColorRef)=clScrollbar then
|
|
begin
|
|
//often have a BK Pixmap
|
|
if IsSolidBrush then
|
|
EnsureAsGCValues
|
|
else
|
|
EnsureAsColor;//GC's with Pixmaps can't work w/Hatch's (yet)
|
|
exit;
|
|
end;
|
|
|
|
case TColor(GDIColor^.ColorRef) of
|
|
//clScrollbar: see above
|
|
clInfoBk,
|
|
clMenu,
|
|
clHighlight,
|
|
clBtnFace,
|
|
clWindow,
|
|
clForm:
|
|
//often have a BK Pixmap
|
|
if IsSolidBrush then
|
|
EnsureAsGCValues
|
|
else
|
|
EnsureAsColor;//GC's with Pixmaps can't work w/Hatch's (yet)
|
|
|
|
clHighlightText,
|
|
clBtnShadow,
|
|
clBtnHighlight,
|
|
clBtnText,
|
|
clInfoText,
|
|
clWindowText,
|
|
clMenuText,
|
|
clGrayText:
|
|
//should never have a BK Pixmap
|
|
EnsureAsGCValues;
|
|
else
|
|
EnsureAsColor;
|
|
end;
|
|
end;
|
|
|
|
procedure CopyGDIColor(var SourceGDIColor, DestGDIColor: TGDIColor);
|
|
begin
|
|
SetGDIColorRef(DestGDIColor,SourceGDIColor.ColorRef);
|
|
end;
|
|
|
|
function IsBackgroundColor(Color: TColor): boolean;
|
|
begin
|
|
Result := (Color = clForm) or
|
|
(Color = clInfoBk) or
|
|
(Color = clBackground);
|
|
end;
|
|
|
|
function CompareGDIColor(const Color1, Color2: TGDIColor): boolean;
|
|
begin
|
|
Result:=Color1.ColorRef=Color2.ColorRef;
|
|
end;
|
|
|
|
function CompareGDIFill(const Fill1, Fill2: TGdkFill): boolean;
|
|
begin
|
|
Result:=Fill1=Fill2;
|
|
end;
|
|
|
|
function CompareGDIBrushes(Brush1, Brush2: PGdiObject): boolean;
|
|
begin
|
|
Result:=Brush1^.IsNullBrush=Brush2^.IsNullBrush;
|
|
if Result then begin
|
|
Result:=CompareGDIColor(Brush1^.GDIBrushColor,Brush2^.GDIBrushColor);
|
|
if Result then begin
|
|
Result:=CompareGDIFill(Brush1^.GDIBrushFill,Brush2^.GDIBrushFill);
|
|
if Result then begin
|
|
Result:=Brush1^.GDIBrushPixMap=Brush2^.GDIBrushPixMap;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
{ Palette Index<->RGB Hash Functions }
|
|
|
|
type
|
|
TIndexRGB = record
|
|
Index: longint;
|
|
RGB: longint;
|
|
end;
|
|
PIndexRGB = ^TIndexRGB;
|
|
|
|
function GetIndexAsKey(p: pointer): pointer;
|
|
begin
|
|
Result:=Pointer(PIndexRGB(p)^.Index + 1);
|
|
end;
|
|
|
|
function GetRGBAsKey(p: pointer): pointer;
|
|
begin
|
|
Result:=Pointer(PIndexRGB(p)^.RGB + 1);
|
|
end;
|
|
|
|
function PaletteIndexToIndexRGB(Pal : PGDIObject; I : longint): PIndexRGB;
|
|
var
|
|
HashItem: PDynHashArrayItem;
|
|
begin
|
|
Result := nil;
|
|
HashItem:=Pal^.IndexTable.FindHashItemWithKey(Pointer(I + 1));
|
|
if HashItem<>nil then
|
|
Result:=PIndexRGB(HashItem^.Item);
|
|
end;
|
|
|
|
function PaletteRGBToIndexRGB(Pal : PGDIObject; RGB : longint): PIndexRGB;
|
|
var
|
|
HashItem: PDynHashArrayItem;
|
|
begin
|
|
Result := nil;
|
|
HashItem:=Pal^.RGBTable.FindHashItemWithKey(Pointer(RGB + 1));
|
|
if HashItem<>nil then
|
|
Result:=PIndexRGB(HashItem^.Item);
|
|
end;
|
|
|
|
{ Palette Index<->RGB lookup Functions }
|
|
|
|
function PaletteIndexExists(Pal : PGDIObject; I : longint): Boolean;
|
|
begin
|
|
Result := Pal^.IndexTable.ContainsKey(Pointer(I + 1));
|
|
end;
|
|
|
|
function PaletteRGBExists(Pal : PGDIObject; RGB : longint): Boolean;
|
|
begin
|
|
Result := Pal^.RGBTable.ContainsKey(Pointer(RGB + 1));
|
|
end;
|
|
|
|
function PaletteAddIndex(Pal : PGDIObject; I, RGB : Longint): Boolean;
|
|
var
|
|
IndexRGB: PIndexRGB;
|
|
begin
|
|
New(IndexRGB);
|
|
IndexRGB^.Index:=I;
|
|
IndexRGB^.RGB:=RGB;
|
|
Pal^.IndexTable.Add(IndexRGB);
|
|
Result := PaletteIndexExists(Pal, I);
|
|
If Not Result then
|
|
Dispose(IndexRGB)
|
|
else begin
|
|
Pal^.RGBTable.Add(IndexRGB);
|
|
Result := PaletteRGBExists(Pal, RGB);
|
|
If not Result then begin
|
|
Pal^.IndexTable.Remove(IndexRGB);
|
|
Dispose(IndexRGB);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function PaletteDeleteIndex(Pal : PGDIObject; I : Longint): Boolean;
|
|
var
|
|
RGBIndex : PIndexRGB;
|
|
begin
|
|
RGBIndex := PaletteIndextoIndexRGB(Pal,I);
|
|
Result := RGBIndex = nil;
|
|
If not Result then begin
|
|
Pal^.IndexTable.Remove(RGBIndex);
|
|
If PaletteRGBExists(Pal, RGBIndex^.RGB) then
|
|
Pal^.RGBTable.Remove(RGBIndex);
|
|
Dispose(RGBIndex);
|
|
end;
|
|
end;
|
|
|
|
function PaletteIndexToRGB(Pal : PGDIObject; I : longint): longint;
|
|
var
|
|
RGBIndex : PIndexRGB;
|
|
begin
|
|
RGBIndex := PaletteIndextoIndexRGB(Pal,I);
|
|
if RGBIndex = nil then
|
|
Result := -1//InvalidRGB
|
|
else
|
|
Result := RGBIndex^.RGB;
|
|
end;
|
|
|
|
function PaletteRGBToIndex(Pal : PGDIObject; RGB : longint): longint;
|
|
var
|
|
RGBIndex : PIndexRGB;
|
|
begin
|
|
RGBIndex := PaletteRGBtoIndexRGB(Pal,RGB);
|
|
if RGBIndex = nil then
|
|
Result:=-1//InvalidIndex
|
|
else
|
|
Result := RGBIndex^.Index;
|
|
end;
|
|
|
|
procedure InitializePalette(const Pal: PGDIObject; const Entries: PPaletteEntry; const RGBCount: Longint);
|
|
var
|
|
I: Integer;
|
|
RGBValue: Longint;
|
|
begin
|
|
for I := 0 to RGBCount - 1 do
|
|
begin
|
|
if PaletteIndexExists(Pal, I) then
|
|
PaletteDeleteIndex(Pal, I);
|
|
with Entries[I] do
|
|
RGBValue := RGB(peRed, peGreen, peBlue) {or (peFlags shl 32)??};
|
|
if not PaletteRGBExists(Pal, RGBValue) then
|
|
PaletteAddIndex(Pal, I, RGBValue);
|
|
end;
|
|
end;
|
|
|
|
function HandleGTKKeyUpDown(AWidget: PGtkWidget; AEvent: PGdkEventKey;
|
|
AData: gPointer; ABeforeEvent, AHandleDown: Boolean;
|
|
const AEventName: PGChar) : GBoolean;
|
|
// returns CallBackDefaultReturn if event can continue in gtk's message system
|
|
{off $DEFINE VerboseKeyboard}
|
|
const
|
|
KEYUP_MAP: array[Boolean {syskey}, Boolean {before}] of Cardinal = (
|
|
(LM_KEYUP, CN_KEYUP),
|
|
(LM_SYSKEYUP, CN_SYSKEYUP)
|
|
);
|
|
|
|
KEYDOWN_MAP: array[Boolean {syskey}, Boolean {before}] of Cardinal = (
|
|
(LM_KEYDOWN, CN_KEYDOWN),
|
|
(LM_SYSKEYDOWN, CN_SYSKEYDOWN)
|
|
);
|
|
|
|
CHAR_MAP: array[Boolean {syskey}, Boolean {before}] of Cardinal = (
|
|
(LM_CHAR, CN_CHAR),
|
|
(LM_SYSCHAR, CN_SYSCHAR)
|
|
);
|
|
var
|
|
Msg: TLMKey;
|
|
EventStopped: Boolean;
|
|
EventString: PChar; // GTK1 and GTK2 workaround
|
|
// (and easy access to bytes)
|
|
KeyCode: Word;
|
|
KCInfo: TKeyCodeInfo;
|
|
VKey: Byte;
|
|
ShiftState: TShiftState;
|
|
|
|
Character: TUTF8Char;
|
|
SysKey: Boolean;
|
|
|
|
CommonKeyData: Integer;
|
|
Flags: Integer;
|
|
FocusedWidget: PGtkWidget;
|
|
LCLObject: TObject;
|
|
FocusedWinControl: TWinControl;
|
|
HandledByLCL: Boolean;
|
|
TargetWidget: PGtkWidget;
|
|
TargetObj: gPointer;
|
|
KeyPressesChar: char;
|
|
|
|
procedure StopKeyEvent;
|
|
begin
|
|
{$IFDEF VerboseKeyboard}
|
|
DebugLn('StopKeyEvent AEventName="',AEventName,'" ABeforeEvent=',dbgs(ABeforeEvent));
|
|
{$ENDIF}
|
|
if not EventStopped
|
|
then begin
|
|
g_signal_stop_emission_by_name(PGtkObject(AWidget), AEventName);
|
|
EventStopped := True;
|
|
end;
|
|
|
|
//MWE: still need to skip on win32 ?
|
|
{MWE:.$IfNDef Win32}
|
|
if EventString <> nil
|
|
then begin
|
|
gdk_event_key_set_string(AEvent, #0);
|
|
AEvent^.length := 0;
|
|
end;
|
|
{MWE:.$EndIf}
|
|
ResetDefaultIMContext;
|
|
|
|
AEvent^.KeyVal := 0;
|
|
end;
|
|
|
|
function DeliverKeyMessage(const Target: Pointer; var AMessage): boolean;
|
|
begin
|
|
Result:=DeliverMessage(Target,AMessage)=0;
|
|
if not Result then StopKeyEvent;
|
|
end;
|
|
|
|
function GetSpecialChar: Char;
|
|
begin
|
|
if (AEvent^.keyval > $FF00) and (AEvent^.keyval < $FF20) and
|
|
(AEvent^.keyval <> GDK_KEY_Tab) then
|
|
Result := Chr(AEvent^.keyval xor $FF00)
|
|
else
|
|
Result := #0;
|
|
end;
|
|
|
|
function CanSendChar: Boolean;
|
|
begin
|
|
Result := False;
|
|
if AEvent^.Length > 1 then Exit;
|
|
|
|
// to be delphi compatible we should not send a space here
|
|
if AEvent^.KeyVal = GDK_KEY_KP_SPACE then Exit;
|
|
|
|
// Check if CTRL is pressed
|
|
if ssCtrl in ShiftState
|
|
then begin
|
|
// Check if we pressed ^@
|
|
if (AEvent^.Length = 0)
|
|
and (AEvent^.KeyVal = GDK_KEY_AT)
|
|
then begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
// check if we send the ^Char subset
|
|
if (AEvent^.Length = 1) and (EventString <> nil)
|
|
then begin
|
|
Result := (EventString^ > #0) and (EventString^ < ' ');
|
|
end;
|
|
Exit;
|
|
end;
|
|
Result := (AEvent^.Length > 0) or (GetSpecialChar <> #0);
|
|
end;
|
|
|
|
function KeyAlreadyHandledByGtk: boolean;
|
|
begin
|
|
Result := false;
|
|
if AWidget = nil then exit;
|
|
|
|
if GtkWidgetIsA(AWidget, gtk_entry_get_type)
|
|
then begin
|
|
// the gtk_entry handles the following keys
|
|
case Aevent^.keyval of
|
|
GDK_Key_Return,
|
|
GDK_Key_Escape,
|
|
GDK_Key_Tab: Exit;
|
|
end;
|
|
|
|
Result := AEvent^.length > 0;
|
|
if Result then Exit;
|
|
|
|
case AEvent^.keyval of
|
|
GDK_Key_BackSpace,
|
|
GDK_Key_Clear,
|
|
GDK_Key_Insert,
|
|
GDK_Key_Delete,
|
|
GDK_Key_Home,
|
|
GDK_Key_End,
|
|
GDK_Key_Left,
|
|
GDK_Key_Right,
|
|
$20..$FF: Result := True;
|
|
end;
|
|
exit;
|
|
end;
|
|
|
|
if GtkWidgetIsA(AWidget, gtk_text_get_type)
|
|
then begin
|
|
// the gtk_text handles the following keys
|
|
case AEvent^.keyval of
|
|
GDK_Key_Escape: Exit;
|
|
end;
|
|
|
|
Result := AEvent^.length > 0;
|
|
if Result then Exit;
|
|
|
|
case AEvent^.keyval of
|
|
GDK_Key_Return,
|
|
GDK_Key_Tab,
|
|
GDK_Key_BackSpace,
|
|
GDK_Key_Clear,
|
|
GDK_Key_Insert,
|
|
GDK_Key_Delete,
|
|
GDK_Key_Home,
|
|
GDK_Key_End,
|
|
GDK_Key_Left,
|
|
GDK_Key_Right,
|
|
GDK_Key_Up,
|
|
GDK_Key_Down,
|
|
$20..$FF: Result := True;
|
|
end;
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
procedure CharToKeyVal(C: Char; out KeyVal: guint; out Length: gint);
|
|
begin
|
|
Length := 1;
|
|
{$ifndef gtk1}
|
|
if C in [#$01..#$1B] then
|
|
begin
|
|
KeyVal := $FF00 or Ord(C);
|
|
if KeyVal = GDK_KEY_BackSpace then
|
|
Length := 0;
|
|
end
|
|
else
|
|
{$endif}
|
|
KeyVal := Ord(C);
|
|
end;
|
|
|
|
function KeyActivatedAccelerator: boolean;
|
|
|
|
function CheckMenuChilds(AMenuItem: TMenuItem): boolean;
|
|
var
|
|
i: Integer;
|
|
Item: TMenuItem;
|
|
MenuItemWidget: PGtkWidget;
|
|
begin
|
|
Result:=false;
|
|
if (AMenuItem=nil) or (not AMenuItem.HandleAllocated) then exit;
|
|
for i:=0 to AMenuItem.Count-1 do begin
|
|
Item:=AMenuItem[i];
|
|
if not Item.HandleAllocated then continue;
|
|
if not GTK_WIDGET_SENSITIVE(PGTKWidget(Item.Handle)) then continue;
|
|
if IsAccel(Msg.CharCode,Item.Caption) then begin
|
|
// found
|
|
Result:=true;
|
|
MenuItemWidget:=PGTKWidget(Item.Handle);
|
|
if GtkWidgetIsA(MenuItemWidget,gtk_menu_item_get_type) then begin
|
|
//DebugLn(['CheckMenuChilds popup: ',dbgsName(Item)]);
|
|
// popup the submenu
|
|
gtk_signal_emit_by_name(PGtkObject(MenuItemWidget),'activate-item');
|
|
end;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
AComponent: TComponent;
|
|
AControl: TControl;
|
|
AForm: TCustomForm;
|
|
begin
|
|
Result:=false;
|
|
//debugln('KeyActivatedAccelerator A');
|
|
if not SysKey then exit;
|
|
// it is a system key -> try menus
|
|
if (Msg.CharCode in [VK_A..VK_Z]) then begin
|
|
if (TObject(TargetObj) is TComponent) then begin
|
|
AComponent:=TComponent(TargetObj);
|
|
//DebugLn(['KeyActivatedAccelerator ',dbgsName(AComponent)]);
|
|
if AComponent is TControl then begin
|
|
AControl:=TControl(AComponent);
|
|
repeat
|
|
AForm:=GetFirstParentForm(AControl);
|
|
if AForm<>nil then begin
|
|
if AForm.Menu<>nil then begin
|
|
Result:=CheckMenuChilds(AForm.Menu.Items);
|
|
if Result then exit;
|
|
end;
|
|
end;
|
|
AControl:=AForm.Parent;
|
|
until AControl=nil;
|
|
|
|
{$IFDEF Gtk2}
|
|
// check main menu of MainForm
|
|
if (Application.MainForm<>nil) then begin
|
|
AControl:=TControl(AComponent);
|
|
AForm:=GetParentForm(AControl);
|
|
if (AForm<>nil)
|
|
and (not (fsModal in AForm.FormState))
|
|
and (not Application.MainForm.IsParentOf(AControl))
|
|
and (Application.MainForm.Menu<>nil) then begin
|
|
Result:=CheckMenuChilds(Application.MainForm.Menu.Items);
|
|
if Result then exit;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
{debugln('KeyActivatedAccelerator call TControl.DialogChar');
|
|
if TControl(AComponent).DialogChar(Msg.CharCode) then begin
|
|
debugln('KeyActivatedAccelerator C handled by LCL');
|
|
StopKeyEvent;
|
|
Result:=true;
|
|
end;}
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure EmulateEatenKeys;
|
|
begin
|
|
// some widgets eats keys, but do not do anything useful for the LCL
|
|
// emulate the keys
|
|
if not ABeforeEvent then Exit;
|
|
if EventStopped then Exit;
|
|
|
|
//DebugLn(['EmulateEatenKeys TargetWidget=',dbghex(PtrInt(TargetWidget))]);
|
|
//DebugLn(['EmulateEatenKeys ',GetWidgetDebugReport(TargetWidget),' gdk_event_get_type(AEvent)=',gdk_event_get_type(AEvent),' GDK_KEY_PRESS=',GDK_KEY_PRESS,' VKey=',VKey]);
|
|
{$IFDEF Gtk2}
|
|
// the gtk2 gtkentry handles the return key and emits an activate signal
|
|
// The LCL does not use that and needs the return key event
|
|
// => emulate it
|
|
if GtkWidgetIsA(TargetWidget, gtk_type_entry)
|
|
and (gdk_event_get_type(AEvent) = GDK_KEY_PRESS)
|
|
and (VKey=13)
|
|
then begin
|
|
//DebugLn(['EmulateKeysEatenByGtk ']);
|
|
FillChar(Msg, SizeOf(Msg), 0);
|
|
Msg.CharCode := VKey;
|
|
if SysKey then
|
|
Msg.msg := LM_SYSKEYDOWN
|
|
else
|
|
Msg.msg := LM_KEYDOWN;
|
|
Msg.KeyData := CommonKeyData or (Flags shl 16) or $0001 {TODO: repeatcount};
|
|
|
|
// send the (Sys)KeyDown message directly to the LCL
|
|
NotifyApplicationUserInput(Msg.Msg);
|
|
DeliverKeyMessage(TargetObj, Msg);
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure CheckDeadKey;
|
|
begin
|
|
if ABeforeEvent then begin
|
|
{$IFDEF Gtk2}
|
|
if im_context_widget<>TargetWidget then begin
|
|
//DebugLn(['CheckDeadKey init im_context ',GetWidgetDebugReport(TargetWidget)]);
|
|
ResetDefaultIMContext;
|
|
im_context_widget:=TargetWidget;
|
|
gtk_im_context_set_client_window(im_context,GetControlWindow(TargetWidget));
|
|
//DebugLn(['CheckDeadKey im_context initialized']);
|
|
end;
|
|
// Note: gtk_im_context_filter_keypress understands keypress and keyrelease
|
|
gtk_im_context_filter_keypress (im_context, AEvent);
|
|
//DebugLn(['CheckDeadKey DeadKey=',DeadKey,' str="',im_context_string,'"']);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result := CallBackDefaultReturn;
|
|
|
|
EventStopped := False;
|
|
HandledByLCL := KeyEventWasHandledByLCL(AEvent, ABeforeEvent);
|
|
|
|
{$IFDEF VerboseKeyboard}
|
|
DebugLn(['[HandleGTKKeyUpDown] ',DbgSName(TControl(AData)),
|
|
' ',(AEvent^.{$IFDEF GTK1}theType{$ELSE}_Type{$ENDIF}),' Widget=',GetWidgetClassName(AWidget),
|
|
' Before=',ABeforeEvent,' Down=',AHandleDown,' HandledByLCL=',HandledByLCL]);
|
|
{$ENDIF}
|
|
|
|
// handle every key event only once
|
|
if HandledByLCL then Exit;
|
|
|
|
TargetWidget := AWidget;
|
|
TargetObj := AData;
|
|
FocusedWinControl := nil;
|
|
FocusedWidget := nil;
|
|
|
|
// The gtk sends keys first to the gtkwindow and then to the focused control.
|
|
// The LCL expects only once to the focused control.
|
|
// And some gtk widgets (combo) eats keys, so that the LCL has no chance to
|
|
// handle it. Therefore keys to the form are immediately redirected to the
|
|
// focused control without changing the normal gtk event path.
|
|
if GtkWidgetIsA(AWidget, gtk_window_get_type)
|
|
then begin
|
|
FocusedWidget := PGtkWindow(AWidget)^.focus_widget;
|
|
if FocusedWidget <> nil
|
|
then begin
|
|
LCLObject := GetNearestLCLObject(FocusedWidget);
|
|
if LCLObject is TWinControl
|
|
then begin
|
|
FocusedWinControl := TWinControl(LCLObject);
|
|
if FocusedWidget <> AWidget
|
|
then begin
|
|
{$IFDEF VerboseKeyboard}
|
|
DebugLn('[HandleGTKKeyUpDown] REDIRECTING ',
|
|
' FocusedWidget=',GetWidgetClassName(FocusedWidget),
|
|
' Control=',FocusedWinControl.Name,':',FocusedWinControl.ClassName);
|
|
{$ENDIF}
|
|
// redirect key to lcl control
|
|
TargetWidget := FocusedWidget;
|
|
TargetObj := FocusedWinControl;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// remember this event
|
|
RememberKeyEventWasHandledByLCL(AEvent, ABeforeEvent);
|
|
|
|
if TargetWidget = nil then Exit;
|
|
|
|
//DebugLn(['HandleGTKKeyUpDown TargetWidget=',GetWidgetDebugReport(TargetWidget)]);
|
|
|
|
|
|
//DebugLn(['HandleGTKKeyUpDown TargetWidget=',GetWidgetDebugReport(TargetWidget),' ',DbgStr(EventString),' state=',AEvent^.state,' keyval=',AEvent^.keyval]);
|
|
FillChar(Msg, SizeOf(Msg), 0);
|
|
|
|
gdk_event_key_get_string(AEvent, EventString);
|
|
//DebugLn(['HandleGTKKeyUpDown TargetWidget=',GetWidgetDebugReport(TargetWidget),' ',DbgStr(EventString),' state=',AEvent^.state,' keyval=',AEvent^.keyval]);
|
|
CheckDeadKey;
|
|
Flags := 0;
|
|
SysKey := False;
|
|
ShiftState := GTKEventStateToShiftState(AEvent^.state);
|
|
{$ifdef gtk1}
|
|
KeyCode := XKeysymToKeycode(gdk_display, AEvent^.keyval);
|
|
{$else}
|
|
KeyCode := AEvent^.hardware_keycode;
|
|
{$endif}
|
|
|
|
if (KeyCode = 0)
|
|
or (KeyCode > High(MKeyCodeInfo))
|
|
or (MKeyCodeInfo[KeyCode].VKey1 = 0)
|
|
then begin
|
|
// no VKey defined, maybe composed char ?
|
|
CommonKeyData := 0;
|
|
end
|
|
else begin
|
|
KCInfo := MKeyCodeInfo[KeyCode];
|
|
|
|
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;
|
|
|
|
if (KCInfo.Flags and KCINFO_FLAG_EXT) <> 0
|
|
then Flags := KF_EXTENDED;
|
|
|
|
|
|
// ssAlt + a key pressed is always a syskey
|
|
// ssAltGr + a key is only a syskey when the key pressed has no levelshift or when ssHift is pressed to0
|
|
SysKey := (ssAlt in ShiftState);
|
|
if not SysKey
|
|
then begin
|
|
// Check ssAltGr
|
|
if (KCInfo.Flags and KCINFO_FLAG_ALTGR) = 0
|
|
then begin
|
|
// VKey has no levelshift char so AltGr is syskey
|
|
SysKey := ssAltGr in ShiftState;
|
|
end
|
|
else begin
|
|
// VKey has levelshift char so AltGr + Shift is syskey
|
|
SysKey := ShiftState * [ssShift, ssAltGr] = [ssShift, ssAltGr]
|
|
end;
|
|
end;
|
|
if SysKey
|
|
then Flags := Flags or KF_ALTDOWN;
|
|
|
|
CommonKeyData := KeyCode shl 16; // Not really scancode, but will do
|
|
|
|
if AHandleDown
|
|
then begin
|
|
{$IFDEF VerboseKeyboard}
|
|
DebugLn('[HandleGTKKeyUpDown] GDK_KEY_PRESS VKey=',dbgs(VKey),' SysKey=',dbgs(SysKey));
|
|
{$ENDIF}
|
|
|
|
Msg.CharCode := VKey;
|
|
Msg.Msg := KEYDOWN_MAP[SysKey, ABeforeEvent];
|
|
|
|
// todo repeat
|
|
// Flags := Flags or KF_REPEAT;
|
|
|
|
Msg.KeyData := CommonKeyData or (Flags shl 16) or $0001 {TODO: repeatcount};
|
|
|
|
if not KeyAlreadyHandledByGtk
|
|
then begin
|
|
// send the (Sys)KeyDown message directly to the LCL
|
|
NotifyApplicationUserInput(Msg.Msg);
|
|
if DeliverKeyMessage(TargetObj, Msg)
|
|
and (Msg.CharCode <> Vkey) then
|
|
StopKeyEvent;
|
|
end;
|
|
|
|
if (not EventStopped) and ABeforeEvent
|
|
then begin
|
|
if KeyActivatedAccelerator then exit;
|
|
end;
|
|
end
|
|
else begin
|
|
{$IFDEF VerboseKeyboard}
|
|
DebugLn('[HandleGTKKeyUpDown] GDK_KEY_RELEASE VKey=',dbgs(VKey));
|
|
{$ENDIF}
|
|
|
|
Msg.CharCode := VKey;
|
|
Msg.Msg := KEYUP_MAP[SysKey, ABeforeEvent];
|
|
Flags := Flags or KF_UP or KF_REPEAT;
|
|
Msg.KeyData := CommonKeyData or (Flags shl 16) or $0001 {always};
|
|
|
|
// send the message directly to the LCL
|
|
Msg.Result:=0;
|
|
NotifyApplicationUserInput(Msg.Msg);
|
|
|
|
if DeliverKeyMessage(TargetObj, Msg)
|
|
and (Msg.CharCode <> VKey)
|
|
then begin
|
|
// key was handled by LCL
|
|
StopKeyEvent;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// send keypresses
|
|
if not EventStopped and AHandleDown then begin
|
|
|
|
// send the UTF8 keypress
|
|
if ABeforeEvent then begin
|
|
// try to get the UTF8 representation of the key
|
|
{$IFDEF GTK1}
|
|
Character := '';
|
|
if (AEvent^.length > 0) and (AEvent^.length <= 8) //max composed UTF8 char has lenght 8
|
|
then begin
|
|
SetLength(Character, AEvent^.length);
|
|
System.Move(AEvent^.thestring^, Character[1], length(Character));
|
|
end;
|
|
{$ELSE GTK2}
|
|
if im_context_string <> '' then
|
|
begin
|
|
Character := UTF8Copy(im_context_string,1,1);
|
|
im_context_string:='';// clear, to avoid sending again
|
|
end
|
|
else
|
|
begin
|
|
KeyPressesChar := GetSpecialChar;
|
|
if KeyPressesChar <> #0 then
|
|
Character := KeyPressesChar
|
|
else
|
|
Character := '';
|
|
end;
|
|
{$ENDIF GTK2}
|
|
|
|
{$IFDEF VerboseKeyboard}
|
|
debugln('[HandleGTKKeyUpDown] GDK_KEY_PRESS UTF8="',DbgStr(Character),'"');
|
|
{$ENDIF}
|
|
|
|
if Character <> ''
|
|
then begin
|
|
LCLObject := GetNearestLCLObject(TargetWidget);
|
|
if LCLObject is TWinControl
|
|
then begin
|
|
// send the key after navigation keys were handled
|
|
Result := TWinControl(LCLObject).IntfUTF8KeyPress(Character, 1, SysKey);
|
|
if Result or (Character = '')
|
|
then StopKeyEvent
|
|
else if (Length(Character) = 1)
|
|
{$IFDEF Gtk1}
|
|
// GTK1 only supports normal ASCII characters (Note: #127 is delete)
|
|
and (Character[1] in [#32..#126])
|
|
{$ENDIF}
|
|
then begin
|
|
CharToKeyVal(Character[1], AEvent^.KeyVal, AEvent^.length);
|
|
if AEvent^.length = 1 then
|
|
begin
|
|
EventString^ := Character[1];
|
|
EventString[1] := #0;
|
|
end
|
|
else
|
|
EventString^ := #0;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// send a normal KeyPress Event for Delphi compatibility
|
|
if not EventStopped and CanSendChar
|
|
then begin
|
|
{$IFDEF EventTrace}
|
|
EventTrace('char', data);
|
|
{$ENDIF}
|
|
|
|
KeyPressesChar := #0;
|
|
if AEvent^.Length = 1
|
|
then begin
|
|
// ASCII key was pressed
|
|
KeyPressesChar := EventString^;
|
|
end
|
|
else
|
|
KeyPressesChar := GetSpecialChar;
|
|
|
|
if KeyPressesChar <> #0
|
|
then begin
|
|
FillChar(Msg, SizeOf(Msg), 0);
|
|
|
|
Msg.KeyData := CommonKeyData;
|
|
Msg.Msg := CHAR_MAP[SysKey, ABeforeEvent];
|
|
|
|
// send the (Sys)Char message directly (not queued) to the LCL
|
|
Msg.Result:=0;
|
|
Msg.CharCode := Ord(KeyPressesChar);
|
|
if DeliverKeyMessage(TargetObj, Msg)
|
|
and (Ord(KeyPressesChar) <> Msg.CharCode)
|
|
then begin
|
|
// key was changed by lcl
|
|
if (Msg.CharCode=0) or (Msg.CharCode>=128)
|
|
then begin
|
|
// key set to invalid => just stop
|
|
StopKeyEvent;
|
|
end
|
|
else begin
|
|
// try to change the key
|
|
CharToKeyVal(chr(Msg.CharCode), AEvent^.KeyVal, AEvent^.length);
|
|
if AEvent^.length = 1 then
|
|
begin
|
|
EventString^ := Character[1];
|
|
EventString[1] := #0;
|
|
end
|
|
else
|
|
EventString^ := #0;
|
|
gdk_event_key_set_string(AEvent, EventString);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
EmulateEatenKeys;
|
|
|
|
{$IFDEF Gtk1}
|
|
Result:=true;
|
|
{$ELSE}
|
|
Result:=EventStopped;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Procedure: InitKeyboardTables
|
|
Params: none
|
|
Returns: none
|
|
|
|
Initializes the CharToVK and CKeyToVK tables
|
|
------------------------------------------------------------------------------}
|
|
procedure InitKeyboardTables;
|
|
|
|
procedure FindVKeyInfo(const AKeySym: Cardinal; var AVKey: Byte;
|
|
var AExtended, AHasMultiVK, ASecondKey: Boolean);
|
|
var
|
|
ByteKey: Byte;
|
|
begin
|
|
AExtended := False;
|
|
AHasMultiVK := False;
|
|
AVKey := VK_UNDEFINED;
|
|
ASecondKey := False;
|
|
|
|
case AKeySym of
|
|
32..255: begin
|
|
ByteKey:=Byte(AKeySym);
|
|
case Chr(ByteKey) of // Normal ASCII chars
|
|
//only unshifted values are checked
|
|
//'A'..'Z',
|
|
'0'..'9',
|
|
' ': AVKey := ByteKey;
|
|
'a'..'z': AVKey := ByteKey - Ord('a') + Ord('A');
|
|
'+': AVKey := VK_OEM_PLUS;
|
|
',': AVKey := VK_OEM_COMMA;
|
|
'-': AVKey := VK_OEM_MINUS;
|
|
'.': AVKey := VK_OEM_PERIOD;
|
|
|
|
// try the US keycodes first
|
|
';': AVKey := VK_OEM_1;
|
|
'/': AVKey := VK_OEM_2;
|
|
'`': AVKey := VK_OEM_3;
|
|
'[': AVKey := VK_OEM_4;
|
|
'\': AVKey := VK_OEM_5;
|
|
']': AVKey := VK_OEM_6;
|
|
'''': AVKey := VK_OEM_7;
|
|
end;
|
|
end;
|
|
|
|
GDK_KEY_Tab,
|
|
GDK_KEY_ISO_Left_Tab: AVKey := VK_TAB;
|
|
GDK_KEY_RETURN: AVKey := VK_RETURN;
|
|
// GDK_KEY_LINEFEED; AVKey := $0A;
|
|
|
|
// Cursor block / keypad
|
|
GDK_KEY_INSERT:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_INSERT;
|
|
end;
|
|
GDK_KEY_DELETE:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_DELETE;
|
|
end;
|
|
GDK_KEY_HOME:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_HOME;
|
|
end;
|
|
GDK_KEY_LEFT:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_LEFT;
|
|
end;
|
|
GDK_KEY_UP:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_UP;
|
|
end;
|
|
GDK_KEY_RIGHT:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_RIGHT;
|
|
end;
|
|
GDK_KEY_DOWN:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_DOWN;
|
|
end;
|
|
GDK_KEY_PAGE_UP:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_PRIOR;
|
|
end;
|
|
GDK_KEY_PAGE_DOWN:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_NEXT;
|
|
end;
|
|
GDK_KEY_END:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_END;
|
|
end;
|
|
|
|
// Keypad
|
|
GDK_KEY_KP_ENTER:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_Return;
|
|
end;
|
|
GDK_KEY_KP_Space, GDK_KEY_KP_Begin:
|
|
begin
|
|
AVKey := VK_CLEAR;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_INSERT:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_INSERT;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_HOME:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_HOME;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_LEFT:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_LEFT;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_UP:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_UP;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_RIGHT:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_RIGHT;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_DOWN:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_DOWN;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_PAGE_UP:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_PRIOR;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_PAGE_DOWN:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_NEXT;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_END:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_END;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_Num_Lock:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_NUMLOCK;
|
|
end;
|
|
GDK_KEY_KP_F1..GDK_KEY_KP_F4:
|
|
begin
|
|
// not on "normal" keyboard so defined extended to differentiate between normal Fn
|
|
AExtended := True;
|
|
AVKey := VK_F1 + AKeySym - GDK_KEY_KP_F1;
|
|
end;
|
|
GDK_KEY_KP_TAB:
|
|
begin
|
|
// not on "normal" keyboard so defined extended to differentiate between normal TAB
|
|
AExtended := True;
|
|
AVKey := VK_TAB;
|
|
end;
|
|
GDK_KEY_KP_Multiply:
|
|
begin
|
|
AVKey := VK_MULTIPLY;
|
|
end;
|
|
GDK_KEY_KP_Add:
|
|
begin
|
|
AVKey := VK_ADD;
|
|
end;
|
|
GDK_KEY_KP_Separator:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_SEPARATOR;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_Subtract:
|
|
begin
|
|
AVKey := VK_SUBTRACT;
|
|
end;
|
|
GDK_KEY_KP_Decimal:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_DECIMAL;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_Delete:
|
|
begin
|
|
// Keypad key is not extended
|
|
AVKey := VK_DELETE;
|
|
AHasMultiVK := True;
|
|
end;
|
|
GDK_KEY_KP_Divide:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_DIVIDE;
|
|
end;
|
|
GDK_KEY_KP_0..GDK_KEY_KP_9:
|
|
begin
|
|
// Keypad key is not extended, it is identified by VK
|
|
AVKey := VK_NUMPAD0 + AKeySym - GDK_KEY_KP_0;
|
|
AHasMultiVK := True;
|
|
end;
|
|
|
|
GDK_KEY_BackSpace: AVKey := VK_BACK;
|
|
GDK_KEY_Clear: AVKey := VK_CLEAR;
|
|
GDK_KEY_Pause: AVKey := VK_PAUSE;
|
|
GDK_KEY_Scroll_Lock: AVKey := VK_SCROLL;
|
|
GDK_KEY_Sys_Req: AVKey := VK_SNAPSHOT;
|
|
GDK_KEY_Escape: AVKey := VK_ESCAPE;
|
|
|
|
GDK_KEY_Kanji: AVKey := VK_KANJI;
|
|
|
|
GDK_Key_Select: AVKey := VK_SELECT;
|
|
GDK_Key_Print: AVKey := VK_PRINT;
|
|
GDK_Key_Execute: AVKey := VK_EXECUTE;
|
|
GDK_Key_Cancel: AVKey := VK_CANCEL;
|
|
GDK_Key_Help: AVKey := VK_HELP;
|
|
GDK_Key_Break: AVKey := VK_CANCEL;
|
|
GDK_Key_Mode_switch: AVKey := VK_MODECHANGE;
|
|
GDK_Key_Caps_Lock: AVKey := VK_CAPITAL;
|
|
GDK_Key_Shift_L: AVKey := VK_SHIFT;
|
|
GDK_Key_Shift_R:
|
|
begin
|
|
AVKey := VK_SHIFT;
|
|
ASecondKey := True;
|
|
end;
|
|
GDK_Key_Control_L: AVKey := VK_CONTROL;
|
|
GDK_Key_Control_R:
|
|
begin
|
|
AVKey := VK_CONTROL;
|
|
ASecondKey := True;
|
|
end;
|
|
// GDK_Key_Meta_L: AVKey := VK_MENU; //shifted alt, so it is found by alt
|
|
// GDK_Key_Meta_R: AVKey := VK_MENU;
|
|
GDK_Key_Alt_L: AVKey := VK_MENU;
|
|
GDK_Key_Alt_R:
|
|
begin
|
|
AVKey := VK_MENU;
|
|
ASecondKey := True;
|
|
end;
|
|
GDK_Key_Super_L: AVKey := VK_LWIN;
|
|
GDK_Key_Super_R: begin
|
|
AVKey := VK_RWIN;
|
|
ASecondKey := True;
|
|
end;
|
|
GDK_Key_Menu: AVKey := VK_APPS;
|
|
|
|
// function keys
|
|
GDK_KEY_F1..GDK_KEY_F24: AVKey := VK_F1 + AKeySym - GDK_Key_F1;
|
|
|
|
// Extra keys on a "internet" keyboard
|
|
GDKX_KEY_Sleep:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_SLEEP;
|
|
end;
|
|
GDKX_KEY_AudioLowerVolume:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_VOLUME_DOWN;
|
|
end;
|
|
GDKX_KEY_AudioMute:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_VOLUME_MUTE;
|
|
end;
|
|
GDKX_KEY_AudioRaiseVolume:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_VOLUME_UP;
|
|
end;
|
|
GDKX_KEY_AudioPlay:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_MEDIA_PLAY_PAUSE;
|
|
end;
|
|
GDKX_KEY_AudioStop:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_MEDIA_STOP;
|
|
end;
|
|
GDKX_KEY_AudioPrev:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_MEDIA_PREV_TRACK;
|
|
end;
|
|
GDKX_KEY_AudioNext:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_MEDIA_NEXT_TRACK;
|
|
end;
|
|
GDKX_KEY_Mail:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_LAUNCH_MAIL;
|
|
end;
|
|
GDKX_KEY_HomePage:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_BROWSER_HOME;
|
|
end;
|
|
GDKX_KEY_Back:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_BROWSER_BACK;
|
|
end;
|
|
GDKX_KEY_Forward:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_BROWSER_FORWARD;
|
|
end;
|
|
GDKX_KEY_Stop:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_BROWSER_STOP;
|
|
end;
|
|
GDKX_KEY_Refresh:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_BROWSER_REFRESH;
|
|
end;
|
|
GDKX_KEY_WWW:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_BROWSER_HOME;
|
|
end;
|
|
GDKX_KEY_Favorites:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_BROWSER_FAVORITES;
|
|
end;
|
|
GDKX_KEY_AudioMedia:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_LAUNCH_MEDIA_SELECT;
|
|
end;
|
|
GDKX_KEY_MyComputer:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_LAUNCH_APP1;
|
|
end;
|
|
GDKX_KEY_Calculator:
|
|
begin
|
|
AExtended := True;
|
|
AVKey := VK_LAUNCH_APP2;
|
|
end;
|
|
|
|
// For faster cases, group by families
|
|
$400..$4FF: begin
|
|
// Katakana
|
|
end;
|
|
|
|
$500..$5FF: begin
|
|
// Arabic
|
|
case AKeySym of
|
|
GDK_KEY_arabic_hamza: AVKey := VK_X;
|
|
GDK_KEY_arabic_hamzaonwaw: AVKey := VK_C;
|
|
GDK_KEY_arabic_hamzaonyeh: AVKey := VK_Z;
|
|
GDK_KEY_arabic_alef: AVKey := VK_H;
|
|
GDK_KEY_arabic_beh: AVKey := VK_F;
|
|
GDK_KEY_arabic_tehmarbuta: AVKey := VK_M;
|
|
GDK_KEY_arabic_teh: AVKey := VK_J;
|
|
GDK_KEY_arabic_theh: AVKey := VK_E;
|
|
GDK_KEY_arabic_jeem: AVKey := VK_OEM_4;
|
|
GDK_KEY_arabic_hah: AVKey := VK_P;
|
|
GDK_KEY_arabic_khah: AVKey := VK_O;
|
|
GDK_KEY_arabic_dal: AVKey := VK_OEM_6;
|
|
GDK_KEY_arabic_thal: AVKey := VK_OEM_3;
|
|
GDK_KEY_arabic_ra: AVKey := VK_V;
|
|
GDK_KEY_arabic_zain: AVKey := VK_OEM_PERIOD;
|
|
GDK_KEY_arabic_seen: AVKey := VK_S;
|
|
GDK_KEY_arabic_sheen: AVKey := VK_A;
|
|
GDK_KEY_arabic_sad: AVKey := VK_W;
|
|
GDK_KEY_arabic_dad: AVKey := VK_Q;
|
|
GDK_KEY_arabic_tah: AVKey := VK_OEM_7;
|
|
GDK_KEY_arabic_zah: AVKey := VK_OEM_2;
|
|
GDK_KEY_arabic_ain: AVKey := VK_U;
|
|
GDK_KEY_arabic_ghain: AVKey := VK_Y;
|
|
GDK_KEY_arabic_feh: AVKey := VK_T;
|
|
GDK_KEY_arabic_qaf: AVKey := VK_R;
|
|
GDK_KEY_arabic_kaf: AVKey := VK_OEM_1;
|
|
GDK_KEY_arabic_lam: AVKey := VK_G;
|
|
GDK_KEY_arabic_meem: AVKey := VK_L;
|
|
GDK_KEY_arabic_noon: AVKey := VK_K;
|
|
GDK_KEY_arabic_heh: AVKey := VK_I;
|
|
GDK_KEY_arabic_waw: AVKey := VK_OEM_COMMA;
|
|
GDK_KEY_arabic_alefmaksura: AVKey := VK_N;
|
|
GDK_KEY_arabic_yeh: AVKey := VK_D;
|
|
end;
|
|
end;
|
|
|
|
$600..$6FF: begin
|
|
// Cyrillic
|
|
|
|
// MWE:
|
|
// These VK codes are not compatible with all cyrillic KBlayouts
|
|
// Example:
|
|
// VK_A on a russian layout generates a cyrillic_EF
|
|
// VK_A on a serbian layout generates a cyrillic_A
|
|
//
|
|
// Mapping cyrillic_A to VK_A is easier so that encoding is used.
|
|
// Maybe in future we can take the KBLayout into account
|
|
case AKeySym of
|
|
GDK_KEY_cyrillic_a..GDK_KEY_cyrillic_ze:
|
|
begin
|
|
AVKey := VK_A + AKeySym - GDK_KEY_cyrillic_a;
|
|
end;
|
|
// Capital is not needed, the lower will match
|
|
//GDK_KEY_cyrillic_A..GDK_KEY_cyrillic_ZE:
|
|
//begin
|
|
// AVKey := VK_A + AKeySym - GDK_KEY_cyrillic_A;
|
|
//end;
|
|
end;
|
|
end;
|
|
|
|
$700..$7FF: begin
|
|
// Greek
|
|
case AKeySym of
|
|
// Capital is not needed, the lower will match
|
|
GDK_KEY_greek_alpha: AVKey := VK_A;
|
|
GDK_KEY_greek_beta: AVKey := VK_B;
|
|
GDK_KEY_greek_gamma: AVKey := VK_G;
|
|
GDK_KEY_greek_delta: AVKey := VK_D;
|
|
GDK_KEY_greek_epsilon: AVKey := VK_E;
|
|
GDK_KEY_greek_zeta: AVKey := VK_Z;
|
|
GDK_KEY_greek_eta: AVKey := VK_H;
|
|
GDK_KEY_greek_theta: AVKey := VK_U;
|
|
GDK_KEY_greek_iota: AVKey := VK_I;
|
|
GDK_KEY_greek_kappa: AVKey := VK_K;
|
|
GDK_KEY_greek_lamda: AVKey := VK_L;
|
|
GDK_KEY_greek_mu: AVKey := VK_M;
|
|
GDK_KEY_greek_nu: AVKey := VK_N;
|
|
GDK_KEY_greek_xi: AVKey := VK_J;
|
|
GDK_KEY_greek_omicron: AVKey := VK_O;
|
|
GDK_KEY_greek_pi: AVKey := VK_P;
|
|
GDK_KEY_greek_rho: AVKey := VK_R;
|
|
GDK_KEY_greek_sigma: AVKey := VK_S;
|
|
GDK_KEY_greek_finalsmallsigma: AVKey := VK_W;
|
|
GDK_KEY_greek_tau: AVKey := VK_T;
|
|
GDK_KEY_greek_upsilon: AVKey := VK_Y;
|
|
GDK_KEY_greek_phi: AVKey := VK_F;
|
|
GDK_KEY_greek_chi: AVKey := VK_X;
|
|
GDK_KEY_greek_psi: AVKey := VK_C;
|
|
GDK_KEY_greek_omega: AVKey := VK_V;
|
|
end;
|
|
end;
|
|
|
|
$C00..$CFF: begin
|
|
// Hebrew
|
|
// Shifted keys will produce A..Z so the VK codes will be assigned there
|
|
end;
|
|
|
|
$D00..$DFF: begin
|
|
// Thai
|
|
// To many differences to assign VK codes through lookup
|
|
// Thai Kedmanee and Thai Pattachote are complete different layouts
|
|
end;
|
|
|
|
$E00..$EFF: begin
|
|
// Korean
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function IgnoreShifted(const AUnshiftKeySym: Cardinal): Boolean;
|
|
begin
|
|
case AUnshiftKeySym of
|
|
GDK_KEY_END,
|
|
GDK_KEY_HOME,
|
|
GDK_KEY_LEFT,
|
|
GDK_KEY_RIGHT,
|
|
GDK_KEY_UP,
|
|
GDK_KEY_DOWN,
|
|
GDK_KEY_PAGE_UP,
|
|
GDK_KEY_PAGE_DOWN: Result := True;
|
|
else
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
procedure NextFreeVK(var AFreeVK: Byte);
|
|
begin
|
|
case AFreeVK of
|
|
$96: AFreeVK := $E1;
|
|
$E1: AFreeVK := $E3;
|
|
$E4: AFreeVK := $E6;
|
|
$E6: AFreeVK := $E9;
|
|
$F5: begin
|
|
{$ifndef HideKeyTableWarnings}
|
|
DebugLn('[WARNING] Out of OEM specific VK codes, changing to unassigned');
|
|
{$endif}
|
|
AFreeVK := $88;
|
|
end;
|
|
$8F: AFreeVK := $97;
|
|
$9F: AFreeVK := $D8;
|
|
$DA: AFreeVK := $E5;
|
|
$E5: AFreeVK := $E8;
|
|
$E8: begin
|
|
{$ifndef HideKeyTableWarnings}
|
|
DebugLn('[WARNING] Out of unassigned VK codes, assigning $FF');
|
|
{$endif}
|
|
AFreeVK := $FF;
|
|
end;
|
|
$FF: AFreeVK := $FF; // stay there
|
|
else
|
|
Inc(AFreeVK);
|
|
end;
|
|
end;
|
|
|
|
|
|
const
|
|
KEYFLAGS: array[0..3] of Byte = (
|
|
$00,
|
|
KCINFO_FLAG_SHIFT,
|
|
KCINFO_FLAG_ALTGR,
|
|
KCINFO_FLAG_ALTGR or KCINFO_FLAG_SHIFT
|
|
);
|
|
EXTFLAG: array[Boolean] of Byte = (
|
|
$00,
|
|
KCINFO_FLAG_EXT
|
|
);
|
|
MULTIFLAG: array[Boolean] of Byte = (
|
|
$00,
|
|
KCINFO_FLAG_SHIFT_XOR_NUM
|
|
);
|
|
|
|
{$ifdef HasX}
|
|
{
|
|
Starting gdk 2.10 Alt, meta, hyper are reported by a own mask. Since we support
|
|
older versions, we need to create the modifiermap ourselves for X and we cannot
|
|
ise them
|
|
}
|
|
type
|
|
TModMap = array[Byte] of Cardinal;
|
|
|
|
procedure SetupModifiers(ADisplay: Pointer; var AModMap: TModMap);
|
|
const
|
|
MODIFIERS: array[0..7] of Cardinal = (
|
|
GDK_SHIFT_MASK,
|
|
GDK_LOCK_MASK,
|
|
GDK_CONTROL_MASK,
|
|
GDK_MOD1_MASK,
|
|
GDK_MOD2_MASK,
|
|
GDK_MOD3_MASK,
|
|
GDK_MOD4_MASK,
|
|
GDK_MOD5_MASK
|
|
);
|
|
var
|
|
Map: PXModifierKeymap;
|
|
KeyCode: PKeyCode;
|
|
Modifier, n: Integer;
|
|
begin
|
|
FillByte(AModMap, SizeOf(AModMap), 0);
|
|
|
|
Map := XGetModifierMapping(ADisplay);
|
|
KeyCode := Map^.modifiermap;
|
|
|
|
for Modifier := Low(MODIFIERS) to High(MODIFIERS) do
|
|
begin
|
|
for n := 1 to Map^.max_keypermod do
|
|
begin
|
|
if KeyCode^ <> 0
|
|
then begin
|
|
AModMap[KeyCode^] := MODIFIERS[Modifier];
|
|
{$ifdef VerboseModifiermap}
|
|
DebugLn('Mapped keycode=%u to modifier=$%2.2x', [KeyCode^, MODIFIERS[Modifier]]);
|
|
{$endif}
|
|
end;
|
|
Inc(KeyCode);
|
|
end;
|
|
end;
|
|
|
|
XFreeModifiermap(Map);
|
|
end;
|
|
|
|
procedure UpdateModifierMap(const AModMap: TModMap; AKeyCode: Byte; AKeySym: Cardinal);
|
|
{$ifdef VerboseModifiermap}
|
|
const
|
|
STATE_NAME: array[TShiftStateEnum] of String = ('ssShift', 'ssAlt', 'ssCtrl',
|
|
'ssLeft', 'ssRight', 'ssMiddle', 'ssDouble',
|
|
'ssMeta', 'ssSuper', 'ssHyper', 'ssAltGr', 'ssCaps', 'ssNum',
|
|
'ssScroll', 'ssTriple', 'ssQuad', 'ssExtra1', 'ssExtra2');
|
|
{$endif}
|
|
var
|
|
ShiftState: TShiftStateEnum;
|
|
begin
|
|
if AModMap[AKeyCode] = 0 then Exit;
|
|
|
|
case AKeySym of
|
|
GDK_KEY_Caps_Lock,
|
|
GDK_KEY_Shift_Lock: ShiftState := ssCaps;
|
|
GDK_KEY_Num_Lock: ShiftState := ssNum;
|
|
GDK_KEY_Scroll_Lock: ShiftState := ssScroll;
|
|
GDK_Key_Shift_L,
|
|
GDK_Key_Shift_R: ShiftState := ssShift;
|
|
GDK_KEY_Control_L,
|
|
GDK_KEY_Control_R: ShiftState := ssCtrl;
|
|
{$ifndef UseOwnShiftState}
|
|
// UseOwnShiftState will track these, so we don't have to put them in the modmap
|
|
GDK_KEY_Meta_L,
|
|
GDK_KEY_Meta_R: ShiftState := ssMeta;
|
|
GDK_KEY_Alt_L,
|
|
GDK_KEY_Alt_R: ShiftState := ssAlt;
|
|
GDK_KEY_Super_L,
|
|
GDK_KEY_Super_R: ShiftState := ssSuper;
|
|
GDK_KEY_Hyper_L,
|
|
GDK_KEY_Hyper_R: ShiftState := ssHyper;
|
|
GDK_KEY_ISO_Level3_Shift{,
|
|
GDK_KEY_Mode_switch}: ShiftState := ssAltGr;
|
|
{$endif}
|
|
else
|
|
Exit;
|
|
end;
|
|
|
|
MModifiers[ShiftState].Mask := AModMap[AKeyCode];
|
|
MModifiers[ShiftState].UseValue := False;
|
|
|
|
{$ifdef VerboseModifiermap}
|
|
DebugLn('Mapped keycode=%u, keysym=$%x, modifier=$%2.2x to shiftstate %s', [AKeyCode, AKeySym, AModMap[AKeyCode], STATE_NAME[ShiftState]]);
|
|
{$endif}
|
|
|
|
end;
|
|
|
|
{$ifdef UseOwnShiftState}
|
|
procedure UpdateKeyStateMap(var AIndex: integer; AKeyCode: Byte; AKeySym: Cardinal);
|
|
var
|
|
Enum: TShiftStateEnum;
|
|
begin
|
|
case AKeySym of
|
|
GDK_KEY_Alt_L, GDK_KEY_Alt_R: Enum := ssAlt;
|
|
GDK_KEY_Meta_L, GDK_KEY_Meta_R: Enum := ssMeta;
|
|
GDK_KEY_Super_L, GDK_KEY_Super_R: Enum := ssSuper;
|
|
GDK_KEY_Hyper_L, GDK_KEY_Hyper_R: Enum := ssHyper;
|
|
GDK_KEY_ISO_Level3_Shift: Enum := ssAltGr;
|
|
else
|
|
Exit;
|
|
end;
|
|
|
|
if High(MKeyStateMap) < AIndex
|
|
then SetLength(MKeyStateMap, AIndex + 8);
|
|
|
|
MKeyStateMap[AIndex].Index := AKeyCode shr 3;
|
|
MKeyStateMap[AIndex].Mask := 1 shl (AKeyCode and 7);
|
|
MKeyStateMap[AIndex].Enum := Enum;
|
|
Inc(AIndex)
|
|
end;
|
|
{$endif UseOwnShiftState}
|
|
|
|
{$endif HasX}
|
|
|
|
const
|
|
// first OEM specific VK
|
|
VK_FIRST_OEM = $92;
|
|
|
|
var
|
|
{$ifdef gtk1}
|
|
XKeyEvent: TXKeyEvent;
|
|
KeySymStart, KeySymNext: PKeySym;
|
|
UpKeySym, LoKeySym: TKeySym;
|
|
KeySyms: array of TKeySym;
|
|
{$else}
|
|
KeySyms: array of guint;
|
|
KeyVals: Pguint;
|
|
KeymapKeys: PGdkKeymapKey;
|
|
UniChar: gunichar;
|
|
{$endif}
|
|
KeySymCount: Integer;
|
|
KeySymChars: array[0..16] of Char;
|
|
KeySymCharLen: Integer;
|
|
|
|
{$ifdef HasX}
|
|
XDisplay: Pointer;
|
|
ModMap: TModMap;
|
|
{$endif}
|
|
{$ifdef UseOwnShiftState}
|
|
KeyStateMapIndex: Integer;
|
|
{$endif}
|
|
|
|
KeyCode: Byte;
|
|
m: Integer;
|
|
LoKey, HiKey: Integer;
|
|
|
|
VKey, FreeVK: Byte;
|
|
HasMultiVK, DummyBool, Extended, SecondKey, HasKey, ComputeVK: Boolean;
|
|
begin
|
|
{$ifdef HasX}
|
|
XDisplay := gdk_display;
|
|
if XDisplay = nil then Exit;
|
|
|
|
FillByte(MKeyStateMap, SizeOF(MKeyStateMap), 0);
|
|
SetupModifiers(XDisplay, ModMap);
|
|
{$endif}
|
|
|
|
{$ifdef gtk1}
|
|
// Init dummy XEvent to retrieve the char corresponding to a key
|
|
FillChar(XKeyEvent, SizeOf(XKeyEvent), 0);
|
|
XKeyEvent._Type := GDK_KEY_PRESS;
|
|
XKeyEvent.Display := XDisplay;
|
|
{$ifdef ver2_2}
|
|
XKeyEvent.Same_Screen := True;
|
|
{$else}
|
|
XKeyEvent.Same_Screen := 1;
|
|
{$endif}
|
|
|
|
// Retrieve the KeyCode bounds
|
|
XDisplayKeyCodes(XDisplay, @LoKey, @HiKey);
|
|
if LoKey < 0
|
|
then begin
|
|
DebugLn('[WARNING] Low keycode (%d) negative, adjusting to 0', [LoKey]);
|
|
LoKey := 0;
|
|
end;
|
|
if HiKey > 255
|
|
then begin
|
|
DebugLn('[WARNING] High keycode (%d) larget than 255, adjusting to 255', [HiKey]);
|
|
HiKey := 255;
|
|
end;
|
|
|
|
KeySymCount := 0;
|
|
KeySymStart := XGetKeyboardMapping(XDisplay, LoKey, HiKey - LoKey + 1, @KeySymCount);
|
|
KeySymNext := KeySymStart;
|
|
|
|
if (KeySymCount = 0) or (KeySymStart = nil)
|
|
then begin
|
|
DebugLn('[WARNING] failed to retrieve keyboardmapping');
|
|
if KeySymStart <> nil
|
|
then XFree(KeySymStart);
|
|
Exit;
|
|
end;
|
|
if KeySymCount > Length(MVKeyInfo[0].KeySym)
|
|
then DebugLn('[WARNING] keysymcount=%u larger than expected=%u', [KeySymCount, Length(MVKeyInfo[0].KeySym)]);
|
|
SetLength(KeySyms, KeySymCount);
|
|
{$else gtk1}
|
|
LoKey := 0;
|
|
HiKey := 255;
|
|
{$endif}
|
|
|
|
{$ifdef UseOwnShiftState}
|
|
KeyStateMapIndex := 0;
|
|
{$endif}
|
|
FreeVK := VK_FIRST_OEM;
|
|
for KeyCode := LoKey to HiKey do
|
|
begin
|
|
{$ifdef gtk1}
|
|
Move(KeySymNext^, KeySyms[0], SizeOf(KeySyms[0]) * KeySymCount);
|
|
Inc(KeySymNext, KeySymCount);
|
|
|
|
HasKey := False;
|
|
m := 0;
|
|
while m < KeySymCount do
|
|
begin
|
|
// there might be only uppercase chars are in the map,
|
|
// so we have to add the lowercase ourselves
|
|
// when a group consists of one char(next =0)
|
|
if KeySyms[m] <> 0
|
|
then begin
|
|
HasKey := True;
|
|
if KeySyms[m+1] = 0
|
|
then begin
|
|
XConvertCase(KeySyms[m], @LoKeySym, @UpKeySym);
|
|
if LoKeySym <> UpKeySym
|
|
then begin
|
|
KeySyms[m] := LoKeySym;
|
|
KeySyms[m+1] := UpKeySym;
|
|
end;
|
|
end;
|
|
end;
|
|
Inc(m, 2);
|
|
end;
|
|
|
|
{$else}
|
|
if not gdk_keymap_get_entries_for_keycode(nil, KeyCode, KeymapKeys, KeyVals, @KeySymCount) then Continue;
|
|
SetLength(KeySyms, KeySymCount);
|
|
Move(KeyVals^, KeySyms[0], SizeOf(KeySyms[0]) * KeySymCount);
|
|
g_free(KeymapKeys); // unused but we cannot pass a nil as param
|
|
g_free(KeyVals);
|
|
HasKey := KeySyms[0] <> 0;
|
|
//DebugLn(['InitKeyboardTables ',KeyCode,' ',HasKey,' ',KeySyms[0]]);
|
|
{$endif}
|
|
|
|
{$ifdef HasX}
|
|
// Check if this keycode is in the modifiers map
|
|
// loop through all keysyms till one found.
|
|
// Some maps have a modifier with an undefined first keysym. It is checked for
|
|
// modifiers, but not for vkeys
|
|
for m := 0 to KeySymCount - 1 do
|
|
begin
|
|
if KeySyms[m] = 0 then Continue;
|
|
UpdateModifierMap(ModMap, KeyCode, KeySyms[m]);
|
|
{$ifdef UseOwnShiftState}
|
|
UpdateKeyStateMap(KeyStateMapIndex, KeyCode, KeySyms[m]);
|
|
{$endif}
|
|
Break;
|
|
end;
|
|
{$endif}
|
|
|
|
// Continue if there is no keysym found
|
|
if not HasKey then Continue;
|
|
|
|
// Start looking for a VKcode
|
|
VKey := VK_UNDEFINED;
|
|
for m := 0 to KeySymCount - 1 do
|
|
begin
|
|
if KeySyms[m] = 0 then Continue;
|
|
FindVKeyInfo(KeySyms[m], VKey, Extended, HasMultiVK, SecondKey);
|
|
{$ifdef Windows}
|
|
// on windows, the keycode is perdef the VK,
|
|
// we only enter this loop to set the correct flags
|
|
VKey := KeyCode;
|
|
Break;
|
|
{$else}
|
|
if HasMultiVK then Break; // has VK per def
|
|
if VKey = VK_UNDEFINED then Continue;
|
|
if MVKeyInfo[VKey].KeyCode[SecondKey or Extended] = 0 then Break; // found unused VK
|
|
|
|
// already in use
|
|
VKey := VK_UNDEFINED;
|
|
{$endif}
|
|
end;
|
|
|
|
ComputeVK := VKey = VK_UNDEFINED;
|
|
if ComputeVK and not HasMultiVK
|
|
then begin
|
|
VKey := FreeVK;
|
|
NextFreeVK(FreeVK);
|
|
end;
|
|
|
|
if VKey = VK_UNDEFINED
|
|
then begin
|
|
MKeyCodeInfo[KeyCode].Flags := $FF
|
|
end
|
|
else begin
|
|
MKeyCodeInfo[KeyCode].Flags := EXTFLAG[Extended] or MULTIFLAG[HasMultiVK];
|
|
MVKeyInfo[VKey].KeyCode[SecondKey] := KeyCode;
|
|
end;
|
|
MKeyCodeInfo[KeyCode].VKey1 := VKey;
|
|
|
|
for m := 0 to Min(High(MVKeyInfo[0].KeyChar), KeySymCount - 1) do
|
|
begin
|
|
if KeySyms[m] = 0 then Continue;
|
|
if (m >= 2) and (KeySyms[m] = KeySyms[m - 2]) then Continue;
|
|
|
|
if HasMultiVK
|
|
then begin
|
|
if m >= 2 then Break; // Only process shift
|
|
|
|
// The keypadkeys have 2 VK_keycodes :(
|
|
// In that case we have to FIndKeyInfo for every keysym
|
|
if m = 1
|
|
then begin
|
|
FindVKeyInfo(KeySyms[m], VKey, Extended, DummyBool, DummyBool);
|
|
MKeyCodeInfo[KeyCode].VKey2 := VKey;
|
|
end;
|
|
end;
|
|
if VKey = VK_UNDEFINED then Continue;
|
|
|
|
MKeyCodeInfo[KeyCode].Flags := MKeyCodeInfo[KeyCode].Flags or KEYFLAGS[m];
|
|
|
|
FillByte(KeySymChars, SizeOf(KeySymChars), 0);
|
|
{$ifdef gtk1}
|
|
// Retrieve the chars for this KeySym
|
|
XKeyEvent.KeyCode := KeyCode;
|
|
case m of
|
|
0: XKeyEvent.State := 0;
|
|
1: XKeyEvent.State := MModifiers[ssShift].Mask;
|
|
2: XKeyEvent.State := MModifiers[ssAltGr].Mask;
|
|
3: XKeyEvent.State := MModifiers[ssAltGr].Mask or MModifiers[ssShift].Mask;
|
|
else
|
|
// TODO: m > 3 ??
|
|
Continue;
|
|
end;
|
|
|
|
KeySymCharLen := XLookupString(@XKeyEvent, KeySymChars, SizeOf(KeySymChars), nil, nil);
|
|
if (KeySymCharLen > 0) and (KeySymChars[KeySymCharLen - 1] = #0)
|
|
then Dec(KeySymCharLen);
|
|
if (KeySymCharLen <= 0) then Continue;
|
|
{$else gtk1}
|
|
UniChar := gdk_keyval_to_unicode(KeySyms[m]);
|
|
if UniChar = 0 then Continue;
|
|
KeySymCharLen := g_unichar_to_utf8(UniChar, @KeySymChars[0]);
|
|
{$endif}
|
|
if (KeySymCharLen > SizeOf(TVKeyUTF8Char))
|
|
then DebugLn('[WARNING] InitKeyboardTables - Keysymstring for keycode=%u longer than %u bytes: %s', [KeyCode, SizeOf(TVKeyUTF8Char), KeySymChars]);
|
|
Move(KeySymChars[0], MVKeyInfo[VKey].KeyChar[m], SizeOf(TVKeyUTF8Char));
|
|
end;
|
|
end;
|
|
{$ifdef UseOwnShiftState}
|
|
SetLength(MKeyStateMap, KeyStateMapIndex);
|
|
{$endif}
|
|
|
|
{$ifdef gtk1}
|
|
XFree(KeySymStart);
|
|
{$endif}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Procedure: DoneKeyboardTables
|
|
Params: none
|
|
Returns: none
|
|
|
|
Frees the dynamic keyboard tables
|
|
------------------------------------------------------------------------------}
|
|
procedure DoneKeyboardTables;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if LCLHandledKeyEvents<>nil then begin
|
|
for i:=0 to LCLHandledKeyEvents.Count-1 do
|
|
TObject(LCLHandledKeyEvents[i]).Free;
|
|
LCLHandledKeyEvents.Free;
|
|
LCLHandledKeyEvents:=nil;
|
|
end;
|
|
if LCLHandledKeyAfterEvents<>nil then begin
|
|
for i:=0 to LCLHandledKeyAfterEvents.Count-1 do
|
|
TObject(LCLHandledKeyAfterEvents[i]).Free;
|
|
LCLHandledKeyAfterEvents.Free;
|
|
LCLHandledKeyAfterEvents:=nil;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: GetVKeyInfo
|
|
Params: AVKey: A virtual key to get the info for
|
|
Returns: A Info record
|
|
|
|
This function is more a safety to make sure MVkeyInfo isn't accessed out of
|
|
it's bounds
|
|
------------------------------------------------------------------------------}
|
|
function GetVKeyInfo(const AVKey: Byte): TVKeyInfo;
|
|
begin
|
|
Result := MVKeyInfo[AVKey];
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Procedure: GTKEventState2ShiftState
|
|
Params: KeyState: The gtk keystate
|
|
Returns: the TShiftState for the given KeyState
|
|
|
|
GTKEventStateToShiftState converts a GTK event state to a LCL/Delphi TShiftState
|
|
------------------------------------------------------------------------------}
|
|
function GTKEventStateToShiftState(KeyState: Word): TShiftState;
|
|
{$ifdef HasX}
|
|
function GetState: TShiftState;
|
|
var
|
|
Keys: chararr32;
|
|
n: Integer;
|
|
begin
|
|
Result := [];
|
|
keys:='';
|
|
XQueryKeyMap(gdk_display, Keys);
|
|
for n := Low(MKeyStateMap) to High(MKeyStateMap) do
|
|
begin
|
|
if Ord(Keys[MKeyStateMap[n].Index]) and MKeyStateMap[n].Mask = 0 then Continue;
|
|
Include(Result, MKeyStateMap[n].Enum);
|
|
Break;
|
|
end;
|
|
end;
|
|
{$else}
|
|
{$ifdef windows}
|
|
function GetState: TShiftState;
|
|
begin
|
|
Result := [];
|
|
if GetKeyState(VK_MENU) < 0 then Include(Result, ssAlt);
|
|
if (GetKeyState(VK_LWIN) < 0) or (GetKeyState(VK_RWIN) < 0) then Include(Result, ssMeta);
|
|
end;
|
|
{$else}
|
|
function GetState: TShiftState;
|
|
begin
|
|
Result := [];
|
|
end;
|
|
{$endif}
|
|
{$endif}
|
|
|
|
var
|
|
State: TShiftStateEnum;
|
|
begin
|
|
{$ifdef UseOwnShiftState}
|
|
Result := GetState;
|
|
{$else}
|
|
Result := [];
|
|
{$endif}
|
|
for State := Low(State) to High(State) do
|
|
begin
|
|
if MModifiers[State].Mask = 0 then Continue;
|
|
if MModifiers[State].UseValue
|
|
then begin
|
|
if KeyState and MModifiers[State].Mask = MModifiers[State].Value
|
|
then Include(Result, State);
|
|
end
|
|
else begin
|
|
if KeyState and MModifiers[State].Mask <> 0
|
|
then Include(Result, State);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Procedure: StoreCommonDialogSetup
|
|
Params: ADialog: TCommonDialog
|
|
Returns: none
|
|
|
|
Stores the size of a TCommonDialog.
|
|
------------------------------------------------------------------------------}
|
|
procedure StoreCommonDialogSetup(ADialog: TCommonDialog);
|
|
var DlgWindow: PGtkWidget;
|
|
begin
|
|
if (ADialog=nil) or (ADialog.Handle=0) then exit;
|
|
DlgWindow:=PGtkWidget(ADialog.Handle);
|
|
if DlgWindow^.Allocation.Width>0 then
|
|
ADialog.Width:=DlgWindow^.Allocation.Width;
|
|
if DlgWindow^.Allocation.Height>0 then
|
|
ADialog.Height:=DlgWindow^.Allocation.Height;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Procedure: DestroyCommonDialogAddOns
|
|
Params: ADialog: TCommonDialog
|
|
Returns: none
|
|
|
|
Free the memory of additional data of a TCommonDialog
|
|
------------------------------------------------------------------------------}
|
|
procedure DestroyCommonDialogAddOns(ADialog: TCommonDialog);
|
|
var
|
|
DlgWindow: PGtkWidget;
|
|
HistoryList: TFPList; // list of TFileSelHistoryListEntry
|
|
AHistoryEntry: PFileSelHistoryEntry;
|
|
i: integer;
|
|
FileSelWidget: PGtkFileSelection;
|
|
LCLHistoryMenu: PGTKWidget;
|
|
{$IFDEF Gtk1}
|
|
//AFilterEntry: TFileSelFilterEntry;
|
|
FilterList: TFPList; // list of TFileSelFilterListEntry
|
|
LCLFilterMenu: PGTKWidget;
|
|
{$ENDIF}
|
|
begin
|
|
if (ADialog=nil) or (not ADialog.HandleAllocated) then exit;
|
|
DlgWindow:=PGtkWidget(ADialog.Handle);
|
|
{$IFDEF VerboseTransient}
|
|
DebugLn('DestroyCommonDialogAddOns ',ADialog.Name,':',ADialog.ClassName);
|
|
{$ENDIF}
|
|
gtk_window_set_transient_for(PGtkWindow(DlgWindow),nil);
|
|
if ADialog is TOpenDialog then begin
|
|
{$IFDEF GTK2}
|
|
FileSelWidget:=GTK_FILE_CHOOSER(DlgWindow);
|
|
{$ELSE}
|
|
FileSelWidget:=GTK_FILE_SELECTION(DlgWindow);
|
|
FreeWidgetInfo(FileSelWidget^.selection_entry);
|
|
FreeWidgetInfo(FileSelWidget^.dir_list);
|
|
FreeWidgetInfo(FileSelWidget^.file_list);
|
|
LCLFilterMenu:=PGTKWidget(gtk_object_get_data(PGtkObject(FileSelWidget),
|
|
'LCLFilterMenu'));
|
|
if LCLFilterMenu<>nil then FreeWidgetInfo(LCLFilterMenu);
|
|
{$ENDIF}
|
|
LCLHistoryMenu:=PGTKWidget(gtk_object_get_data(PGtkObject(FileSelWidget),
|
|
'LCLHistoryMenu'));
|
|
if LCLHistoryMenu<>nil then FreeWidgetInfo(LCLHistoryMenu);
|
|
|
|
// free history
|
|
HistoryList:=TFPList(gtk_object_get_data(PGtkObject(DlgWindow),
|
|
'LCLHistoryList'));
|
|
if HistoryList<>nil then begin
|
|
for i:=0 to HistoryList.Count-1 do begin
|
|
AHistoryEntry:=PFileSelHistoryEntry(HistoryList[i]);
|
|
StrDispose(AHistoryEntry^.Filename);
|
|
AHistoryEntry^.Filename:=nil;
|
|
Dispose(AHistoryEntry);
|
|
end;
|
|
HistoryList.Free;
|
|
gtk_object_set_data(PGtkObject(DlgWindow),'LCLHistoryList',nil);
|
|
end;
|
|
|
|
{$IFDEF GTK1}
|
|
// free filter
|
|
FilterList:=TFPList(gtk_object_get_data(PGtkObject(DlgWindow),
|
|
'LCLFilterList'));
|
|
if FilterList<>nil then begin
|
|
for i:=0 to FilterList.Count-1 do
|
|
TObject(FilterList[i]).Free;
|
|
FilterList.Free;
|
|
gtk_object_set_data(PGtkObject(DlgWindow),'LCLFilterList',nil);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
// free preview handle
|
|
if ADialog is TPreviewFileDialog then begin
|
|
if TPreviewFileDialog(ADialog).PreviewFileControl<>nil then
|
|
TPreviewFileDialog(ADialog).PreviewFileControl.Handle:=0;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Procedure: PopulateFileAndDirectoryLists
|
|
Params: FileSelection: PGtkFileSelection;
|
|
Mask: string (File mask, such as *.txt)
|
|
Returns: none
|
|
|
|
Populate the directory and file lists according to the given mask
|
|
------------------------------------------------------------------------------}
|
|
procedure PopulateFileAndDirectoryLists(FileSelection: PGtkFileSelection;
|
|
const Mask: string);
|
|
var
|
|
Dirs, Files: PGtkCList;
|
|
Text: array [0..1] of Pgchar;
|
|
Info: TSearchRec;
|
|
DirName: PChar;
|
|
Dir: string;
|
|
StrList: TStringList;
|
|
CurFileMask: String;
|
|
|
|
procedure Add(List: PGtkCList; const s: string);
|
|
begin
|
|
Text[0] := PChar(s);
|
|
gtk_clist_append(List, Text);
|
|
end;
|
|
|
|
procedure AddList(List: PGtkCList);
|
|
var
|
|
i: integer;
|
|
begin
|
|
StrList.Sorted := True;
|
|
//DebugLn(['AddList ',StrList.Text]);
|
|
for i:=0 to StrList.Count-1 do
|
|
Add(List, StrList[i]);
|
|
StrList.Sorted := False;
|
|
end;
|
|
|
|
begin
|
|
StrList := TStringList.Create;
|
|
dirs := PGtkCList(FileSelection^.dir_list);
|
|
files := PGtkCList(FileSelection^.file_list);
|
|
DirName := gtk_file_selection_get_filename(FileSelection);
|
|
if DirName <> nil then begin
|
|
SetString(Dir, DirName, strlen(DirName));
|
|
SetLength(Dir, LastDelimiter(PathDelim,Dir));
|
|
end else
|
|
Dir := '';
|
|
//DebugLn(['PopulateFileAndDirectoryLists ',Dir]);
|
|
Text[1] := nil;
|
|
gtk_clist_freeze(Dirs);
|
|
gtk_clist_clear(Dirs);
|
|
gtk_clist_freeze(Files);
|
|
gtk_clist_clear(Files);
|
|
{ Add all directories }
|
|
Strlist.Add('..'+PathDelim);
|
|
if FindFirstUTF8(AppendPathDelim(Dir)+GetAllFilesMask, faAnyFile and faDirectory,
|
|
Info) = 0
|
|
then begin
|
|
repeat
|
|
if ((Info.Attr and faDirectory) = faDirectory) and (Info.Name <> '.')
|
|
and (Info.Name <> '..') and (Info.Name<>'') then
|
|
StrList.Add(AppendPathDelim(Info.Name));
|
|
until FindNextUTF8(Info) <> 0;
|
|
end;
|
|
FindCloseUTF8(Info);
|
|
AddList(Dirs);
|
|
// add required files
|
|
StrList.Clear;
|
|
CurFileMask:=Mask;
|
|
if CurFileMask='' then CurFileMask:=GetAllFilesMask;
|
|
if FindFirstUTF8(AppendPathDelim(Dir)+GetAllFilesMask, faAnyFile, Info) = 0 then
|
|
begin
|
|
repeat
|
|
if ((Info.Attr and faDirectory) <> faDirectory) then begin
|
|
//debugln('PopulateFileAndDirectoryLists CurFileMask="',CurFileMask,'" Info.Name="',Info.Name,'" ',dbgs(MatchesMaskList(Info.Name,CurFileMask)));
|
|
if (CurFileMask='') or (MatchesMaskList(Info.Name,CurFileMask)) then
|
|
begin
|
|
Strlist.Add(Info.Name);
|
|
end;
|
|
end;
|
|
until FindNextUTF8(Info) <> 0;
|
|
end;
|
|
FindCloseUTF8(Info);
|
|
AddList(Files);
|
|
StrList.Free;
|
|
gtk_clist_thaw(Dirs);
|
|
gtk_clist_thaw(Files);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Procedure: DeliverMessage
|
|
Params: Message: the message to process
|
|
Returns: True if handled
|
|
|
|
Generic function which calls the WindowProc if defined, otherwise the
|
|
dispatcher
|
|
------------------------------------------------------------------------------}
|
|
function DeliverMessage(const Target: Pointer; var AMessage): PtrInt;
|
|
begin
|
|
if (TLMessage(AMessage).Msg = LM_PAINT) or
|
|
(TLMessage(AMessage).Msg = LM_GTKPAINT) then
|
|
CurrentSentPaintMessageTarget := TObject(Target);
|
|
|
|
Result := LCLMessageGlue.DeliverMessage(TObject(Target), AMessage);
|
|
|
|
CurrentSentPaintMessageTarget := nil;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: ObjectToGTKObject
|
|
Params: AnObject: A LCL Object
|
|
Returns: The GTKObject of the given object
|
|
|
|
Returns the GTKObject of the given object, nil if no object available
|
|
------------------------------------------------------------------------------}
|
|
function ObjectToGTKObject(const AnObject: TObject): PGtkObject;
|
|
var
|
|
handle : HWND;
|
|
begin
|
|
Handle := 0;
|
|
if not assigned(AnObject) then
|
|
begin
|
|
assert (false, 'TRACE: [ObjectToGtkObject] Object not assigned');
|
|
end
|
|
else if (AnObject is TWinControl) then
|
|
begin
|
|
if TWinControl(AnObject).HandleAllocated then
|
|
handle := TWinControl(AnObject).Handle;
|
|
end
|
|
else if (AnObject is TMenuItem) then
|
|
begin
|
|
if TMenuItem(AnObject).HandleAllocated then
|
|
handle := TMenuItem(AnObject).Handle;
|
|
end
|
|
else if (AnObject is TMenu) then
|
|
begin
|
|
if TMenu(AnObject).HandleAllocated then
|
|
handle := TMenu(AnObject).Items.Handle;
|
|
end
|
|
else if (AnObject is TCommonDialog) then
|
|
begin
|
|
{if TCommonDialog(AObject).HandleAllocated then }
|
|
handle := TCommonDialog(AnObject).Handle;
|
|
end
|
|
else begin
|
|
Assert(False, Format('Trace: [ObjectToGtkObject] Message received with unhandled class-type <%s>', [AnObject.ClassName]));
|
|
end;
|
|
Result := PGTKObject(handle);
|
|
if handle = 0 then
|
|
Assert (false, 'Trace: [ObjectToGtkObject]****** Warning: handle = 0 *******');
|
|
end;
|
|
|
|
|
|
(***********************************************************************
|
|
Widget member functions
|
|
************************************************************************)
|
|
|
|
// ----------------------------------------------------------------------
|
|
// the main widget is the widget passed as handle to the winAPI
|
|
// main data is stored in the fixed form to get a reference to its parent
|
|
// ----------------------------------------------------------------------
|
|
function GetMainWidget(const Widget: Pointer): Pointer;
|
|
begin
|
|
if Widget = nil
|
|
then raise EInterfaceException.Create('GetMainWidget Widget=nil');
|
|
|
|
Result := gtk_object_get_data(Widget, 'Main');
|
|
if Result = nil then Result := Widget; // the widget is the main widget itself.
|
|
end;
|
|
|
|
procedure SetMainWidget(const ParentWidget, ChildWidget: Pointer);
|
|
begin
|
|
if ParentWidget = nil
|
|
then raise EInterfaceException.Create('SetMainWidget ParentWidget=nil');
|
|
if ChildWidget = nil
|
|
then raise EInterfaceException.Create('SetMainWidget ChildWidget=nil');
|
|
if ParentWidget = ChildWidget
|
|
then raise EInterfaceException.Create('SetMainWidget ParentWidget=ChildWidget');
|
|
{$IFDEF Gtk2}
|
|
if PGtkWidget(ParentWidget)^.parent=ChildWidget
|
|
then raise EInterfaceException.Create('SetMainWidget Parent^.Parent=ChildWidget');
|
|
{$ENDIF}
|
|
|
|
gtk_object_set_data(ChildWidget, 'Main', ParentWidget)
|
|
end;
|
|
|
|
{ ------------------------------------------------------------------------------
|
|
Get the fixed widget of a widget.
|
|
Every LCL control with a clientarea, has at least a main widget for the control
|
|
and a fixed widget for the client area. If the Fixed widget is not set, use
|
|
try to get it trough WinWidgetInfo
|
|
------------------------------------------------------------------------------ }
|
|
//TODO: remove when WinWidgetInfo implementation is complete
|
|
function GetFixedWidget(const Widget: Pointer): Pointer;
|
|
var
|
|
WidgetInfo: PWinWidgetInfo;
|
|
begin
|
|
if Widget = nil
|
|
then raise EInterfaceException.Create('GetFixedWidget Widget=nil');
|
|
|
|
WidgetInfo := GetWidgetInfo(Widget, False);
|
|
if WidgetInfo <> nil
|
|
then Result := WidgetInfo^.ClientWidget
|
|
else Result := nil;
|
|
if Result <> nil then Exit;
|
|
|
|
Result := gtk_object_get_data(Widget, 'Fixed');
|
|
// A last resort
|
|
if Result = nil then Result := Widget;
|
|
end;
|
|
|
|
{ ------------------------------------------------------------------------------
|
|
Set the fixed widget of a widget.
|
|
Every LCL control with a clientarea, has at least a main widget for the control
|
|
and a fixed widget for the client area.
|
|
------------------------------------------------------------------------------ }
|
|
procedure SetFixedWidget(const ParentWidget, FixedWidget: Pointer);
|
|
var
|
|
WidgetInfo: PWinWidgetInfo;
|
|
begin
|
|
if ParentWidget = nil
|
|
then raise EInterfaceException.Create('SetFixedWidget ParentWidget=nil');
|
|
|
|
WidgetInfo := GetWidgetInfo(ParentWidget, True);
|
|
WidgetInfo^.ClientWidget := FixedWidget;
|
|
//TODO: remove old compatebility
|
|
gtk_object_set_data(ParentWidget, 'Fixed', FixedWidget)
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
Set the LCLobject which created this widget.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
procedure SetLCLObject(const Widget: Pointer; const AnObject: TObject);
|
|
var
|
|
WidgetInfo: PWinWidgetInfo;
|
|
begin
|
|
if Widget = nil
|
|
then raise EInterfaceException.Create('SetLCLObject Widget=nil');
|
|
if AnObject = nil
|
|
then raise EInterfaceException.Create('SetLCLObject AnObject=nil');
|
|
|
|
WidgetInfo := GetWidgetInfo(Widget, True);
|
|
WidgetInfo^.LCLObject := AnObject;
|
|
end;
|
|
|
|
function GetLCLObject(const Widget: Pointer): TObject;
|
|
var
|
|
WidgetInfo: PWinWidgetInfo;
|
|
begin
|
|
if Widget = nil
|
|
then raise EInterfaceException.Create('GetLCLObject Widget=nil');
|
|
|
|
WidgetInfo := GetWidgetInfo(Widget);
|
|
if WidgetInfo <> nil
|
|
then Result := WidgetInfo^.LCLObject
|
|
else Result := nil;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
Some need the HiddenLCLobject which created a parent of this widget.
|
|
|
|
MWE: is this obsolete ?
|
|
-------------------------------------------------------------------------------}
|
|
procedure SetHiddenLCLObject(const Widget: Pointer; const AnObject: TObject);
|
|
begin
|
|
if (Widget <> nil) then
|
|
gtk_object_set_data(Widget, 'LCLHiddenClass', Pointer(AnObject));
|
|
end;
|
|
|
|
function GetHiddenLCLObject(const Widget: Pointer): TObject;
|
|
begin
|
|
Result := TObject(gtk_object_get_data(Widget, 'LCLHiddenClass'));
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function GetNearestLCLObject(Widget: PGtkWidget): TObject;
|
|
|
|
Retrieves the LCLObject belonging to the widget. If the widget is created as
|
|
child of a main widget, the parent is queried.
|
|
|
|
This function probably obsoletes Get/SetMainWidget
|
|
-------------------------------------------------------------------------------}
|
|
//TODO: check if Get/SetMainWidget is still required
|
|
function GetNearestLCLObject(Widget: PGtkWidget): TObject;
|
|
begin
|
|
while (Widget<>nil) do begin
|
|
Result:=GetLCLObject(Widget);
|
|
if Result<>nil then exit;
|
|
Widget:=Widget^.Parent;
|
|
end;
|
|
Result:=nil;
|
|
end;
|
|
|
|
function CreateFixedClientWidget(WithWindow: Boolean = True): PGTKWidget;
|
|
begin
|
|
Result := gtk_fixed_new();
|
|
{$IFDEF GTK2}
|
|
if WithWindow then
|
|
gtk_fixed_set_has_window(PGtkFixed(Result), true);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure FixedMoveControl(Parent, Child : PGTKWidget; Left, Top : Longint);
|
|
|
|
Move a childwidget on a client area (fixed or layout widget).
|
|
------------------------------------------------------------------------------}
|
|
procedure FixedMoveControl(Parent, Child : PGTKWidget; Left, Top : Longint);
|
|
begin
|
|
If GTKWidgetIsA(Parent, GTK_Layout_Get_Type) then begin
|
|
// parent is layout
|
|
gtk_Layout_move(PGtkLayout(Parent), Child, Left, Top)
|
|
end else If GTKWidgetIsA(Parent, GTK_Fixed_Get_Type) then begin
|
|
// parent is fixed
|
|
gtk_fixed_move(PGtkFixed(Parent), Child, gint16(Left), gint16(Top));
|
|
end else begin
|
|
// parent is invalid
|
|
DebugLn('[FixedMoveControl] WARNING: Invalid Fixed Widget');
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure FixedPutControl(Parent, Child : PGTKWIdget; Left, Top : Longint);
|
|
|
|
Add a childwidget onto a client area (fixed or layout widget).
|
|
------------------------------------------------------------------------------}
|
|
procedure FixedPutControl(Parent, Child: PGTKWidget; Left, Top: Longint);
|
|
|
|
procedure RaiseInvalidFixedWidget;
|
|
begin
|
|
// this is in a separate procedure for optimisation
|
|
DebugLn('[FixedPutControl] WARNING: Invalid Fixed Widget.',
|
|
' Parent=',DbgS(Parent),
|
|
' Child=',DbgS(Child)
|
|
);
|
|
end;
|
|
|
|
begin
|
|
if GtkWidgetIsA(Parent, gtk_fixed_get_type) then
|
|
gtk_fixed_put(PGtkFixed(Parent), Child, gint16(Left), gint16(Top))
|
|
else
|
|
if GtkWidgetIsA(Parent, gtk_layout_get_type) then
|
|
gtk_layout_put(PGtkLayout(Parent), Child, Left, Top)
|
|
else
|
|
RaiseInvalidFixedWidget;
|
|
end;
|
|
|
|
function GetWinControlWidget(Child: PGtkWidget): PGtkWidget;
|
|
// return the first widget, which is associated with a TWinControl handle
|
|
var
|
|
LCLParent: TObject;
|
|
begin
|
|
Result:=nil;
|
|
LCLParent:=GetNearestLCLObject(Child);
|
|
if (LCLParent=nil) or (not (LCLParent is TWinControl))
|
|
or (not TWinControl(LCLParent).HandleAllocated)
|
|
then exit;
|
|
Result:=PGtkWidget(TWinControl(LCLParent).Handle);
|
|
end;
|
|
|
|
function GetWinControlFixedWidget(Child: PGtkWidget): PGtkWidget;
|
|
begin
|
|
Result:=GetWinControlWidget(Child);
|
|
if Result=nil then exit;
|
|
Result:=GetFixedWidget(Result);
|
|
end;
|
|
|
|
function FindFixedChildListItem(ParentFixed: PGtkFixed; Child: PGtkWidget): PGList;
|
|
begin
|
|
Result:=ParentFixed^.children;
|
|
while (Result<>nil) do begin
|
|
if (Result^.Data<>nil) and (PGtkFixedChild(Result^.Data)^.Widget=Child) then
|
|
exit;
|
|
Result:=Result^.Next;
|
|
end;
|
|
end;
|
|
|
|
function FindFixedLastChildListItem(ParentFixed: PGtkFixed): PGList;
|
|
begin
|
|
Result:=g_list_last(ParentFixed^.children);
|
|
end;
|
|
|
|
function GetFixedChildListWidget(Item: PGList): PGtkWidget;
|
|
begin
|
|
Result:=PGtkFixedChild(Item^.Data)^.Widget;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure MoveGListLinkBehind(First, Item, After: PGList);
|
|
|
|
Move the list item 'Item' behind the list item 'After'.
|
|
If After=nil then insert as first item.
|
|
------------------------------------------------------------------------------}
|
|
procedure MoveGListLinkBehind(First, Item, After: PGList);
|
|
var
|
|
Data: Pointer;
|
|
NewPos: Integer;
|
|
begin
|
|
if (Item=After) or (Item^.Next=After) then exit;
|
|
if (g_list_position(First,Item)<0) then
|
|
RaiseGDBException('MoveGListLinkBehind Item not found');
|
|
if (After<>nil) and (g_list_position(First,After)<0) then
|
|
RaiseGDBException('MoveGListLinkBehind After not found');
|
|
Data:=Item^.Data;
|
|
g_list_remove_link(First,Item);
|
|
if After<>nil then begin
|
|
NewPos:=g_list_position(First,After)+1;
|
|
end else begin
|
|
NewPos:=0;
|
|
end;
|
|
g_list_insert(First,Data,NewPos);
|
|
end;
|
|
|
|
procedure MoveGListLink(First: PGList; FromIndex, ToIndex: integer);
|
|
var
|
|
Item: PGList;
|
|
InsertAfter: PGList;
|
|
i: Integer;
|
|
begin
|
|
if (FromIndex=ToIndex) then exit;
|
|
Item:=First;
|
|
i:=0;
|
|
while (i<FromIndex) do begin
|
|
Item:=Item^.next;
|
|
inc(i);
|
|
end;
|
|
// unbind
|
|
if Item^.next<>nil then Item^.next^.prev:=Item^.prev;
|
|
if Item^.prev<>nil then Item^.prev^.next:=Item^.next;
|
|
Item^.next:=nil;
|
|
Item^.prev:=nil;
|
|
// insert
|
|
if ToIndex=0 then begin
|
|
Item^.next:=First;
|
|
First^.prev:=Item;
|
|
end else begin
|
|
i:=0;
|
|
InsertAfter:=First;
|
|
while (i<ToIndex-1) do begin
|
|
if InsertAfter^.next=nil then break;
|
|
InsertAfter:=InsertAfter^.next;
|
|
inc(i);
|
|
end;
|
|
Item^.prev:=InsertAfter;
|
|
Item^.next:=InsertAfter^.next;
|
|
InsertAfter^.next:=Item;
|
|
if Item^.next<>nil then Item^.next^.prev:=Item;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function GetControlWindow(Widget: Pointer) : PGDKWindow;
|
|
|
|
Get the gdkwindow of a widget.
|
|
------------------------------------------------------------------------------}
|
|
function GetControlWindow(Widget: Pointer) : PGDKWindow;
|
|
begin
|
|
if Widget <> nil then
|
|
begin
|
|
If not GTKWidgetIsA(PGTKWidget(Widget), GTK_Layout_Get_Type) then
|
|
Result := PGTKWidget(Widget)^.Window
|
|
else
|
|
Result := PGtkLayout(Widget)^.bin_window;
|
|
{$IFDEF Gtk2}
|
|
if (Result=nil) and (GTK_WIDGET_NO_WINDOW(Widget)) then
|
|
Result:=gtk_widget_get_parent_window(Widget);
|
|
{$ENDIF}
|
|
end else
|
|
RaiseGDBException('GetControlWindow Widget=nil');
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------
|
|
function CreateWidgetInfo(const AWidget: Pointer): PWidgetInfo;
|
|
|
|
Creates a WidgetInfo structure for the given widget
|
|
Info needed by the API of a HWND (=Widget)
|
|
|
|
This structure obsoletes all other object data, like
|
|
"core-child", "fixed", "class"
|
|
------------------------------------------------------------------------------}
|
|
function CreateWidgetInfo(const AWidget: Pointer): PWidgetInfo;
|
|
begin
|
|
if AWidget = nil then Result:= nil
|
|
else begin
|
|
New(Result);
|
|
FillChar(Result^, SizeOf(Result^), 0);
|
|
gtk_object_set_data(AWidget, 'widgetinfo', Result);
|
|
Result^.DefaultCursor := HCursor(-1);
|
|
end;
|
|
end;
|
|
|
|
function CreateWidgetInfo(const AWidget: Pointer; const AObject: TObject;
|
|
const AParams: TCreateParams): PWidgetInfo;
|
|
begin
|
|
Result := CreateWidgetInfo(AWidget);
|
|
if Result = nil then Exit;
|
|
|
|
Result^.LCLObject := AObject;
|
|
// in most cases the created widget is the core widget
|
|
// so default to it
|
|
Result^.CoreWidget := AWidget;
|
|
Result^.Style := AParams.Style;
|
|
Result^.ExStyle := AParams.ExStyle;
|
|
Result^.WndProc := PtrUInt(AParams.WindowClass.lpfnWndProc);
|
|
end;
|
|
|
|
function GetWidgetInfo(const AWidget: Pointer {; const Create: Boolean = False}): PWidgetInfo;
|
|
begin
|
|
Result := GetWidgetInfo(AWidget, False);
|
|
end;
|
|
|
|
function GetWidgetInfo(const AWidget: Pointer;
|
|
const ACreate: Boolean): PWidgetInfo;
|
|
var
|
|
MainWidget: PGtkObject;
|
|
begin
|
|
if AWidget <> nil then
|
|
begin
|
|
MainWidget := GetMainWidget(AWidget);
|
|
Result := gtk_object_get_data(MainWidget, 'widgetinfo');
|
|
if (Result = nil) and ACreate then
|
|
begin
|
|
Result := CreateWidgetInfo(MainWidget);
|
|
// use the main widget as default
|
|
Result^.CoreWidget := PGtkWidget(MainWidget);
|
|
end;
|
|
end
|
|
else Result := nil;
|
|
end;
|
|
|
|
procedure FreeWidgetInfo(AWidget: Pointer);
|
|
var
|
|
Info: PWidgetInfo;
|
|
begin
|
|
if AWidget = nil then Exit;
|
|
//DebugLn(['FreeWidgetInfo ',GetWidgetDebugReport(AWidget)]);
|
|
Info := gtk_object_get_data(AWidget, 'widgetinfo');
|
|
if Info = nil then Exit;
|
|
|
|
if Info^.DoubleBuffer <> nil then
|
|
gdk_pixmap_unref(Info^.DoubleBuffer);
|
|
|
|
if (Info^.UserData <> nil) and (Info^.DataOwner) then begin
|
|
FreeMem(Info^.UserData);
|
|
//Info^.UserData := nil; // see below the whole memory is cleared by Fillchar
|
|
end;
|
|
gtk_object_set_data(AWidget,'widgetinfo',nil);
|
|
|
|
// Set WidgetInfo memory to nil. This will expose bugs that use widgetinfo after
|
|
// it has been freed and is still referenced by something!
|
|
FillChar(Info^, SizeOf(TWidgetInfo), 0);
|
|
|
|
Dispose(Info);
|
|
//DebugLn(['FreeWidgetInfo END']);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure DestroyWidget(Widget: PGtkWidget);
|
|
|
|
- sends LM_DESTROY
|
|
- frees the WidgetInfo
|
|
- destroys the widget in the gtk
|
|
|
|
IMPORTANT:
|
|
The above order must be kept, to avoid callbacks working with dangling
|
|
pointers.
|
|
|
|
Some widgets have a LM_DESTROY set, so if the gtk or some other code
|
|
destroys those widget, the above is done in gtkdestroyCB.
|
|
-------------------------------------------------------------------------------}
|
|
procedure DestroyWidget(Widget: PGtkWidget);
|
|
var
|
|
Info: PWidgetInfo;
|
|
AWinControl: TWinControl;
|
|
Mess: TLMessage;
|
|
begin
|
|
//DebugLn(['DestroyWidget A ',GetWidgetDebugReport(Widget)]);
|
|
{$IFDEF DebugLCLComponents}
|
|
if DebugGtkWidgets.FindInfo(Widget)=nil then
|
|
DebugLn(['DestroyWidget ',GetWidgetDebugReport(Widget)]);
|
|
{$ENDIF}
|
|
Info:=GetWidgetInfo(Widget);
|
|
if Info<>nil then begin
|
|
if (Info^.LCLObject is TWinControl) then begin
|
|
AWinControl:=TWinControl(Info^.LCLObject);
|
|
if AWinControl.HandleAllocated
|
|
and (PGtkWidget(AWinControl.Handle)=Widget) then begin
|
|
// send the LM_DESTROY message before destroying the widget
|
|
FillChar(Mess,SizeOf(Mess),0);
|
|
Mess.msg := LM_DESTROY;
|
|
DeliverMessage(Info^.LCLObject, Mess);
|
|
end;
|
|
end;
|
|
FreeWidgetInfo(Widget);
|
|
end;
|
|
{$IFDEF DebugLCLComponents}
|
|
DebugGtkWidgets.MarkDestroyed(Widget);
|
|
{$ENDIF}
|
|
gtk_widget_destroy(Widget);
|
|
//DebugLn(['DestroyWidget B']);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function GetGtkNoteBookDummyPage(ANoteBookWidget: PGtkNoteBook): PGtkWidget;
|
|
|
|
Retrieves the DummyWidget associated with the ANoteBookWidget
|
|
-------------------------------------------------------------------------------}
|
|
function GetGtkNoteBookDummyPage(ANoteBookWidget: PGtkNoteBook): PGtkWidget;
|
|
begin
|
|
Result:=gtk_object_get_data(PGtkObject(ANoteBookWidget),'LCLDummyPage');
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure SetGtkNoteBookDummyPage(ANoteBookWidget: PGtkNoteBook;
|
|
DummyWidget: PGtkWidget): PGtkWidget;
|
|
|
|
Associates the DummyWidget with the ANoteBookWidget
|
|
-------------------------------------------------------------------------------}
|
|
procedure SetGtkNoteBookDummyPage(ANoteBookWidget: PGtkNoteBook;
|
|
DummyWidget: PGtkWidget);
|
|
begin
|
|
gtk_object_set_data(PGtkObject(ANoteBookWidget),'LCLDummyPage',DummyWidget);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
UpdateNoteBookClientWidget
|
|
Params: ANoteBook: TObject
|
|
|
|
This procedure updates the 'Fixed' object data.
|
|
* obsolete *
|
|
------------------------------------------------------------------------------}
|
|
procedure UpdateNoteBookClientWidget(ANoteBook: TObject);
|
|
var
|
|
ClientWidget: PGtkWidget;
|
|
NoteBookWidget: PGtkNotebook;
|
|
begin
|
|
if not TCustomNotebook(ANoteBook).HandleAllocated then exit;
|
|
NoteBookWidget := PGtkNotebook(TCustomNotebook(ANoteBook).Handle);
|
|
ClientWidget := nil;
|
|
SetFixedWidget(NoteBookWidget, ClientWidget);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function GetGtkNoteBookPageCount(ANoteBookWidget: PGtkNoteBook): integer;
|
|
|
|
Returns the number of pages in a PGtkNotebook
|
|
-------------------------------------------------------------------------------}
|
|
function GetGtkNoteBookPageCount(ANoteBookWidget: PGtkNoteBook): integer;
|
|
var
|
|
AListItem: PGList;
|
|
begin
|
|
Result:=0;
|
|
if ANoteBookWidget=nil then exit;
|
|
AListItem:=ANoteBookWidget^.children;
|
|
while AListItem<>nil do begin
|
|
inc(Result);
|
|
AListItem:=AListItem^.Next;
|
|
end;
|
|
end;
|
|
|
|
{$IFDef GTK1}
|
|
var
|
|
NoteBookCloseBtnPixmapImg: PGdkPixmap = nil;
|
|
NoteBookCloseBtnPixmapMask: PGdkPixmap = nil;
|
|
{$EndIf}
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure RemoveDummyNoteBookPage(NoteBookWidget: PGtkNotebook);
|
|
|
|
Removes the dummy page.
|
|
See also AddDummyNoteBookPage
|
|
-------------------------------------------------------------------------------}
|
|
procedure RemoveDummyNoteBookPage(NoteBookWidget: PGtkNotebook);
|
|
var
|
|
DummyWidget: PGtkWidget;
|
|
begin
|
|
DummyWidget:=GetGtkNoteBookDummyPage(NoteBookWidget);
|
|
if DummyWidget=nil then exit;
|
|
gtk_notebook_remove_page(NoteBookWidget,
|
|
gtk_notebook_page_num(NoteBookWidget,DummyWidget));
|
|
DummyWidget:=nil;
|
|
SetGtkNoteBookDummyPage(NoteBookWidget,DummyWidget);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
method GetNoteBookCloseBtnImage
|
|
Params:
|
|
Result: none
|
|
|
|
Loads the image for the close button in the tabs of the TCustomNoteBook(s).
|
|
-------------------------------------------------------------------------------}
|
|
{$IfDef GTK1}
|
|
procedure GetNoteBookCloseBtnImage(Window: PGdkWindow;
|
|
var Img, Mask: PGdkPixmap);
|
|
begin
|
|
if (NoteBookCloseBtnPixmapImg=nil)
|
|
and (Window<>nil) then begin
|
|
LoadXPMFromLazResource('tnotebook_close_tab',Window,
|
|
NoteBookCloseBtnPixmapImg,NoteBookCloseBtnPixmapMask);
|
|
end;
|
|
Img:=NoteBookCloseBtnPixmapImg;
|
|
Mask:=NoteBookCloseBtnPixmapMask;
|
|
end;
|
|
{$EndIF}
|
|
|
|
{-------------------------------------------------------------------------------
|
|
method UpdateNotebookPageTab
|
|
Params: ANoteBook: TCustomNotebook; APage: TCustomPage
|
|
Result: none
|
|
|
|
Updates the tab of a page of a notebook. This contains the image to the left
|
|
side, the label, the close button, the menu image and the menu label.
|
|
-------------------------------------------------------------------------------}
|
|
procedure UpdateNotebookPageTab(ANoteBook, APage: TObject);
|
|
var
|
|
TheNoteBook: TCustomNotebook;
|
|
ThePage: TCustomPage;
|
|
|
|
NoteBookWidget: PGtkWidget; // the notebook
|
|
PageWidget: PGtkWidget; // the page (content widget)
|
|
TabWidget: PGtkWidget; // the tab (hbox containing a pixmap, a label
|
|
// and a close button)
|
|
TabImageWidget: PGtkWidget; // the icon widget in the tab (a fixed widget)
|
|
TabLabelWidget: PGtkWidget; // the label in the tab
|
|
TabCloseBtnWidget: PGtkWidget;// the close button in the tab
|
|
TabCloseBtnImageWidget: PGtkWidget; // the pixmap in the close button
|
|
MenuWidget: PGtkWidget; // the popup menu (hbox containing a pixmap and
|
|
// a label)
|
|
MenuImageWidget: PGtkWidget; // the icon widget in the popup menu item (a fixed widget)
|
|
MenuLabelWidget: PGtkWidget; // the label in the popup menu item
|
|
|
|
procedure UpdateTabImage;
|
|
var
|
|
HasIcon: Boolean;
|
|
IconSize: TPoint;
|
|
ImageIndex: Integer;
|
|
begin
|
|
HasIcon:=false;
|
|
IconSize:=Point(0,0);
|
|
ImageIndex := TheNoteBook.GetImageIndex(ThePage.PageIndex);
|
|
if (TheNoteBook.Images<>nil)
|
|
and (ImageIndex >= 0)
|
|
and (ImageIndex < TheNoteBook.Images.Count) then
|
|
begin
|
|
// page has valid image
|
|
IconSize := Point(TheNoteBook.Images.Width, TheNoteBook.Images.Height);
|
|
HasIcon := (IconSize.X>0) and (IconSize.Y>0);
|
|
end;
|
|
|
|
if HasIcon then
|
|
begin
|
|
// page has an image
|
|
if TabImageWidget <> nil then
|
|
begin
|
|
// there is already an icon widget for the image in the tab
|
|
// -> resize the icon widget
|
|
gtk_widget_set_usize(TabImageWidget,IconSize.X,IconSize.Y);
|
|
end else
|
|
begin
|
|
// there is no pixmap for the image in the tab
|
|
// -> insert one ot the left side of the label
|
|
TabImageWidget := gtk_label_new(#0);
|
|
g_signal_connect(PgtkObject(TabImageWidget), 'expose_event',
|
|
TGTKSignalFunc(@PageIconWidgetExposeAfter), ThePage);
|
|
{$IFNDEF GTK2}
|
|
g_signal_connect(PgtkObject(TabImageWidget), 'draw',
|
|
TGTKSignalFunc(@PageIconWidgetDrawAfter), ThePage);
|
|
{$ENDIF}
|
|
gtk_object_set_data(PGtkObject(TabWidget), 'TabImage', TabImageWidget);
|
|
gtk_widget_set_usize(TabImageWidget, IconSize.X, IconSize.Y);
|
|
gtk_widget_show(TabImageWidget);
|
|
gtk_box_pack_start_defaults(PGtkBox(TabWidget), TabImageWidget);
|
|
gtk_box_reorder_child(PGtkBox(TabWidget), TabImageWidget, 0);
|
|
end;
|
|
if MenuImageWidget<>nil then
|
|
begin
|
|
// there is already an icon widget for the image in the menu
|
|
// -> resize the icon widget
|
|
gtk_widget_set_usize(MenuImageWidget, IconSize.X, IconSize.Y);
|
|
end else
|
|
begin
|
|
// there is no icon widget for the image in the menu
|
|
// -> insert one at the left side of the label
|
|
MenuImageWidget:=gtk_label_new(#0);
|
|
g_signal_connect_after(PgtkObject(MenuImageWidget), 'expose_event',
|
|
TGTKSignalFunc(@PageIconWidgetExposeAfter), ThePage);
|
|
{$IFNDEF GTK2}
|
|
g_signal_connect_after(PgtkObject(MenuImageWidget), 'draw',
|
|
TGTKSignalFunc(@PageIconWidgetDrawAfter), ThePage);
|
|
{$ENDIF}
|
|
gtk_widget_set_usize(MenuImageWidget,IconSize.X,IconSize.Y);
|
|
gtk_object_set_data(PGtkObject(MenuWidget),'TabImage',MenuImageWidget);
|
|
gtk_widget_show(MenuImageWidget);
|
|
gtk_box_pack_start_defaults(PGtkBox(MenuWidget),MenuImageWidget);
|
|
gtk_box_reorder_child(PGtkBox(MenuWidget),MenuImageWidget,0);
|
|
end;
|
|
end else
|
|
begin
|
|
// page does not have an image
|
|
if TabImageWidget<>nil then
|
|
begin
|
|
// there is a pixmap for an old image in the tab
|
|
// -> remove the icon widget
|
|
DestroyWidget(TabImageWidget);
|
|
gtk_object_set_data(PGtkObject(TabWidget), 'TabImage', nil);
|
|
TabImageWidget:=nil;
|
|
end;
|
|
if MenuImageWidget<>nil then
|
|
begin
|
|
// there is a pixmap for an old image in the menu
|
|
// -> remove the icon widget
|
|
DestroyWidget(MenuImageWidget);
|
|
gtk_object_set_data(PGtkObject(MenuWidget), 'TabImage', nil);
|
|
MenuImageWidget:=nil;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure UpdateTabLabel;
|
|
var
|
|
ACaption: String;
|
|
begin
|
|
ACaption := ThePage.Caption;
|
|
GTKWidgetSet.SetLabelCaption(PGtkLabel(TabLabelWidget), ACaption);
|
|
|
|
if MenuLabelWidget <> nil then
|
|
GTKWidgetSet.SetLabelCaption(PGtkLabel(MenuLabelWidget), ACaption);
|
|
end;
|
|
|
|
procedure UpdateTabCloseBtn;
|
|
var
|
|
{$IfDef GTK1}
|
|
Img: PGdkPixmap;
|
|
Mask: PGdkBitmap;
|
|
{$Else}
|
|
style: PGtkRcStyle;
|
|
{$EndIf}
|
|
begin
|
|
{$IfDef GTK1}
|
|
//debugln('UpdateTabCloseBtn ',GetWidgetDebugReport(NoteBookWidget));
|
|
GetNoteBookCloseBtnImage(GetControlWindow(NoteBookWidget), Img, Mask);
|
|
{$EndIf}
|
|
//debugln('UpdateTabCloseBtn ',dbgs(nboShowCloseButtons in TheNotebook.Options),' ',dbgs(Img<>nil));
|
|
if (nboShowCloseButtons in TheNotebook.Options)
|
|
{$ifdef GTK1}and (Img <> nil){$ENDIF} then
|
|
begin
|
|
// close buttons enabled
|
|
if TabCloseBtnWidget = nil then
|
|
begin
|
|
// there is no close button yet
|
|
// -> add one to the right side of the label in the tab
|
|
TabCloseBtnWidget := gtk_button_new;
|
|
gtk_button_set_relief(PGtkButton(TabCloseBtnWidget), GTK_RELIEF_NONE);
|
|
{$ifdef gtk2}
|
|
gtk_button_set_focus_on_click(PGtkButton(TabCloseBtnWidget), False);
|
|
style := gtk_widget_get_modifier_style(TabCloseBtnWidget);
|
|
style^.xthickness := 0;
|
|
style^.ythickness := 0;
|
|
gtk_widget_modify_style(TabCloseBtnWidget, style);
|
|
{$endif}
|
|
gtk_object_set_data(PGtkObject(TabWidget), 'TabCloseBtn',
|
|
TabCloseBtnWidget);
|
|
// put a pixmap into the button
|
|
{$IfDef GTK1}
|
|
TabCloseBtnImageWidget:=gtk_pixmap_new(Img,Mask);
|
|
{$Else}
|
|
TabCloseBtnImageWidget:=gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
|
|
{$EndIf}
|
|
gtk_object_set_data(PGtkObject(TabCloseBtnWidget),'TabCloseBtnImage',
|
|
TabCloseBtnImageWidget);
|
|
gtk_widget_show(TabCloseBtnImageWidget);
|
|
gtk_container_add(PGtkContainer(TabCloseBtnWidget),
|
|
TabCloseBtnImageWidget);
|
|
gtk_widget_show(TabCloseBtnWidget);
|
|
g_signal_connect(PGtkObject(TabCloseBtnWidget), 'clicked',
|
|
TGTKSignalFunc(@gtkNoteBookCloseBtnClicked), APage);
|
|
gtk_box_pack_start(PGtkBox(TabWidget), TabCloseBtnWidget, False, False, 0);
|
|
end;
|
|
end else begin
|
|
// close buttons disabled
|
|
if TabCloseBtnWidget<>nil then begin
|
|
// there is a close button
|
|
// -> remove it
|
|
gtk_object_set_data(PGtkObject(TabWidget), 'TabCloseBtn',
|
|
nil);
|
|
DestroyWidget(TabCloseBtnWidget);
|
|
TabCloseBtnWidget:=nil;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
ThePage := TCustomPage(APage);
|
|
TheNoteBook := TCustomNotebook(ANoteBook);
|
|
if (APage=nil) or (not ThePage.HandleAllocated) then exit;
|
|
if TheNoteBook=nil then begin
|
|
TheNoteBook:=TCustomNotebook(ThePage.Parent);
|
|
if TheNoteBook=nil then exit;
|
|
end;
|
|
NoteBookWidget:=PGtkWidget(TWinControl(TheNoteBook).Handle);
|
|
PageWidget:=PGtkWidget(TWinControl(ThePage).Handle);
|
|
|
|
// get the tab container and the tab components: pixmap, label and closebtn
|
|
TabWidget:=gtk_notebook_get_tab_label(PGtkNoteBook(NotebookWidget),
|
|
PageWidget);
|
|
if TabWidget<>nil then begin
|
|
TabImageWidget:=gtk_object_get_data(PGtkObject(TabWidget), 'TabImage');
|
|
TabLabelWidget:=gtk_object_get_data(PGtkObject(TabWidget), 'TabLabel');
|
|
TabCloseBtnWidget:=gtk_object_get_data(PGtkObject(TabWidget),'TabCloseBtn');
|
|
end else begin
|
|
TabImageWidget:=nil;
|
|
TabLabelWidget:=nil;
|
|
TabCloseBtnWidget:=nil;
|
|
end;
|
|
|
|
// get the menu container and its components: pixmap and label
|
|
MenuWidget:=gtk_notebook_get_menu_label(PGtkNoteBook(NotebookWidget),
|
|
PageWidget);
|
|
if MenuWidget<>nil then begin
|
|
MenuImageWidget:=gtk_object_get_data(PGtkObject(MenuWidget), 'TabImage');
|
|
MenuLabelWidget:=gtk_object_get_data(PGtkObject(MenuWidget), 'TabLabel');
|
|
end else begin
|
|
MenuImageWidget:=nil;
|
|
MenuLabelWidget:=nil;
|
|
end;
|
|
|
|
UpdateTabImage;
|
|
UpdateTabLabel;
|
|
UpdateTabCloseBtn;
|
|
end;
|
|
|
|
|
|
{-------------------------------------------------------------------------------
|
|
GetWidgetScreenPos
|
|
|
|
Returns the absolute left top position of a widget on the screen.
|
|
-------------------------------------------------------------------------------}
|
|
function GetWidgetOrigin(TheWidget: PGtkWidget): TPoint;
|
|
var
|
|
TheWindow: PGdkWindow;
|
|
{$IFDEF RaiseExceptionOnNilPointers}
|
|
LCLObject: TObject;
|
|
{$ENDIF}
|
|
begin
|
|
TheWindow:=GetControlWindow(TheWidget);
|
|
if TheWindow<>nil then begin
|
|
BeginGDKErrorTrap;
|
|
gdk_window_get_origin(TheWindow,@Result.X,@Result.Y);
|
|
EndGDKErrorTrap;
|
|
end else begin
|
|
{$IFDEF RaiseExceptionOnNilPointers}
|
|
LCLobject:=GetLCLObject(TheWidget);
|
|
DbgOut('GetWidgetOrigin ');
|
|
if LCLObject=nil then
|
|
DbgOut(' LCLObject=nil')
|
|
else if LCLObject is TControl then
|
|
DbgOut(' LCLObject=',TControl(LCLObject).Name,':',TControl(LCLObject).ClassName)
|
|
else
|
|
DbgOut(' LCLObject=',TControl(LCLObject).ClassName);
|
|
DebugLn('');
|
|
RaiseException('GetWidgetOrigin Window=nil');
|
|
{$ENDIF}
|
|
Result.X:=0;
|
|
Result.Y:=0;
|
|
end;
|
|
// check if the gdkwindow is the clientwindow of the parent
|
|
if gtk_widget_get_parent_window(TheWidget)=TheWindow then begin
|
|
// the widget is using its parent window
|
|
// -> adjust the coordinates
|
|
inc(Result.X,TheWidget^.Allocation.X);
|
|
inc(Result.Y,TheWidget^.Allocation.Y);
|
|
end;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
GetWidgetClientScreenPos
|
|
|
|
Returns the absolute left top position of a widget's client area
|
|
on the screen.
|
|
-------------------------------------------------------------------------------}
|
|
function GetWidgetClientOrigin(TheWidget: PGtkWidget): TPoint;
|
|
|
|
{$IFDEF Gtk2}
|
|
procedure GetNoteBookClientOrigin(NBWidget: PGtkNotebook);
|
|
var
|
|
PageIndex: LongInt;
|
|
PageWidget: PGtkWidget;
|
|
ClientWidget: PGTKWidget;
|
|
FrameBorders: TRect;
|
|
begin
|
|
// get current page
|
|
PageIndex:=gtk_notebook_get_current_page(NBWidget);
|
|
if PageIndex>=0 then
|
|
PageWidget:=gtk_notebook_get_nth_page(NBWidget,PageIndex)
|
|
else
|
|
PageWidget:=nil;
|
|
|
|
// get client widget of page
|
|
if (PageWidget<>nil) then
|
|
ClientWidget:=GetFixedWidget(PageWidget)
|
|
else
|
|
ClientWidget:=nil;
|
|
|
|
// Be careful while using ClientWidget here, it may be nil
|
|
if (ClientWidget<>nil) and (ClientWidget^.window<>nil) then
|
|
begin
|
|
// get the position of the current page
|
|
gdk_window_get_origin(ClientWidget^.window,@Result.X,@Result.Y);
|
|
if GTK_WIDGET_NO_WINDOW(ClientWidget)
|
|
then begin
|
|
Inc(Result.X, ClientWidget^.Allocation.X);
|
|
Inc(Result.Y, ClientWidget^.Allocation.Y);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
// use defaults
|
|
Result:=GetWidgetOrigin(TheWidget);
|
|
FrameBorders:=GetStyleNotebookFrameBorders;
|
|
GetWidgetClientOrigin.x:=Result.x+FrameBorders.Left;
|
|
GetWidgetClientOrigin.y:=Result.y+FrameBorders.Top;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
var
|
|
ClientWidget: PGtkWidget;
|
|
ClientWindow: PGdkWindow;
|
|
begin
|
|
ClientWidget := GetFixedWidget(TheWidget);
|
|
if ClientWidget <> TheWidget then
|
|
begin
|
|
ClientWindow := GetControlWindow(ClientWidget);
|
|
if ClientWindow <> nil then
|
|
begin
|
|
{$IFDEF DebugGDK}
|
|
BeginGDKErrorTrap;
|
|
{$ENDIF}
|
|
gdk_window_get_origin(ClientWindow, @Result.X, @Result.Y);
|
|
{$Ifdef GTK2}
|
|
if GTK_WIDGET_NO_WINDOW(ClientWidget) then
|
|
begin
|
|
Inc(Result.X, ClientWidget^.Allocation.X);
|
|
Inc(Result.Y, ClientWidget^.Allocation.Y);
|
|
end;
|
|
{$EndIf}
|
|
{$IFDEF DebugGDK}
|
|
EndGDKErrorTrap;
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
{$IFDEF Gtk2}
|
|
end
|
|
else
|
|
if GtkWidgetIsA(TheWidget,GTK_TYPE_NOTEBOOK) then
|
|
begin
|
|
GetNoteBookClientOrigin(PGtkNoteBook(TheWidget));
|
|
Exit;
|
|
{$ENDIF}
|
|
end;
|
|
Result := GetWidgetOrigin(TheWidget);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TranslateGdkPointToClientArea
|
|
|
|
Translates SourcePos relative to SourceWindow to a coordinate relative to the
|
|
client area of the LCL WinControl.
|
|
-------------------------------------------------------------------------------}
|
|
function TranslateGdkPointToClientArea(SourceWindow: PGdkWindow;
|
|
SourcePos: TPoint; DestinationWidget: PGtkWidget): TPoint;
|
|
var
|
|
SrcWindowOrigin: TPoint;
|
|
ClientAreaWindowOrigin: TPoint;
|
|
Src2ClientAreaVector: TPoint;
|
|
begin
|
|
if SourceWindow = nil then
|
|
begin
|
|
{$IFDEF RaiseExceptionOnNilPointers}
|
|
RaiseException('TranslateGdkPointToClientArea Window=nil');
|
|
{$ENDIF}
|
|
DebugLn('WARNING: TranslateGdkPointToClientArea SourceWindow=nil');
|
|
end;
|
|
gdk_window_get_origin(SourceWindow, @SrcWindowOrigin.X, @SrcWindowOrigin.Y);
|
|
|
|
ClientAreaWindowOrigin := GetWidgetClientOrigin(DestinationWidget);
|
|
Src2ClientAreaVector.X := ClientAreaWindowOrigin.X - SrcWindowOrigin.X;
|
|
Src2ClientAreaVector.Y := ClientAreaWindowOrigin.Y - SrcWindowOrigin.Y;
|
|
Result.X := SourcePos.X - Src2ClientAreaVector.X;
|
|
Result.Y := SourcePos.Y - Src2ClientAreaVector.Y;
|
|
end;
|
|
|
|
function SubtractScoll(AWidget: PGtkWidget; APosition: TPoint): TPoint;
|
|
begin
|
|
Result := APosition;
|
|
AWidget := gtk_object_get_data(PGTKObject(AWidget), odnScrollArea);
|
|
if GTK_IS_SCROLLED_WINDOW(AWidget) then
|
|
begin
|
|
with gtk_scrolled_window_get_hadjustment(PGtkScrolledWindow(AWidget))^ do
|
|
dec(Result.x, Trunc(value - lower));
|
|
with gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(AWidget))^ do
|
|
dec(Result.y, Trunc(value - lower));
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: UpdateMouseCaptureControl
|
|
Params: none
|
|
Returns: none
|
|
|
|
Sets MouseCaptureWidget to the current capturing widget.
|
|
------------------------------------------------------------------------------}
|
|
procedure UpdateMouseCaptureControl;
|
|
var
|
|
OldMouseCaptureWidget,
|
|
CurMouseCaptureWidget: PGtkWidget;
|
|
begin
|
|
OldMouseCaptureWidget:=MouseCaptureWidget;
|
|
CurMouseCaptureWidget:=gtk_grab_get_current;
|
|
|
|
if OldMouseCaptureWidget<>CurMouseCaptureWidget then begin
|
|
// the mouse grab changed
|
|
// -> this means the gtk itself has changed the mouse grab
|
|
{$IFDEF VerboseMouseCapture}
|
|
DebugLn('UpdateMouseCaptureControl Capture changed from ',
|
|
'[',GetWidgetDebugReport(OldMouseCaptureWidget),' type=',MouseCaptureTypeNames[MouseCaptureType],']',
|
|
' to [',GetWidgetDebugReport(CurMouseCaptureWidget),' type=GTK]');
|
|
if CurMouseCaptureWidget<>nil then
|
|
DebugLn('parent ', GetWidgetDebugReport(CurMouseCaptureWidget^.Parent));
|
|
{$ENDIF}
|
|
|
|
// notify the new capture control
|
|
MouseCaptureWidget:=CurMouseCaptureWidget;
|
|
MouseCaptureType:=mctGTK;
|
|
if MouseCaptureWidget<>nil then begin
|
|
// the MouseCaptureWidget is probably not a main widget
|
|
SendMessage(HWnd(PtrUInt(MouseCaptureWidget)), LM_CAPTURECHANGED, 0,
|
|
HWnd(PtrUInt(OldMouseCaptureWidget)));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure IncreaseMouseCaptureIndex;
|
|
begin
|
|
if MouseCaptureIndex<$ffffffff then
|
|
inc(MouseCaptureIndex)
|
|
else
|
|
MouseCaptureIndex:=0;
|
|
end;
|
|
|
|
procedure CaptureMouseForWidget(Widget: PGtkWidget; Owner: TMouseCaptureType);
|
|
var
|
|
CaptureWidget: PGtkWidget;
|
|
NowIndex: Cardinal;
|
|
begin
|
|
{$IFDEF VerboseMouseCapture}
|
|
DebugLn('CaptureMouseForWidget START ',GetWidgetDebugReport(Widget));
|
|
{$ENDIF}
|
|
if not (Owner in [mctGTKIntf,mctLCL]) then exit;
|
|
// not every widget can capture the mouse
|
|
CaptureWidget:=GetDefaultMouseCaptureWidget(Widget);
|
|
if CaptureWidget=nil then exit;
|
|
|
|
UpdateMouseCaptureControl;
|
|
if (MouseCaptureType<>mctGTK) then begin
|
|
// we are capturing
|
|
if (MouseCaptureWidget=CaptureWidget) then begin
|
|
// we are already capturing this widget
|
|
exit;
|
|
end;
|
|
// release old capture
|
|
ReleaseMouseCapture;
|
|
end;
|
|
|
|
{$IFDEF VerboseMouseCapture}
|
|
DebugLn('CaptureMouseForWidget Start Capturing for ',GetWidgetDebugReport(CaptureWidget));
|
|
{$ENDIF}
|
|
IncreaseMouseCaptureIndex;
|
|
NowIndex:=MouseCaptureIndex;
|
|
if not gtk_widget_has_focus(CaptureWidget) then
|
|
gtk_widget_grab_focus(CaptureWidget);
|
|
if NowIndex=MouseCaptureIndex then begin
|
|
{$IFDEF VerboseMouseCapture}
|
|
DebugLn('CaptureMouseForWidget Commit Capturing for ',GetWidgetDebugReport(CaptureWidget));
|
|
{$ENDIF}
|
|
MouseCaptureWidget:=CaptureWidget;
|
|
MouseCaptureType:=Owner;
|
|
gtk_grab_add(CaptureWidget);
|
|
end;
|
|
end;
|
|
|
|
function GetDefaultMouseCaptureWidget(Widget: PGtkWidget
|
|
): PGtkWidget;
|
|
var
|
|
WidgetInfo: PWinWidgetInfo;
|
|
LCLObject: TObject;
|
|
begin
|
|
Result:=nil;
|
|
if Widget=nil then exit;
|
|
if GtkWidgetIsA(Widget,GTKAPIWidget_Type) then begin
|
|
WidgetInfo:=GetWidgetInfo(Widget,false);
|
|
if WidgetInfo<>nil then
|
|
Result:=WidgetInfo^.CoreWidget;
|
|
exit;
|
|
end;
|
|
LCLObject:=GetNearestLCLObject(Widget);
|
|
if LCLObject=nil then exit;
|
|
if (TWinControl(LCLObject) is TCustomSplitter) and (TWinControl(LCLObject).HandleAllocated)
|
|
then begin
|
|
WidgetInfo:=GetWidgetInfo(PGtkWidget(TWinControl(LCLObject).Handle),false);
|
|
if WidgetInfo<>nil then
|
|
Result:=WidgetInfo^.CoreWidget;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure ReleaseMouseCapture;
|
|
|
|
If the current mouse capture was captured by the LCL or the gtk intf, release
|
|
the capture. Don't release mouse captures of the gtk, because captures must
|
|
be balanced and this is already done by the gtk.
|
|
------------------------------------------------------------------------------}
|
|
procedure ReleaseMouseCapture;
|
|
var
|
|
OldMouseCaptureWidget: PGtkWidget;
|
|
Info: PWidgetInfo;
|
|
begin
|
|
{$IFDEF VerboseMouseCapture}
|
|
DebugLn('ReleaseMouseCapture ',dbgs(ord(MouseCaptureType)),' MouseCaptureWidget=[',GetWidgetDebugReport(MouseCaptureWidget),']');
|
|
{$ENDIF}
|
|
if MouseCaptureType=mctGTK then
|
|
begin
|
|
Info := GetWidgetInfo(gtk_grab_get_current, false);
|
|
if (Info <> nil) and (Info^.CoreWidget <> nil) then
|
|
begin
|
|
if GtkWidgetIsA(Info^.CoreWidget, gtk_list_get_type) then
|
|
begin
|
|
// Paul Ishenin:
|
|
// listbox grabs pointer and other control for itself, when we click on listbox item
|
|
// also it changes its state to drag_selection
|
|
// this is not expected in LCL and as result cause bugs, such as 7892
|
|
// so we need end drag selection manually
|
|
OldMouseCaptureWidget := Info^.CoreWidget;
|
|
gtk_list_end_drag_selection(PGtkList(OldMouseCaptureWidget));
|
|
end;
|
|
end;
|
|
exit;
|
|
end;
|
|
OldMouseCaptureWidget:=MouseCaptureWidget;
|
|
MouseCaptureWidget:=nil;
|
|
MouseCaptureType:=mctGTK;
|
|
if OldMouseCaptureWidget<>nil then
|
|
gtk_grab_remove(OldMouseCaptureWidget);
|
|
// tell the LCL
|
|
SetCaptureControl(nil);
|
|
end;
|
|
|
|
procedure ReleaseCaptureWidget(Widget : PGtkWidget);
|
|
begin
|
|
if (Widget=nil)
|
|
or ((MouseCaptureWidget<>Widget) and (MouseCaptureWidget<>Widget^.parent))
|
|
then
|
|
exit;
|
|
DebugLn('ReleaseCaptureWidget ',GetWidgetDebugReport(Widget));
|
|
ReleaseMouseCapture;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure: SignalConnect
|
|
Params: AWidget: PGTKWidget
|
|
ASignal: PChar
|
|
AProc: Pointer
|
|
AInfo: PWidgetInfo
|
|
Returns: Nothing
|
|
|
|
Connects a gtk signal handler.
|
|
This is a wrapper to get around gtk casting
|
|
-------------------------------------------------------------------------------}
|
|
procedure SignalConnect(const AWidget: PGTKWidget; const ASignal: PChar;
|
|
const AProc: Pointer; const AInfo: PWidgetInfo);
|
|
begin
|
|
g_signal_connect(PGtkObject(AWidget), ASignal, TGTKSignalFunc(AProc), AInfo);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure: SignalConnectAfter
|
|
Params: AWidget: PGTKWidget
|
|
ASignal: PChar
|
|
AProc: Pointer
|
|
AInfo: PGtkWSWidgetInfo
|
|
Returns: Nothing
|
|
|
|
Connects a gtk signal after handler.
|
|
This is a wrapper to get around gtk casting
|
|
-------------------------------------------------------------------------------}
|
|
procedure SignalConnectAfter(const AWidget:PGTKWidget; const ASignal: PChar;
|
|
const AProc: Pointer; const AInfo: PWidgetInfo);
|
|
begin
|
|
g_signal_connect_after(PGTKObject(AWidget), ASignal, TGTKSignalFunc(AProc), AInfo);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure ConnectSignal(const AnObject:PGTKObject; const ASignal: PChar;
|
|
const ACallBackProc: Pointer; const ReqSignalMask: TGdkEventMask;
|
|
Flags: TConnectSignalFlags);
|
|
|
|
Connects a gtk signal handler.
|
|
-------------------------------------------------------------------------------}
|
|
procedure InitDesignSignalMasks;
|
|
var
|
|
SignalType: TDesignSignalType;
|
|
begin
|
|
DesignSignalMasks[dstUnknown]:=0;
|
|
for SignalType:=Low(TDesignSignalType) to High(TDesignSignalType) do
|
|
DesignSignalMasks[SignalType]:=1 shl ord(SignalType);
|
|
end;
|
|
|
|
function DesignSignalNameToType(Name: PChar; After: boolean): TDesignSignalType;
|
|
begin
|
|
for Result:=Low(TDesignSignalType) to High(TDesignSignalType) do
|
|
if ComparePChar(DesignSignalNames[Result],Name)
|
|
and (DesignSignalAfter[Result]=After) then exit;
|
|
Result:=dstUnknown;
|
|
end;
|
|
|
|
function GetDesignSignalMask(Widget: PGtkWidget): TDesignSignalMask;
|
|
begin
|
|
Result:=TDesignSignalMask(PtrUInt(gtk_object_get_data(PGtkObject(Widget),
|
|
'LCLDesignMask')));
|
|
end;
|
|
|
|
procedure SetDesignSignalMask(Widget: PGtkWidget; NewMask: TDesignSignalMask);
|
|
begin
|
|
gtk_object_set_data(PGtkObject(Widget),'LCLDesignMask',Pointer(PtrInt(NewMask)));
|
|
end;
|
|
|
|
function GetDesignOnlySignalFlag(Widget: PGtkWidget;
|
|
DesignSignalType: TDesignSignalType): boolean;
|
|
begin
|
|
Result:=(GetDesignSignalMask(Widget)
|
|
and DesignSignalMasks[DesignSignalType])<>0;
|
|
end;
|
|
|
|
function SignalConnected(const AnObject:PGTKObject; const ASignal: PChar;
|
|
const ACallBackProc: Pointer; const ALCLObject: TObject;
|
|
const ASFlags: TConnectSignalFlags): boolean;
|
|
{$IFDEF Gtk1}
|
|
var
|
|
Handler: PGTKHandler;
|
|
SignalID: guint;
|
|
begin
|
|
Handler := gtk_object_get_data_by_id (AnObject, gtk_handler_quark);
|
|
SignalID := g_signal_lookup(ASignal, GTK_OBJECT_TYPE(AnObject));
|
|
if (SignalID<0) or (SignalID>$ffffff) then
|
|
RaiseGDBException('SignalConnected');
|
|
|
|
while (Handler <> nil) do begin
|
|
with Handler^ do
|
|
begin
|
|
// check if signal is already connected
|
|
//debugln('ConnectSignal Id=',dbgs(Id));
|
|
if (Id > 0)
|
|
and (Signal_ID = SignalID)
|
|
and (Func = TGTKSignalFunc(ACallBackProc))
|
|
and (func_data = Pointer(ALCLObject))
|
|
and (((flags and bmSignalAfter)<>0)=(csfAfter in ASFlags))
|
|
then begin
|
|
// signal is already connected
|
|
Result:=true;
|
|
Exit;
|
|
end;
|
|
|
|
Handler := Next;
|
|
end;
|
|
end;
|
|
Result:=false;
|
|
end;
|
|
{$ELSE}
|
|
begin
|
|
Result:=g_signal_handler_find(AnObject,
|
|
G_SIGNAL_MATCH_FUNC or G_SIGNAL_MATCH_DATA,
|
|
0,0,nil,ACallBackProc,ALCLObject)<>0;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure ConnectSignal(const AnObject:PGTKObject; const ASignal: PChar;
|
|
const ACallBackProc: Pointer; const ALCLObject: TObject;
|
|
const AReqSignalMask: TGdkEventMask; const ASFlags: TConnectSignalFlags);
|
|
var
|
|
WinWidgetInfo: PWinWidgetInfo;
|
|
MainWidget: PGtkWidget;
|
|
OldDesignMask, NewDesignMask: TDesignSignalMask;
|
|
DesignSignalType: TDesignSignalType;
|
|
RealizeConnected: Boolean;
|
|
HasRealizeSignal: Boolean;
|
|
begin
|
|
if ACallBackProc = nil then
|
|
RaiseGDBException('ConnectSignal');
|
|
|
|
// first loop through the handlers to:
|
|
// - check if a handler already exists
|
|
// - Find the realize handler to change data
|
|
DesignSignalType:=DesignSignalNameToType(ASignal,csfAfter in ASFlags);
|
|
if SignalConnected(AnObject,ASignal,ACallBackProc,ALCLObject,ASFlags) then
|
|
begin
|
|
// signal is already connected
|
|
// update the DesignSignalMask
|
|
if (DesignSignalType <> dstUnknown)
|
|
and (not (csfDesignOnly in ASFlags))
|
|
then begin
|
|
OldDesignMask := GetDesignSignalMask(PGtkWidget(AnObject));
|
|
NewDesignMask :=OldDesignMask and not DesignSignalMasks[DesignSignalType];
|
|
if OldDesignMask <> NewDesignMask
|
|
then SetDesignSignalMask(PGtkWidget(AnObject),NewDesignMask);
|
|
end;
|
|
Exit;
|
|
end;
|
|
|
|
// if we are here, then no handler was defined yet
|
|
// -> register handler
|
|
//if (Msg=LM_LBUTTONUP) then DebugLn('CONNECT ',ReqSignalMask,' Widget=',DbgS(AnObject));
|
|
//debugln('ConnectSignal ',DbgSName(ALCLObject),' ',ASignal,' After=',dbgs(csfAfter in ASFlags));
|
|
if csfAfter in ASFlags then
|
|
g_signal_connect_after(AnObject, ASignal,
|
|
TGTKSignalFunc(ACallBackProc), ALCLObject)
|
|
else
|
|
g_signal_connect (AnObject, ASignal,
|
|
TGTKSignalFunc(ACallBackProc), ALCLObject);
|
|
|
|
// update signal mask which will be set in the realize handler
|
|
if (csfUpdateSignalMask in ASFlags) and (AReqSignalMask <> 0)
|
|
then begin
|
|
MainWidget := GetMainWidget(PGtkWidget(AnObject));
|
|
if MainWidget=nil
|
|
then MainWidget := PGtkWidget(AnObject);
|
|
WinWidgetInfo := GetWidgetInfo(MainWidget,true);
|
|
WinWidgetInfo^.EventMask := WinWidgetInfo^.EventMask or AReqSignalMask;
|
|
end;
|
|
|
|
// -> register realize handler
|
|
if (csfConnectRealize in ASFlags) then begin
|
|
HasRealizeSignal:=g_signal_lookup('realize', GTK_OBJECT_TYPE(AnObject))>0;
|
|
if HasRealizeSignal then begin
|
|
RealizeConnected:=SignalConnected(AnObject,'realize',@GTKRealizeCB,
|
|
ALCLObject,[]);
|
|
if not RealizeConnected then begin
|
|
g_signal_connect(AnObject, 'realize',
|
|
TGTKSignalFunc(@GTKRealizeCB), ALCLObject);
|
|
g_signal_connect_after(AnObject, 'realize',
|
|
TGTKSignalFunc(@GTKRealizeAfterCB), ALCLObject);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// update the DesignSignalMask
|
|
if (DesignSignalType <> dstUnknown)
|
|
then begin
|
|
OldDesignMask:=GetDesignSignalMask(PGtkWidget(AnObject));
|
|
if csfDesignOnly in ASFlags then
|
|
NewDesignMask:=OldDesignMask or DesignSignalMasks[DesignSignalType]
|
|
else
|
|
NewDesignMask:=OldDesignMask and not DesignSignalMasks[DesignSignalType];
|
|
if OldDesignMask<>NewDesignMask then
|
|
SetDesignSignalMask(PGtkWidget(AnObject),NewDesignMask);
|
|
end;
|
|
end;
|
|
|
|
procedure ConnectSignal(const AnObject:PGTKObject; const ASignal: PChar;
|
|
const ACallBackProc: Pointer; const ALCLObject: TObject;
|
|
const AReqSignalMask: TGdkEventMask);
|
|
begin
|
|
ConnectSignal(AnObject,ASignal,ACallBackProc, ALCLObject, AReqSignalMask,
|
|
[csfConnectRealize,csfUpdateSignalMask]);
|
|
end;
|
|
|
|
procedure ConnectSignalAfter(const AnObject:PGTKObject; const ASignal: PChar;
|
|
const ACallBackProc: Pointer; const ALCLObject: TObject;
|
|
const AReqSignalMask: TGdkEventMask);
|
|
begin
|
|
ConnectSignal(AnObject,ASignal,ACallBackProc, ALCLObject, AReqSignalMask,
|
|
[csfConnectRealize,csfUpdateSignalMask,csfAfter]);
|
|
end;
|
|
|
|
procedure ConnectSignal(const AnObject:PGTKObject; const ASignal: PChar;
|
|
const ACallBackProc: Pointer; const ALCLObject: TObject);
|
|
begin
|
|
ConnectSignal(AnObject,ASignal,ACallBackProc, ALCLObject, 0);
|
|
end;
|
|
|
|
procedure ConnectSignalAfter(const AnObject:PGTKObject; const ASignal: PChar;
|
|
const ACallBackProc: Pointer; const ALCLObject: TObject);
|
|
begin
|
|
ConnectSignalAfter(AnObject,ASignal,ACallBackProc, ALCLObject, 0);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure: ConnectInternalWidgetsSignals
|
|
Params: AWidget: PGtkWidget; AWinControl: TWinControl
|
|
Returns: Nothing
|
|
|
|
Connects hidden child widgets signals.
|
|
Many gtk widgets create internally child widgets (e.g. scrollbars). In
|
|
Design mode these widgets should not auto react themselves, but instead send
|
|
messages to the lcl. Therefore these widgets are connected also to our
|
|
signal handlers.
|
|
This procedure is called by the realize-after handler of all LCL widgets
|
|
and each time the design mode of a LCL control changes.
|
|
------------------------------------------------------------------------------}
|
|
procedure ConnectInternalWidgetsSignals(AWidget: PGtkWidget;
|
|
AWinControl: TWinControl);
|
|
|
|
function WidgetIsInternal(TheWidget: PGtkWidget): boolean;
|
|
begin
|
|
Result:=(TheWidget<>nil)
|
|
and (PGtkWidget(AWinControl.Handle)<>TheWidget)
|
|
and (GetMainWidget(TheWidget)=nil);
|
|
end;
|
|
|
|
procedure ConnectSignals(TheWidget: PGtkWidget); forward;
|
|
|
|
procedure ConnectChilds(TheWidget: PGtkWidget);
|
|
var
|
|
ScrolledWindow: PGtkScrolledWindow;
|
|
BinWidget: PGtkBin;
|
|
{$IFDEF Gtk2}
|
|
ChildEntry2: PGList;
|
|
{$ELSE}
|
|
ChildEntry: PGSList;
|
|
{$ENDIF}
|
|
ChildWidget: PGtkWidget;
|
|
begin
|
|
//if AWinControl is TListView then DebugLn('ConnectChilds A ',DbgS(TheWidget));
|
|
if GtkWidgetIsA(TheWidget,GTK_TYPE_CONTAINER) then begin
|
|
//if AWinControl is TListView then DebugLn('ConnectChilds B ');
|
|
// this is a container widget -> connect all childs
|
|
{$IFDEF Gtk2}
|
|
ChildEntry2:=gtk_container_get_children(PGtkContainer(TheWidget));
|
|
while ChildEntry2<>nil do begin
|
|
ChildWidget:=PGtkWidget(ChildEntry2^.Data);
|
|
if ChildWidget<>TheWidget then
|
|
ConnectSignals(ChildWidget);
|
|
ChildEntry2:=ChildEntry2^.Next;
|
|
end;
|
|
{$ELSE}
|
|
ChildEntry:=PGtkContainer(TheWidget)^.resize_widgets;
|
|
while ChildEntry<>nil do begin
|
|
ChildWidget:=PGtkWidget(ChildEntry^.Data);
|
|
ConnectSignals(ChildWidget);
|
|
ChildEntry:=ChildEntry^.Next;
|
|
end;
|
|
{$endif}
|
|
end;
|
|
if GtkWidgetIsA(TheWidget,GTK_TYPE_BIN) then begin
|
|
//if AWinControl is TListView then DebugLn('ConnectChilds C ');
|
|
BinWidget:=PGtkBin(TheWidget);
|
|
ConnectSignals(BinWidget^.child);
|
|
end;
|
|
if GtkWidgetIsA(TheWidget,GTK_TYPE_SCROLLED_WINDOW) then begin
|
|
//if AWinControl is TListView then DebugLn('ConnectChilds D ');
|
|
ScrolledWindow:=PGtkScrolledWindow(TheWidget);
|
|
ConnectSignals(ScrolledWindow^.hscrollbar);
|
|
ConnectSignals(ScrolledWindow^.vscrollbar);
|
|
end;
|
|
if GtkWidgetIsA(TheWidget,GTK_TYPE_COMBO) then begin
|
|
//if AWinControl is TListView then DebugLn('ConnectChilds E ');
|
|
ConnectSignals(PGtkCombo(TheWidget)^.entry);
|
|
ConnectSignals(PGtkCombo(TheWidget)^.button);
|
|
end;
|
|
end;
|
|
|
|
procedure ConnectSignals(TheWidget: PGtkWidget);
|
|
var
|
|
LCLObject, HiddenLCLObject: TObject;
|
|
DesignSignalType: TDesignSignalType;
|
|
DesignFlags: TConnectSignalFlags;
|
|
begin
|
|
//if AWinControl is TListView then DebugLn('ConnectSignals A ',DbgS(TheWidget));
|
|
if TheWidget=nil then exit;
|
|
|
|
// check if TheWidget belongs to another LCL object
|
|
LCLObject:=GetLCLObject(TheWidget);
|
|
HiddenLCLObject:=GetHiddenLCLObject(TheWidget);
|
|
if (LCLObject<>nil) and (LCLObject<>AWinControl) then begin
|
|
exit;
|
|
end;
|
|
if (HiddenLCLObject<>nil) and (HiddenLCLObject<>AWinControl) then begin
|
|
exit;
|
|
end;
|
|
|
|
//if AWinControl is TListView then DebugLn('ConnectSignals B ',DbgS(TheWidget));
|
|
// connect signals needed for design mode:
|
|
for DesignSignalType:=Low(TDesignSignalType) to High(TDesignSignalType) do
|
|
begin
|
|
if DesignSignalType=dstUnknown then continue;
|
|
if (not DesignSignalBefore[DesignSignalType])
|
|
and (not DesignSignalAfter[DesignSignalType]) then
|
|
continue;
|
|
|
|
DesignFlags:=[csfDesignOnly];
|
|
if DesignSignalAfter[DesignSignalType] then
|
|
Include(DesignFlags,csfAfter);
|
|
ConnectSignal(PGtkObject(TheWidget),DesignSignalNames[DesignSignalType],
|
|
DesignSignalFuncs[DesignSignalType],AWinControl,0,
|
|
DesignFlags);
|
|
end;
|
|
|
|
if WidgetIsInternal(TheWidget) then
|
|
// mark widget as 'hidden' connected
|
|
SetHiddenLCLObject(TheWidget,AWinControl);
|
|
|
|
// connect recursively ...
|
|
ConnectChilds(TheWidget);
|
|
end;
|
|
|
|
begin
|
|
if (AWinControl=nil) or (AWidget=nil)
|
|
or (not (csDesigning in AWinControl.ComponentState)) then exit;
|
|
ConnectSignals(AWidget);
|
|
end;
|
|
|
|
// ----------------------------------------------------------------------
|
|
// The Accelgroup and AccelKey is needed by menus
|
|
// ----------------------------------------------------------------------
|
|
function GetAccelGroup(const Widget: PGtkWidget;
|
|
CreateIfNotExists: boolean): PGTKAccelGroup;
|
|
begin
|
|
Result := PGTKAccelGroup(gtk_object_get_data(PGtkObject(Widget),'AccelGroup'));
|
|
if (Result=nil) and CreateIfNotExists then begin
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('GetAccelGroup CREATING Widget=',DbgS(Widget),' CreateIfNotExists=',dbgs(CreateIfNotExists));
|
|
{$ENDIF}
|
|
Result:=gtk_accel_group_new;
|
|
SetAccelGroup(Widget,Result);
|
|
if GtkWidgetIsA(Widget,GTK_TYPE_WINDOW) then
|
|
ShareWindowAccelGroups(Widget);
|
|
end;
|
|
end;
|
|
|
|
procedure SetAccelGroup(const Widget: PGtkWidget;
|
|
const AnAccelGroup: PGTKAccelGroup);
|
|
begin
|
|
if (Widget = nil) then exit;
|
|
gtk_object_set_data(PGtkObject(Widget), 'AccelGroup', AnAccelGroup);
|
|
if AnAccelGroup<>nil then begin
|
|
// attach group to widget
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn(['SetAccelGroup AnAccelGroup=',DbgS(AnAccelGroup),' IsMenu=',GtkWidgetIsA(Widget,GTK_TYPE_MENU)]);
|
|
{$ENDIF}
|
|
if GtkWidgetIsA(Widget,GTK_TYPE_MENU) then
|
|
gtk_menu_set_accel_group(PGtkMenu(Widget), AnAccelGroup)
|
|
else begin
|
|
{$IfDef GTK2}
|
|
Assert(GtkWidgetIsA(Widget,GTK_TYPE_WINDOW));
|
|
gtk_window_add_accel_group(GTK_WINDOW(widget), AnAccelGroup);
|
|
{$else}
|
|
gtk_accel_group_attach(AnAccelGroup, PGtkObject(Widget));
|
|
{$endif}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure FreeAccelGroup(const Widget: PGtkWidget);
|
|
var
|
|
AccelGroup: PGTKAccelGroup;
|
|
begin
|
|
AccelGroup:=GetAccelGroup(Widget,false);
|
|
if AccelGroup<>nil then begin
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('FreeAccelGroup AccelGroup=',DbgS(AccelGroup));
|
|
{$ENDIF}
|
|
gtk_accel_group_unref(AccelGroup);
|
|
SetAccelGroup(Widget,nil);
|
|
end;
|
|
end;
|
|
|
|
procedure ShareWindowAccelGroups(AWindow: PGtkWidget);
|
|
|
|
procedure AttachUnique(TheWindow: PGtkWidget; TheAccelGroup: PGTKAccelGroup);
|
|
begin
|
|
{$IfDef GTK2}
|
|
if (TheWindow=nil) or (TheAccelGroup=nil)
|
|
or (TheAccelGroup^.acceleratables=nil)
|
|
or (g_slist_find(TheAccelGroup^.acceleratables, TheWindow)=nil)
|
|
then
|
|
exit;
|
|
gtk_window_add_accel_group(GTK_WINDOW(TheWindow), TheAccelGroup);
|
|
{$else}
|
|
if (TheAccelGroup=nil)
|
|
or ((TheAccelGroup^.attach_objects<>nil)
|
|
and (g_slist_find(TheAccelGroup^.attach_objects, TheWindow)<>nil))
|
|
then
|
|
exit;
|
|
gtk_accel_group_attach(TheAccelGroup, PGtkObject(TheWindow));
|
|
{$endif}
|
|
end;
|
|
|
|
var
|
|
TheForm, CurForm: TCustomForm;
|
|
i: integer;
|
|
TheAccelGroup, CurAccelGroup: PGTKAccelGroup;
|
|
CurWindow: PGtkWidget;
|
|
begin
|
|
TheForm:=TCustomForm(GetLCLObject(AWindow));
|
|
|
|
// check if visible TCustomForm (not frame)
|
|
if (TheForm=nil) or (not (TheForm is TCustomForm))
|
|
or (not TheForm.Visible) or (TheForm.Parent<>nil)
|
|
or (csDesigning in TheForm.ComponentState)
|
|
then
|
|
exit;
|
|
|
|
// check if modal form
|
|
if fsModal in TheForm.FormState then begin
|
|
// a modal form does not share accelerators
|
|
exit;
|
|
end;
|
|
|
|
// check if there is an accelerator group
|
|
TheAccelGroup:=GetAccelGroup(AWindow,false);
|
|
|
|
// this is a normal form
|
|
// -> share accelerators with all other visible normal forms
|
|
for i:=0 to Screen.FormCount-1 do begin
|
|
CurForm:=Screen.Forms[i];
|
|
if (CurForm=TheForm)
|
|
or (not CurForm.HandleAllocated)
|
|
or (not CurForm.Visible)
|
|
or (fsModal in CurForm.FormState)
|
|
or (CurForm.Parent<>nil)
|
|
or (csDesigning in CurForm.ComponentState)
|
|
then continue;
|
|
|
|
CurWindow:=PGtkWidget(CurForm.Handle);
|
|
CurAccelGroup:=GetAccelGroup(CurWindow,false);
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('ShareWindowAccelGroups ',TheForm.Name,':',TheForm.ClassName,
|
|
' <-> ',CurForm.Name,':',CurForm.ClassName);
|
|
{$ENDIF}
|
|
|
|
// cross connect
|
|
AttachUnique(CurWindow,TheAccelGroup);
|
|
AttachUnique(AWindow,CurAccelGroup);
|
|
end;
|
|
end;
|
|
|
|
procedure UnshareWindowAccelGroups(AWindow: PGtkWidget);
|
|
|
|
procedure Detach(TheWindow: PGtkWidget; TheAccelGroup: PGTKAccelGroup);
|
|
begin
|
|
{$IfDef GTK2}
|
|
if (TheWindow=nil) or (TheAccelGroup=nil)
|
|
or (TheAccelGroup^.acceleratables=nil)
|
|
or (g_slist_find(TheAccelGroup^.acceleratables, TheWindow)=nil)
|
|
then
|
|
exit;
|
|
gtk_window_remove_accel_group(GTK_WINDOW(TheWindow), TheAccelGroup);
|
|
{$else}
|
|
if (TheAccelGroup=nil)
|
|
or (TheAccelGroup^.attach_objects=nil)
|
|
or (g_slist_find(TheAccelGroup^.attach_objects, TheWindow)=nil)
|
|
then
|
|
exit;
|
|
gtk_accel_group_detach(TheAccelGroup, PGtkObject(TheWindow));
|
|
{$endif}
|
|
end;
|
|
|
|
var
|
|
TheForm, CurForm: TCustomForm;
|
|
i: integer;
|
|
TheAccelGroup, CurAccelGroup: PGTKAccelGroup;
|
|
CurWindow: PGtkWidget;
|
|
begin
|
|
TheForm:=TCustomForm(GetLCLObject(AWindow));
|
|
|
|
// check if TCustomForm
|
|
if (TheForm=nil) or (not (TheForm is TCustomForm))
|
|
then exit;
|
|
|
|
TheAccelGroup:=GetAccelGroup(AWindow,false);
|
|
|
|
// -> unshare accelerators with all other forms
|
|
for i:=0 to Screen.FormCount-1 do begin
|
|
CurForm:=Screen.Forms[i];
|
|
if (CurForm=TheForm)
|
|
or (not CurForm.HandleAllocated)
|
|
then continue;
|
|
|
|
CurWindow:=PGtkWidget(CurForm.Handle);
|
|
CurAccelGroup:=GetAccelGroup(CurWindow,false);
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('UnshareWindowAccelGroups ',TheForm.Name,':',TheForm.ClassName,
|
|
' <-> ',CurForm.Name,':',CurForm.ClassName);
|
|
{$ENDIF}
|
|
|
|
// unlink
|
|
Detach(CurWindow,TheAccelGroup);
|
|
Detach(AWindow,CurAccelGroup);
|
|
end;
|
|
end;
|
|
|
|
function GetAccelGroupForComponent(Component: TComponent;
|
|
CreateIfNotExists: boolean): PGTKAccelGroup;
|
|
var
|
|
Control: TControl;
|
|
MenuItem: TMenuItem;
|
|
Form: TCustomForm;
|
|
Menu: TMenu;
|
|
begin
|
|
Result:=nil;
|
|
if Component=nil then exit;
|
|
|
|
if Component is TMenuItem then begin
|
|
MenuItem:=TMenuItem(Component);
|
|
Menu:=MenuItem.GetParentMenu;
|
|
if (Menu=nil) or (Menu.Parent=nil) then exit;
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('GetAccelGroupForComponent A ',Component.Name,':',Component.ClassName);
|
|
{$ENDIF}
|
|
Result:=GetAccelGroupForComponent(Menu.Parent,CreateIfNotExists);
|
|
end else if Component is TControl then begin
|
|
Control:=TControl(Component);
|
|
while Control.Parent<>nil do Control:=Control.Parent;
|
|
if Control is TCustomForm then begin
|
|
Form:=TCustomForm(Control);
|
|
if Form.HandleAllocated then begin
|
|
Result:=GetAccelGroup(PGtkWidget(Form.Handle),CreateIfNotExists);
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('GetAccelGroupForComponent C ',Component.Name,':',Component.ClassName);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
end;
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('GetAccelGroupForComponent END ',Component.Name,':',Component.ClassName,' Result=',DbgS(Result));
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function GetAccelKey(Widget: PGtkWidget): PAcceleratorKey;
|
|
begin
|
|
Result := PAcceleratorKey(gtk_object_get_data(PGtkObject(Widget),'AccelKey'));
|
|
end;
|
|
|
|
function SetAccelKey(const Widget: PGtkWidget;
|
|
Key: guint; Mods: TGdkModifierType; const Signal: string): PAcceleratorKey;
|
|
begin
|
|
if (Widget = nil) then exit(nil);
|
|
Result:=GetAccelKey(Widget);
|
|
if Result=nil then begin
|
|
if Key>0 then begin
|
|
New(Result);
|
|
FillChar(Result^,SizeOf(Result),0);
|
|
end;
|
|
end else begin
|
|
if Key=0 then begin
|
|
Dispose(Result);
|
|
Result:=nil;
|
|
end;
|
|
end;
|
|
if (Result<>nil) then begin
|
|
Result^.Key:=Key;
|
|
Result^.Mods:=Mods;
|
|
Result^.Signal:=Signal;
|
|
Result^.Realized:=false;
|
|
end;
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('SetAccelKey Widget=',DbgS(Widget),
|
|
' Key=',dbgs(Key),' Mods=',DbgS(Mods),
|
|
' Signal="',Signal,'" Result=',DbgS(Result));
|
|
{$ENDIF}
|
|
gtk_object_set_data(PGtkObject(Widget), 'AccelKey', Result);
|
|
end;
|
|
|
|
procedure ClearAccelKey(Widget: PGtkWidget);
|
|
begin
|
|
SetAccelKey(Widget,0,0,'');
|
|
end;
|
|
|
|
procedure RealizeAccelerator(Component: TComponent; Widget : PGtkWidget);
|
|
var
|
|
AccelKey: PAcceleratorKey;
|
|
AccelGroup: PGTKAccelGroup;
|
|
begin
|
|
if (Component=nil) or (Widget=nil) then
|
|
RaiseGDBException('RealizeAccelerate: invalid input');
|
|
|
|
// Set the accelerator
|
|
AccelKey:=GetAccelKey(Widget);
|
|
if (AccelKey=nil) or (AccelKey^.Realized) then exit;
|
|
|
|
if AccelKey^.Key>0 then begin
|
|
AccelGroup:=GetAccelGroupForComponent(Component,true);
|
|
if AccelGroup<>nil then begin
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('RealizeAccelerator Add Accelerator ',
|
|
Component.Name,':',Component.ClassName,
|
|
' Widget=',DbgS(Widget),
|
|
' Signal=',AccelKey^.Signal,
|
|
' Key=',dbgs(AccelKey^.Key),' Mods=',dbgs(AccelKey^.Mods),
|
|
'');
|
|
{$ENDIF}
|
|
gtk_widget_add_accelerator(Widget, PChar(AccelKey^.Signal),
|
|
AccelGroup, AccelKey^.Key, AccelKey^.Mods, GTK_ACCEL_VISIBLE);
|
|
AccelKey^.Realized:=true;
|
|
end else begin
|
|
AccelKey^.Realized:=false;
|
|
end;
|
|
end else begin
|
|
AccelKey^.Realized:=true;
|
|
end;
|
|
end;
|
|
|
|
procedure UnrealizeAccelerator(Widget : PGtkWidget);
|
|
var
|
|
AccelKey: PAcceleratorKey;
|
|
begin
|
|
if (Widget=nil) then
|
|
RaiseGDBException('UnrealizeAccelerate: invalid input');
|
|
|
|
AccelKey:=GetAccelKey(Widget);
|
|
if (AccelKey=nil) or (not AccelKey^.Realized) then exit;
|
|
|
|
if AccelKey^.Signal<>'' then begin
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('UnrealizeAccelerator ',
|
|
' Widget=',DbgS(Widget),
|
|
' Signal=',AccelKey^.Signal,
|
|
' Key=',dbgs(AccelKey^.Key),' Mods=',dbgs(AccelKey^.Mods),
|
|
'');
|
|
{$ENDIF}
|
|
{$Ifdef GTK2}
|
|
DebugLn('ToDo: gtkproc.inc UnrealizeAccelerator');
|
|
{$else}
|
|
gtk_widget_remove_accelerators(Widget, PChar(AccelKey^.Signal), false);
|
|
{$EndIf}
|
|
end;
|
|
AccelKey^.Realized:=false;
|
|
end;
|
|
|
|
procedure RegroupAccelerator(Widget: PGtkWidget);
|
|
begin
|
|
UnrealizeAccelerator(Widget);
|
|
RealizeAccelerator(TComponent(GetLCLObject(Widget)),Widget);
|
|
end;
|
|
|
|
procedure Accelerate(Component: TComponent; const Widget : PGtkWidget;
|
|
const Key: guint; Mods: TGdkModifierType; const Signal : string);
|
|
var
|
|
OldAccelKey: PAcceleratorKey;
|
|
begin
|
|
if (Component=nil) or (Widget=nil) or (Signal='') then
|
|
RaiseGDBException('Accelerate: invalid input');
|
|
{$IFDEF VerboseAccelerator}
|
|
DebugLn('Accelerate ',DbgSName(Component),' Key=',dbgs(Key),' Mods=',DbgS(Mods),' Signal=',Signal);
|
|
{$ENDIF}
|
|
|
|
// delete old accelerator key
|
|
OldAccelKey:=GetAccelKey(Widget);
|
|
if (OldAccelKey <> nil) then begin
|
|
if (OldAccelKey^.Key=Key) and (OldAccelKey^.Mods=Mods)
|
|
and (OldAccelKey^.Signal=Signal)
|
|
then begin
|
|
// no change
|
|
exit;
|
|
end;
|
|
|
|
UnrealizeAccelerator(Widget);
|
|
end;
|
|
|
|
// Set the accelerator
|
|
SetAccelKey(Widget,Key,Mods,Signal);
|
|
if (Key>0) and (not (csDesigning in Component.ComponentState))
|
|
then
|
|
RealizeAccelerator(Component,Widget);
|
|
end;
|
|
|
|
procedure Accelerate(Component: TComponent; const Widget : PGtkWidget;
|
|
const NewShortCut: TShortCut; const Signal : string);
|
|
var
|
|
GDKModifier: TGdkModifierType;
|
|
GDKKey: guint;
|
|
NewKey: word;
|
|
NewModifier: TShiftState;
|
|
Shift: TShiftStateEnum;
|
|
begin
|
|
{ Map the shift states }
|
|
GDKModifier := 0;
|
|
ShortCutToKey(NewShortCut, NewKey, NewModifier);
|
|
for Shift := Low(Shift) to High(Shift) do
|
|
begin
|
|
if Shift in NewModifier
|
|
then GDKModifier := GDKModifier or MModifiers[Shift].Mask;
|
|
end;
|
|
|
|
// Send the unmodified keysym ?
|
|
if (ssShift in NewModifier)
|
|
and ((NewKey < VK_F1) or (NewKey > VK_F24))
|
|
then GDKKey := GetVKeyInfo(NewKey).KeySym[1]
|
|
else GDKKey := GetVKeyInfo(NewKey).KeySym[0];
|
|
|
|
Accelerate(Component,Widget,GDKKey,GDKModifier,Signal);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
method TGtkWidgetSet LoadPixbufFromLazResource
|
|
Params: const ResourceName: string;
|
|
var Pixbuf: PGdkPixbuf
|
|
Result: none
|
|
|
|
Loads a pixbuf from a lazarus resource. The resource must be a XPM file.
|
|
-------------------------------------------------------------------------------}
|
|
procedure LoadPixbufFromLazResource(const ResourceName: string;
|
|
var Pixbuf: PGdkPixbuf);
|
|
var
|
|
ImgData: PPChar;
|
|
begin
|
|
Pixbuf:=nil;
|
|
try
|
|
ImgData:=LazResourceXPMToPPChar(ResourceName);
|
|
except
|
|
on e: Exception do
|
|
DebugLn('WARNING: TGtkWidgetSet.LoadXPMFromLazResource: '+e.Message);
|
|
end;
|
|
{$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
|
|
{$IFDEF VerboseGdkPixbuf}
|
|
debugln('LoadPixbufFromLazResource A1');
|
|
{$ENDIF}
|
|
pixbuf:=gdk_pixbuf_new_from_xpm_data(ImgData);
|
|
{$IFDEF VerboseGdkPixbuf}
|
|
debugln('LoadPixbufFromLazResource A2');
|
|
{$ENDIF}
|
|
{$IFDEF DebugGDK}EndGDKErrorTrap;{$ENDIF}
|
|
FreeMem(ImgData);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
method CreatePixbufFromDrawable
|
|
Params: ASource: The source drawable
|
|
AColorMap: The colormap to use, when nil a matching colormap is passed
|
|
AIncludeAplha: If set, the resulting pixmap has an alpha channel
|
|
ASrcX, ASrcY: Offset within the source
|
|
ADstX, ADstY: Offset within destination
|
|
AWidth, AHeight: Size of the new image
|
|
Result: New Pixbuf with refcount = 1
|
|
|
|
Replaces the gdk_pixbuf_get_from_drawable function which is buggy on big endian
|
|
X servers when an alpha channel is requested.
|
|
-------------------------------------------------------------------------------}
|
|
function CreatePixbufFromDrawable(ASource: PGdkDrawable; AColorMap:PGdkColormap; AIncludeAplha: Boolean; ASrcX, ASrcY, ADstX, ADstY, AWidth, AHeight: longint): PGdkPixbuf;
|
|
{$ifndef HasX}
|
|
const
|
|
CanRequestAlpha: Boolean = True;
|
|
var
|
|
{$else}
|
|
var
|
|
CanRequestAlpha: Boolean;
|
|
{$endif}
|
|
PixBuf: PGdkPixBuf;
|
|
{$ifdef Windows}
|
|
Image: PGdkImage;
|
|
{$endif}
|
|
begin
|
|
{$ifdef HasX}
|
|
CanRequestAlpha := BitmapBitOrder(gdk_display) = LSBFirst;
|
|
{$endif}
|
|
|
|
// If Source is GdkBitmap then gdk_pixbuf_get_from_drawable will get
|
|
// pixbuf with 2 colors: transparent and white, but we need only Black and White.
|
|
// If we all alpha at the end then problem is gone.
|
|
CanRequestAlpha := CanRequestAlpha and (gdk_drawable_get_depth(ASource) > 1);
|
|
|
|
if CanRequestAlpha and AIncludeAplha
|
|
then Pixbuf := gdk_pixbuf_new(GDK_COLORSPACE_RGB, True, 8, AWidth, AHeight)
|
|
else Pixbuf := nil;
|
|
|
|
// gtk1 requires always a colormap and fails when none passed
|
|
// gtk2 fails when the colormap depth is different than the drawable depth.
|
|
// It wil use the correct system map when none passed.
|
|
// Bitmaps (depth = 1) don't need a colormap
|
|
{$ifdef gtk1}
|
|
if AColormap = nil
|
|
then AColorMap := gdk_colormap_get_system;
|
|
{$else}
|
|
if (AColorMap = nil)
|
|
and (gdk_drawable_get_depth(ASource) > 1)
|
|
and (gdk_drawable_get_colormap(ASource) = nil)
|
|
then AColorMap := gdk_colormap_get_system;
|
|
{$endif}
|
|
{$ifdef Windows}
|
|
if gdk_drawable_get_depth(ASource) = 1 then
|
|
begin
|
|
// Fix gdk error in converter. For 1 bit Byte order is not significant
|
|
Image := gdk_drawable_get_image(ASource, ASrcX, ASrcY, AWidth, AHeight);
|
|
Image^.byte_order := GDK_MSB_FIRST;
|
|
Result := gdk_pixbuf_get_from_image(Pixbuf, Image, nil, ASrcX, ASrcY, ADstX, ADstY, AWidth, AHeight);
|
|
gdk_image_unref(Image);
|
|
end
|
|
else
|
|
{$endif}
|
|
Result := gdk_pixbuf_get_from_drawable(Pixbuf, ASource, AColorMap, ASrcX, ASrcY, ADstX, ADstY, AWidth, AHeight);
|
|
//DbgDumpPixbuf(Result, '');
|
|
|
|
if CanRequestAlpha then Exit; // we're done
|
|
if not AIncludeAplha then Exit;
|
|
|
|
pixbuf := gdk_pixbuf_add_alpha(Result, false, guchar(0),guchar(0),guchar(0));
|
|
gdk_pixbuf_unref(Result);
|
|
Result := pixbuf;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
method LoadXPMFromLazResource
|
|
Params: const ResourceName: string;
|
|
Window: PGdkWindow;
|
|
var PixmapImg, PixmapMask: PGdkPixmap
|
|
Result: none
|
|
|
|
Loads a pixmap from a lazarus resource. The resource must be a XPM file.
|
|
-------------------------------------------------------------------------------}
|
|
procedure LoadXPMFromLazResource(const ResourceName: string;
|
|
Window: PGdkWindow; var PixmapImg, PixmapMask: PGdkPixmap);
|
|
var
|
|
ImgData: PPGChar;
|
|
begin
|
|
PixmapImg:=nil;
|
|
PixmapMask:=nil;
|
|
try
|
|
ImgData:=PPGChar(LazResourceXPMToPPChar(ResourceName));
|
|
except
|
|
on e: Exception do
|
|
DebugLn('WARNING: TGtkWidgetSet.LoadXPMFromLazResource: '+e.Message);
|
|
end;
|
|
{$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
|
|
PixmapImg:=gdk_pixmap_create_from_xpm_d(Window,PixmapMask,nil,ImgData);
|
|
{$IFDEF DebugGDK}EndGDKErrorTrap;{$ENDIF}
|
|
FreeMem(ImgData);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function MENU_ITEM_CLASS(widget: PGtkWidget): PGtkMenuItemClass;
|
|
|
|
Returns the gtk klass of a menuitem widget.
|
|
------------------------------------------------------------------------------}
|
|
function MENU_ITEM_CLASS(widget: PGtkWidget): PGtkMenuItemClass;
|
|
begin
|
|
Result:=GTK_MENU_ITEM_CLASS(gtk_object_get_class(widget));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function CHECK_MENU_ITEM_CLASS(widget: PGtkWidget): PGtkCheckMenuItemClass;
|
|
|
|
Returns the gtk klass of a checkmenuitem widget.
|
|
------------------------------------------------------------------------------}
|
|
function CHECK_MENU_ITEM_CLASS(widget: PGtkWidget): PGtkCheckMenuItemClass;
|
|
begin
|
|
Result:=GTK_CHECK_MENU_ITEM_CLASS(gtk_object_get_class(widget));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure LockRadioGroupOnChange(RadioGroup: PGSList; const ADelta: Integer);
|
|
|
|
Calls LockOnChange for all groupmembers
|
|
------------------------------------------------------------------------------}
|
|
procedure LockRadioGroupOnChange(RadioGroup: PGSList; const ADelta: Integer);
|
|
begin
|
|
while RadioGroup <> nil do
|
|
begin
|
|
if RadioGroup^.Data <> nil
|
|
then LockOnChange(PgtkObject(RadioGroup^.Data), ADelta);
|
|
RadioGroup := RadioGroup^.Next;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure UpdateRadioGroupChecks(RadioGroup: PGSList);
|
|
|
|
Set 'checked' for all menuitems in the group
|
|
------------------------------------------------------------------------------}
|
|
procedure UpdateRadioGroupChecks(RadioGroup: PGSList);
|
|
var
|
|
CurListItem: PGSList;
|
|
MenuItem: PGtkCheckMenuItem;
|
|
LCLMenuItem: TMenuItem;
|
|
begin
|
|
// Check if it is a single entry
|
|
if (RadioGroup = nil) or (RadioGroup^.Next = nil)
|
|
then Exit;
|
|
|
|
// Lock whole group for update
|
|
LockRadioGroupOnChange(RadioGroup, +1);
|
|
CurListItem := RadioGroup;
|
|
try
|
|
// set active radiomenuitem
|
|
while CurListItem <> nil do
|
|
begin
|
|
MenuItem := PGtkCheckMenuItem(CurListItem^.Data);
|
|
if MenuItem<>nil
|
|
then begin
|
|
LCLMenuItem := TMenuItem(GetLCLObject(MenuItem));
|
|
if (LCLMenuItem <> nil)
|
|
and (gtk_check_menu_item_get_active(MenuItem) <> LCLMenuItem.Checked)
|
|
then gtk_check_menu_item_set_active(MenuItem, LCLMenuItem.Checked);
|
|
end;
|
|
CurListItem := CurListItem^.Next;
|
|
end;
|
|
finally
|
|
// Unlock whole group for update
|
|
LockRadioGroupOnChange(RadioGroup, -1);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure DrawMenuItemIcon(MenuItem: PGtkCheckMenuItem;
|
|
area: PGdkRectangle); cdecl;
|
|
|
|
Handler for drawing the icon of a menuitem.
|
|
------------------------------------------------------------------------------}
|
|
procedure DrawMenuItemIcon(MenuItem: PGtkCheckMenuItem;
|
|
Area: PGdkRectangle); cdecl;
|
|
var
|
|
Widget: PGtkWidget;
|
|
Container: PgtkContainer;
|
|
ALeft, ATop, BorderWidth: gint;
|
|
LCLMenuItem: TMenuItem;
|
|
AWindow: PGdkWindow;
|
|
IconWidth, IconHeight: integer;
|
|
IconSize: TPoint;
|
|
{$IFDEF Gtk2}
|
|
HorizPadding, ToggleSpacing: Integer;
|
|
{$ENDIF}
|
|
|
|
AEffect: TGraphicsDrawEffect;
|
|
AImageList: TCustomImageList;
|
|
FreeImageList: Boolean;
|
|
AImageIndex: Integer;
|
|
ItemBmp: TBitmap;
|
|
begin
|
|
if (MenuItem=nil) then
|
|
exit;
|
|
if not (GTK_WIDGET_DRAWABLE (PGtkWidget(MenuItem))) then
|
|
exit;
|
|
|
|
// get icon
|
|
LCLMenuItem:=TMenuItem(GetLCLObject(MenuItem));
|
|
if LCLMenuItem=nil then begin // needed for gtk2 dialog
|
|
if GtkWidgetIsA(PGtkWidget(MenuItem), gtk_check_menu_item_get_type) then
|
|
OldCheckMenuItemDrawProc(MenuItem, Area);
|
|
Exit;
|
|
end;
|
|
if not LCLMenuItem.HasIcon then
|
|
begin
|
|
// call default draw function
|
|
OldCheckMenuItemDrawProc(MenuItem,Area);
|
|
exit;
|
|
end;
|
|
IconSize:=LCLMenuItem.GetIconSize;
|
|
IconWidth:=IconSize.X;
|
|
IconHeight:=IconSize.Y;
|
|
|
|
// calculate left and top
|
|
Widget := PGtkWidget(MenuItem);
|
|
AWindow:=GetControlWindow(Widget);
|
|
if AWindow = nil then
|
|
exit;
|
|
Container := GTK_CONTAINER (MenuItem);
|
|
BorderWidth := Container^.flag0 and bm_TGtkContainer_border_width;
|
|
|
|
{$IFDEF Gtk2}
|
|
gtk_widget_style_get(PGtkWidget(MenuItem),
|
|
'horizontal-padding', @HorizPadding,
|
|
'toggle-spacing', @ToggleSpacing,
|
|
nil);
|
|
|
|
ALeft := BorderWidth +
|
|
gtk_widget_get_xthickness(gtk_widget_get_style(Widget)) +
|
|
HorizPadding +
|
|
((PGtkMenuItem(MenuItem)^.toggle_size-ToggleSpacing-IconWidth) div 2);
|
|
|
|
if gtk_widget_get_direction(Widget) = GTK_TEXT_DIR_RTL then
|
|
ALeft := Widget^.Allocation.width - IconWidth - ALeft; //not sure it is the correct Width
|
|
{$ELSE}
|
|
ALeft := (BorderWidth + gtk_widget_get_xthickness(gtk_widget_get_style(Widget)) + 2)
|
|
+((PGtkMenuItem(MenuItem)^.toggle_size-IconWidth) div 2);
|
|
{$ENDIF}
|
|
|
|
ATop := (Widget^.Allocation.Height - IconHeight) div 2;
|
|
|
|
// draw icon
|
|
AImageList := LCLMenuItem.GetImageList;
|
|
if AImageList = nil then
|
|
begin
|
|
AImageList := TImageList.Create(nil);
|
|
// prevent multiple calls to GetBitmap;
|
|
ItemBmp := LCLMenuItem.Bitmap;
|
|
AImageList.Width := ItemBmp.Width; // maybe height to prevent too wide bitmaps?
|
|
AImageList.Height := ItemBmp.Height;
|
|
if ItemBmp.Masked
|
|
then AImageIndex := AImageList.AddMasked(ItemBmp, ItemBmp.TransparentColor)
|
|
else AImageIndex := AImageList.Add(ItemBmp, nil);
|
|
FreeImageList := True;
|
|
end
|
|
else
|
|
begin
|
|
FreeImageList := False;
|
|
AImageIndex := LCLMenuItem.ImageIndex;
|
|
end;
|
|
|
|
if not LCLMenuItem.Enabled then
|
|
AEffect := gdeDisabled
|
|
else
|
|
AEffect := gdeNormal;
|
|
|
|
if AImageIndex < AImageList.Count then
|
|
{$IFDEF VerboseGtkToDos}{$note reimplement}{$ENDIF}
|
|
DrawImageListIconOnWidget(AImageList, AImageIndex, AEffect,
|
|
Widget, false, false, ALeft, ATop);
|
|
|
|
if FreeImageList then
|
|
AImageList.Free;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure MenuSizeRequest(widget:PGtkWidget;
|
|
requisition:PGtkRequisition); cdecl;
|
|
|
|
SizeAllocate Handler for check menuitem widgets.
|
|
------------------------------------------------------------------------------}
|
|
procedure MenuSizeRequest(widget:PGtkWidget; requisition:PGtkRequisition); cdecl;
|
|
var
|
|
CurToggleSize, MaxToggleSize: integer;
|
|
MenuShell: PGtkMenuShell;
|
|
ListItem: PGList;
|
|
MenuItem: PGtkMenuItem;
|
|
CheckMenuItem: PGtkMenuItem;
|
|
LCLMenuItem: TMenuItem;
|
|
IconSize: TPoint;
|
|
begin
|
|
MaxToggleSize:=0;
|
|
MenuShell:=GTK_MENU_SHELL(widget);
|
|
ListItem:=MenuShell^.Children;
|
|
CheckMenuItem:=nil;
|
|
while ListItem<>nil do begin
|
|
MenuItem:=PGtkMenuItem(ListItem^.Data);
|
|
if GTK_IS_CHECK_MENU_ITEM(PGtkWidget(MenuItem)) then begin
|
|
CheckMenuItem:=MenuItem;
|
|
CurToggleSize:=OldCheckMenuItemToggleSize;
|
|
LCLMenuItem:=TMenuItem(GetLCLObject(MenuItem));
|
|
if LCLMenuItem<>nil then begin
|
|
IconSize:=LCLMenuItem.GetIconSize;
|
|
{if IconSize.X>100 then
|
|
debugln('MenuSizeRequest LCLMenuItem=',LCLMenuItem.Name,' ',LCLMenuItem.Caption,
|
|
' ');}
|
|
if CurToggleSize<IconSize.X then
|
|
CurToggleSize:=IconSize.X;
|
|
end;
|
|
if MaxToggleSize<CurToggleSize then
|
|
MaxToggleSize:=CurToggleSize;
|
|
end;
|
|
ListItem:=ListItem^.Next;
|
|
end;
|
|
//DebugLn('MenuSizeRequest A MaxToggleSize=',MaxToggleSize);
|
|
{$IFDEF Gtk2}
|
|
// Gtk2ToDo
|
|
if CheckMenuItem<>nil then begin
|
|
GTK_MENU_ITEM(CheckMenuItem)^.toggle_size := 0;
|
|
gtk_menu_item_toggle_size_allocate(GTK_MENU_ITEM(CheckMenuItem),MaxToggleSize);
|
|
GTK_MENU_ITEM(CheckMenuItem)^.toggle_size := MaxToggleSize;
|
|
end;
|
|
{$ELSE}
|
|
if CheckMenuItem<>nil then
|
|
MENU_ITEM_CLASS(PGtkWidget(CheckMenuItem))^.toggle_size:=MaxToggleSize;
|
|
{$ENDIF}
|
|
//DebugLn('MenuSizeRequest B ',GetWidgetDebugReport(widget),' ',requisition^.width,',',requisition^.height);
|
|
OldMenuSizeRequestProc(Widget,requisition);
|
|
//DebugLn('MenuSizeRequest C ',GetWidgetDebugReport(widget),' ',requisition^.width,',',requisition^.height);
|
|
end;
|
|
|
|
procedure UpdateInnerMenuItem(LCLMenuItem: TMenuItem; MenuItemWidget: PGtkWidget);
|
|
begin
|
|
UpdateInnerMenuItem(LCLMenuItem, MenuItemWidget, LCLMenuItem.ShortCut);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure UpdateInnerMenuItem(LCLMenuItem: TMenuItem;
|
|
MenuItemWidget: PGtkWidget; NewShortCut: TShortCut);
|
|
|
|
Update the inner widgets of a menuitem widget.
|
|
------------------------------------------------------------------------------}
|
|
procedure UpdateInnerMenuItem(LCLMenuItem: TMenuItem;
|
|
MenuItemWidget: PGtkWidget; NewShortCut: TShortCut);
|
|
{$ifdef GTK2}
|
|
const
|
|
WidgetDirection : array[boolean] of longint = (GTK_TEXT_DIR_LTR, GTK_TEXT_DIR_RTL);
|
|
{$endif}
|
|
function UseRTL: Boolean;
|
|
begin
|
|
Result := LCLMenuItem.GetIsRightToLeft;
|
|
end;
|
|
var
|
|
HBoxWidget: PGtkWidget;
|
|
|
|
procedure SetMenuItemLabelText(LCLMenuItem: TMenuItem;
|
|
MenuItemWidget: PGtkWidget);
|
|
var
|
|
LabelWidget: PGtkLabel;
|
|
begin
|
|
if (MenuItemWidget = nil) or (LCLMenuItem = nil) then
|
|
Exit;
|
|
LabelWidget := gtk_object_get_data(PGtkObject(MenuItemWidget), 'LCLLabel');
|
|
GtkWidgetset.SetLabelCaption(LabelWidget, LCLMenuItem.Caption);
|
|
{$ifdef GTK2}
|
|
gtk_widget_set_direction(PGtkWidget(LabelWidget), WidgetDirection[UseRTL]);
|
|
{$endif}
|
|
end;
|
|
|
|
procedure UpdateShortCutLabel;
|
|
var
|
|
LabelWidget: PGtkLabel;
|
|
NeedShortCut: Boolean;
|
|
Key: Word;
|
|
Shift: TShiftState;
|
|
s: String;
|
|
begin
|
|
//DebugLn(['UpdateShortCutLabel ',dbgsName(LCLMenuItem),' ',ShortCutToText(NewShortCut)]);
|
|
ShortCutToKey(NewShortCut, Key, Shift);
|
|
|
|
// check if shortcut is needed
|
|
NeedShortCut := Key <> 0;
|
|
|
|
if NeedShortCut and
|
|
(LCLMenuItem.Parent <> nil) and
|
|
LCLMenuItem.Parent.HandleAllocated and
|
|
GtkWidgetIsA(PGtkWidget(LCLMenuItem.Parent.Handle), GTK_TYPE_MENU_BAR) then
|
|
begin
|
|
// no shortcut captions for items in menubar
|
|
NeedShortCut := False;
|
|
end;
|
|
|
|
LabelWidget := PGtkLabel(gtk_object_get_data(PGtkObject(MenuItemWidget),
|
|
'LCLShortCutLabel'));
|
|
|
|
if NeedShortCut then
|
|
begin
|
|
ShortCutToKey(NewShortCut, Key, Shift);
|
|
s := GetAcceleratorString(Key, Shift);
|
|
// ShortCutToText(NewShortCut);
|
|
if LabelWidget = nil then
|
|
begin
|
|
// create a label for the ShortCut
|
|
LabelWidget := PGtkLabel(gtk_label_new(PChar(Pointer(s))));
|
|
gtk_object_set_data(PGtkObject(MenuItemWidget), 'LCLShortCutLabel', LabelWidget);
|
|
gtk_container_add(GTK_CONTAINER(HBoxWidget), PGtkWidget(LabelWidget));
|
|
gtk_widget_show(PGtkWidget(LabelWidget));
|
|
end
|
|
else
|
|
begin
|
|
gtk_label_set_text(LabelWidget, PChar(Pointer(s)));
|
|
end;
|
|
{$ifdef GTK2}
|
|
gtk_widget_set_direction(PGtkWidget(LabelWidget), GTK_TEXT_DIR_LTR); //Shortcut always LTR
|
|
{$endif}
|
|
if UseRTL then
|
|
gtk_misc_set_alignment(GTK_MISC(LabelWidget), 0.0, 0.5)
|
|
else
|
|
gtk_misc_set_alignment(GTK_MISC (LabelWidget), 1.0, 0.5);
|
|
end else
|
|
if LabelWidget <> nil then
|
|
gtk_widget_destroy(PGtkWidget(LabelWidget));
|
|
end;
|
|
|
|
procedure CreateIcon;
|
|
var
|
|
{$IFNDEF Gtk2}
|
|
IconWidth, IconHeight: integer;
|
|
IconSize: TPoint;
|
|
{$ENDIF}
|
|
MinHeightWidget: PGtkWidget;
|
|
begin
|
|
// the icon will be painted instead of the toggle
|
|
// of a normal gtkcheckmenuitem
|
|
|
|
if LCLMenuItem.HasIcon then
|
|
begin
|
|
{$IFNDEF Gtk2}
|
|
IconSize := LCLMenuItem.GetIconSize;
|
|
IconWidth := IconSize.X;
|
|
IconHeight := IconSize.Y;
|
|
// set the toggle width
|
|
GTK_MENU_ITEM(MenuItemWidget)^.toggle_size := guint16(IconWidth);
|
|
{$ENDIF}
|
|
|
|
GTK_MENU_ITEM(MenuItemWidget)^.flag0:=
|
|
PGtkMenuItem(MenuItemWidget)^.flag0 or
|
|
{$IFDEF Gtk2}
|
|
bm_TGtkCheckMenuItem_always_show_toggle;
|
|
{$ELSE}
|
|
bm_show_toggle_indicator;
|
|
{$ENDIF}
|
|
|
|
// set our own draw handler
|
|
if OldCheckMenuItemDrawProc = nil then
|
|
OldCheckMenuItemDrawProc := CHECK_MENU_ITEM_CLASS(MenuItemWidget)^.draw_indicator;
|
|
CHECK_MENU_ITEM_CLASS(MenuItemWidget)^.draw_indicator := @DrawMenuItemIcon;
|
|
|
|
{$IFNDEF Gtk2}
|
|
// add a dummy widget for the icon height
|
|
MinHeightWidget := gtk_label_new('');
|
|
gtk_widget_show(MinHeightWidget);
|
|
gtk_widget_set_usize(MinHeightWidget, 1, IconHeight);
|
|
gtk_box_pack_start(GTK_BOX(HBoxWidget), MinHeightWidget, False, False, 0);
|
|
{$ENDIF}
|
|
end
|
|
else
|
|
MinHeightWidget := nil;
|
|
gtk_object_set_data(PGtkObject(MenuItemWidget),
|
|
'LCLMinHeight', MinHeightWidget);
|
|
end;
|
|
|
|
procedure CreateLabel;
|
|
var
|
|
LabelWidget: PGtkLabel;
|
|
begin
|
|
// create a label for the Caption
|
|
LabelWidget := PGtkLabel(gtk_label_new(''));
|
|
gtk_misc_set_alignment(GTK_MISC (LabelWidget), 0.0, 0.5);
|
|
gtk_object_set_data(PGtkObject(MenuItemWidget), 'LCLLabel', LabelWidget);
|
|
gtk_container_add(GTK_CONTAINER(HBoxWidget), PGtkWidget(LabelWidget));
|
|
SetMenuItemLabelText(LCLMenuItem, MenuItemWidget);
|
|
//gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(LabelWidget), MenuItemWidget);
|
|
gtk_widget_show(PGtkWidget(LabelWidget));
|
|
end;
|
|
|
|
begin
|
|
HBoxWidget := gtk_object_get_data(PGtkObject(MenuItemWidget), 'LCLHBox');
|
|
if HBoxWidget = nil then
|
|
begin
|
|
// create inner widgets
|
|
if LCLMenuItem.Caption = cLineCaption then
|
|
begin
|
|
// a separator is an empty gtkmenuitem
|
|
exit;
|
|
end;
|
|
HBoxWidget := gtk_hbox_new(false, 20);
|
|
{$ifdef GTK2}
|
|
gtk_widget_set_direction(PGtkWidget(HBoxWidget), WidgetDirection[UseRTL]);
|
|
{$endif}
|
|
gtk_object_set_data(PGtkObject(MenuItemWidget), 'LCLHBox', HBoxWidget);
|
|
CreateIcon;
|
|
CreateLabel;
|
|
UpdateShortCutLabel;
|
|
gtk_container_add(GTK_CONTAINER(MenuItemWidget), HBoxWidget);
|
|
gtk_widget_show(HBoxWidget);
|
|
end else
|
|
begin
|
|
// there are already inner widgets
|
|
if LCLMenuItem.Caption = cLineCaption then
|
|
begin
|
|
// a separator is an empty gtkmenuitem -> delete the inner widgets
|
|
DestroyWidget(HBoxWidget);
|
|
gtk_object_set_data(PGtkObject(MenuItemWidget), 'LCLHBox', nil);
|
|
end else
|
|
begin
|
|
// just update the content
|
|
{$ifdef GTK2}
|
|
gtk_widget_set_direction(PGtkWidget(HBoxWidget), WidgetDirection[UseRTL]);
|
|
{$endif}
|
|
SetMenuItemLabelText(LCLMenuItem, MenuItemWidget);
|
|
UpdateShortCutLabel;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function CreateStatusBarPanel(StatusBar: TObject; Index: integer): PGtkWidget;
|
|
begin
|
|
Result := gtk_statusbar_new;
|
|
gtk_widget_show(Result);
|
|
// other properties are set in UpdateStatusBarPanels
|
|
end;
|
|
|
|
procedure UpdateStatusBarPanels(StatusBar: TObject; StatusBarWidget: PGtkWidget);
|
|
var
|
|
AStatusBar: TStatusBar;
|
|
HBox: PGtkWidget;
|
|
CurPanelCount: integer;
|
|
NewPanelCount: Integer;
|
|
CurStatusPanelWidget: PGtkWidget;
|
|
ListItem: PGList;
|
|
i: Integer;
|
|
ExpandItem: boolean;
|
|
{$IFNDEF GTK1}
|
|
ShowSizeGrip: Boolean;
|
|
{$ENDIF}
|
|
begin
|
|
AStatusBar := StatusBar as TStatusBar;
|
|
HBox := PGtkWidget(StatusBarWidget);
|
|
if (not GtkWidgetIsA(StatusBarWidget, GTK_HBOX_GET_TYPE)) then
|
|
RaiseGDBException('');
|
|
|
|
// create needed panels
|
|
CurPanelCount := integer(g_list_length(PGtkBox(HBox)^.children));
|
|
if AStatusBar.SimplePanel or (AStatusBar.Panels.Count < 1) then
|
|
NewPanelCount := 1
|
|
else
|
|
NewPanelCount := AStatusBar.Panels.Count;
|
|
|
|
while CurPanelCount < NewPanelCount do
|
|
begin
|
|
CurStatusPanelWidget := CreateStatusBarPanel(StatusBar, CurPanelCount);
|
|
ExpandItem := (CurPanelCount = NewPanelCount - 1);
|
|
gtk_box_pack_start(PGtkBox(HBox), CurStatusPanelWidget,
|
|
ExpandItem, ExpandItem, 0);
|
|
inc(CurPanelCount);
|
|
end;
|
|
|
|
// remove unneeded panels
|
|
while CurPanelCount > NewPanelCount do
|
|
begin
|
|
CurStatusPanelWidget := PGtkBoxChild(
|
|
g_list_nth_data(PGtkBox(HBox)^.children, CurPanelCount - 1))^.Widget;
|
|
{$IFDEF GTK2}
|
|
gtk_object_remove_data(PGtkObject(CurStatusPanelWidget),'lcl_statusbar_id');
|
|
{$ENDIF}
|
|
DestroyConnectedWidgetCB(CurStatusPanelWidget, True);
|
|
dec(CurPanelCount);
|
|
end;
|
|
|
|
// check new panel count
|
|
CurPanelCount := integer(g_list_length(PGtkBox(HBox)^.children));
|
|
//DebugLn('TGtkWidgetSet.UpdateStatusBarPanels B ',Dbgs(StatusBar),' NewPanelCount=',dbgs(NewPanelCount),' CurPanelCount=',dbgs(CurPanelCount));
|
|
if CurPanelCount <> NewPanelCount then
|
|
RaiseGDBException('');
|
|
|
|
// set panel properties
|
|
{$IFNDEF GTK1}
|
|
ShowSizeGrip := AStatusBar.SizeGrip and AStatusBar.SizeGripEnabled;
|
|
{$ENDIF}
|
|
ListItem := PGTKBox(HBox)^.children;
|
|
i := 0;
|
|
while ListItem <> nil do
|
|
begin
|
|
CurStatusPanelWidget := PGtkBoxChild(PGTKWidget(ListItem^.data))^.widget;
|
|
ExpandItem := (ListItem^.next = nil);
|
|
gtk_box_set_child_packing(PGtkBox(HBox), CurStatusPanelWidget,
|
|
ExpandItem, ExpandItem, 0, GTK_PACK_START);
|
|
UpdateStatusBarPanel(StatusBar, i, CurStatusPanelWidget);
|
|
inc(i);
|
|
ListItem := ListItem^.next;
|
|
{$IFNDEF GTK1}
|
|
gtk_statusbar_set_has_resize_grip(PGtkStatusBar(CurStatusPanelWidget),
|
|
(ListItem = nil) and ShowSizeGrip);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF GTK2}
|
|
function gtk2PaintStatusBarWidget(Widget: PGtkWidget; Event : PGDKEventExpose;
|
|
Data: gPointer): GBoolean; cdecl;
|
|
var
|
|
Msg: TLMDrawItems;
|
|
PS : TPaintStruct;
|
|
ItemStruct: PDrawItemStruct;
|
|
ItemID: Integer;
|
|
begin
|
|
Result := CallBackDefaultReturn;
|
|
if (Event^.Count > 0) then exit;
|
|
|
|
if (csDesigning in TComponent(Data).ComponentState) then
|
|
exit;
|
|
|
|
if TStatusBar(Data).SimplePanel then
|
|
exit;
|
|
|
|
ItemId := PtrInt(gtk_object_get_data(PGtkObject(Widget), 'lcl_statusbar_id')^);
|
|
|
|
if not ((ItemId >= 0) and (ItemId < TStatusBar(Data).Panels.Count)) then
|
|
exit;
|
|
|
|
if TStatusBar(Data).Panels[ItemId].Style <> psOwnerDraw then
|
|
exit;
|
|
|
|
FillChar(Msg, SizeOf(Msg), #0);
|
|
FillChar(PS, SizeOf(PS), #0);
|
|
FillChar(ItemStruct, SizeOf(ItemStruct), #0);
|
|
New(ItemStruct);
|
|
// we must fill up complete area otherwise gtk2 will do
|
|
// strange paints when item is not fully exposed.
|
|
ItemStruct^.rcItem := Rect(Widget^.allocation.x,
|
|
Widget^.allocation.y,
|
|
Widget^.allocation.width + Widget^.allocation.x,
|
|
Widget^.allocation.height + Widget^.allocation.y);
|
|
|
|
OffsetRect(ItemStruct^.rcItem, -ItemStruct^.rcItem.Left, -ItemStruct^.rcItem.Top);
|
|
|
|
// take frame borders into account
|
|
with ItemStruct^.rcItem do
|
|
begin
|
|
Left := Left + Widget^.style^.xthickness;
|
|
Top := Top + Widget^.style^.ythickness;
|
|
Right := Right - Widget^.style^.xthickness;
|
|
Bottom := Bottom - Widget^.style^.ythickness;
|
|
end;
|
|
|
|
ItemStruct^.itemID := ItemID;
|
|
PS.rcPaint := ItemStruct^.rcItem;
|
|
ItemStruct^._hDC := BeginPaint(THandle(PtrUInt(Widget)), PS);
|
|
Msg.Ctl := TStatusBar(Data).Handle;
|
|
Msg.DrawItemStruct := ItemStruct;
|
|
Msg.Msg := LM_DRAWITEM;
|
|
try
|
|
DeliverMessage(TStatusBar(Data), Msg);
|
|
Result := not CallBackDefaultReturn;
|
|
finally
|
|
PS.hdc := ItemStruct^._hDC;
|
|
EndPaint(THandle(PtrUInt(TGtkDeviceContext(PS.hdc).Widget)), PS);
|
|
Dispose(ItemStruct);
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure UpdateStatusBarPanel(StatusBar: TObject; Index: integer;
|
|
StatusPanelWidget: PGtkWidget);
|
|
var
|
|
AStatusBar: TStatusBar;
|
|
CurPanel: TStatusPanel;
|
|
FrameWidget: PGtkWidget;
|
|
LabelWidget: PGtkLabel;
|
|
PanelText: String;
|
|
ContextID: LongWord;
|
|
NewShadowType: TGtkShadowType;
|
|
NewJustification: TGtkJustification;
|
|
{$ifndef gtk1}
|
|
xalign, yalign: gfloat;
|
|
{$endif}
|
|
begin
|
|
//DebugLn('UpdateStatusBarPanel ',DbgS(StatusBar),' Index=',dbgs(Index));
|
|
AStatusBar := StatusBar as TStatusBar;
|
|
|
|
CurPanel := nil;
|
|
if (not AStatusBar.SimplePanel) and (AStatusBar.Panels.Count > Index) then
|
|
CurPanel := AStatusBar.Panels[Index];
|
|
//DebugLn('Panel ',Index,' ',GetWidgetClassName(StatusPanelWidget),
|
|
// ' frame=',GetWidgetClassName(PGTKStatusBar(StatusPanelWidget)^.frame),
|
|
// ' thelabel=',GetWidgetClassName(PGTKStatusBar(StatusPanelWidget)^.thelabel),
|
|
// '');
|
|
FrameWidget := PGTKStatusBar(StatusPanelWidget)^.frame;
|
|
LabelWidget := PGtkLabel(
|
|
{$ifndef gtk1}
|
|
PGTKStatusBar(StatusPanelWidget)^._label
|
|
{$else}
|
|
PGTKStatusBar(StatusPanelWidget)^.thelabel
|
|
{$endif});
|
|
|
|
// Text
|
|
if AStatusBar.SimplePanel then
|
|
PanelText := AStatusBar.SimpleText
|
|
else
|
|
if CurPanel <> nil then
|
|
PanelText := CurPanel.Text
|
|
else
|
|
PanelText := '';
|
|
|
|
ContextID := gtk_statusbar_get_context_id(PGTKStatusBar(StatusPanelWidget),
|
|
'state');
|
|
//DebugLn(' PanelText="',PanelText,'"');
|
|
if PanelText <> '' then
|
|
gtk_statusbar_push(PGTKStatusBar(StatusPanelWidget), ContextID, PGChar(PanelText))
|
|
else
|
|
gtk_statusbar_push(PGTKStatusBar(StatusPanelWidget), ContextID, '');
|
|
|
|
|
|
if CurPanel <> nil then
|
|
begin
|
|
//DebugLn(' Alignment="',ord(CurPanel.Alignment),'"');
|
|
// Alignment
|
|
NewJustification := aGtkJustification[CurPanel.Alignment];
|
|
if GTK_IS_LABEL(LabelWidget) then
|
|
begin
|
|
{$ifndef gtk1}
|
|
if GTK_IS_MISC(LabelWidget) then
|
|
begin
|
|
{gtk_label_set_justify() has no effect on labels containing
|
|
only a single line !}
|
|
gtk_misc_get_alignment(GTK_MISC(LabelWidget), @xalign, @yalign);
|
|
xalign := AlignToGtkAlign(CurPanel.Alignment);
|
|
gtk_misc_set_alignment(GTK_MISC(LabelWidget), xalign, yalign);
|
|
end else
|
|
gtk_label_set_justify(LabelWidget, NewJustification);
|
|
{$else}
|
|
gtk_label_set_justify(LabelWidget, NewJustification);
|
|
{$endif}
|
|
end;
|
|
|
|
// Bevel
|
|
|
|
// Paul: this call will not modify frame on gtk2. GtkStatusBar resets frame
|
|
// shadow on every size request. I have tried to modify rcStyle and tried to
|
|
// hook property change event. Both ways are 1) not valid 2) does not give me
|
|
// any result.
|
|
// As a possible solution we can subclass PGtkStatusBar but if gtk developers
|
|
// decided that stausbar should work so whether we need to override that?
|
|
NewShadowType := aGtkShadowFromBevel[CurPanel.Bevel];
|
|
if GTK_IS_FRAME(FrameWidget) then
|
|
gtk_frame_set_shadow_type(PGtkFrame(FrameWidget), NewShadowType);
|
|
|
|
// Width
|
|
//DebugLn(' CurPanel.Width="',CurPanel.Width,'"');
|
|
gtk_widget_set_usize(StatusPanelWidget, CurPanel.Width,
|
|
StatusPanelWidget^.allocation.height);
|
|
{$IFDEF GTK2}
|
|
gtk_object_set_data(PGtkObject(StatusPanelWidget),'lcl_statusbar_id',
|
|
@AStatusBar.Panels[Index].ID);
|
|
g_signal_connect_after(StatusPanelWidget, 'expose-event',
|
|
TGtkSignalFunc(@gtk2PaintStatusBarWidget), AStatusBar);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
function gtkListGetSelectionMode(list: PGtkList): TGtkSelectionMode; cdecl;
|
|
begin
|
|
Result:=TGtkSelectionMode(
|
|
(list^.flag0 and bm_TGtkList_selection_mode) shr bp_TGtkList_selection_mode);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
SaveSizeNotification
|
|
Params: Widget: PGtkWidget A widget that is the handle of a lcl control.
|
|
|
|
When the gtk sends a size signal, it is not send directly to the LCL. All gtk
|
|
size/move messages are collected and only the last one for each widget is sent
|
|
to the LCL.
|
|
This is neccessary, because the gtk sends size messages several times and
|
|
it replays resizes. Since the LCL reacts to every size notification and
|
|
resizes child controls, this results in a perpetuum mobile.
|
|
------------------------------------------------------------------------------}
|
|
procedure SaveSizeNotification(Widget: PGtkWidget);
|
|
{$IFDEF VerboseSizeMsg}
|
|
var
|
|
LCLControl: TWinControl;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF VerboseSizeMsg}
|
|
DbgOut('SaveSizeNotification Widget=',DbgS(Widget));
|
|
LCLControl:=TWinControl(GetLCLObject(Widget));
|
|
if (LCLControl<>nil) then begin
|
|
if LCLControl is TWinControl then
|
|
DebugLn(' ',LCLControl.Name,':',LCLControl.ClassName)
|
|
else
|
|
DebugLn(' ERROR: ',LCLControl.ClassName);
|
|
end else begin
|
|
DebugLn(' ERROR: LCLControl=nil');
|
|
end;
|
|
{$ENDIF}
|
|
if not FWidgetsResized.Contains(Widget) then
|
|
FWidgetsResized.Add(Widget);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
SaveClientSizeNotification
|
|
Params: FixWidget: PGtkWidget A widget that is the fixed widget
|
|
of a lcl control.
|
|
|
|
When the gtk sends a size signal, it is not sent directly to the LCL. All gtk
|
|
size/move messages are collected and only the last one for each widget is sent
|
|
to the LCL.
|
|
This is neccessary, because the gtk sends size messages several times and
|
|
it replays resizes. Since the LCL reacts to every size notification and
|
|
resizes child controls, this results in a perpetuum mobile.
|
|
------------------------------------------------------------------------------}
|
|
procedure SaveClientSizeNotification(FixWidget: PGtkWidget);
|
|
{$IFDEF VerboseSizeMsg}
|
|
var
|
|
LCLControl: TWinControl;
|
|
MainWidget: PGtkWidget;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF VerboseSizeMsg}
|
|
MainWidget:=GetMainWidget(FixWidget);
|
|
//write('SaveClientSizeNotification',
|
|
// ' FixWidget=',DbgS(FixWidget),
|
|
// ' MainWIdget=',DbgS(MainWidget));
|
|
LCLControl:=TWinControl(GetLCLObject(MainWidget));
|
|
if (LCLControl<>nil) then begin
|
|
if LCLControl is TWinControl then begin
|
|
//DebugLn('SaveClientSizeNotification ',LCLControl.Name,':',LCLControl.ClassName,
|
|
// ' FixWidget=',DbgS(FixWidget),
|
|
// ' MainWidget=',DbgS(MainWidget));
|
|
end else begin
|
|
DbgOut('ERROR: SaveClientSizeNotification ',
|
|
' LCLControl=',LCLControl.ClassName,
|
|
' FixWidget=',DbgS(FixWidget),
|
|
' MainWidget=',DbgS(MainWidget));
|
|
RaiseGDBException('SaveClientSizeNotification');
|
|
end;
|
|
end else begin
|
|
DbgOut('ERROR: SaveClientSizeNotification LCLControl=nil',
|
|
' FixWidget=',DbgS(FixWidget),
|
|
' MainWIdget=',DbgS(MainWidget));
|
|
RaiseGDBException('SaveClientSizeNotification');
|
|
end;
|
|
{$ENDIF}
|
|
if not FFixWidgetsResized.Contains(FixWidget) then
|
|
FFixWidgetsResized.Add(FixWidget);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
CreateTopologicalSortedWidgets
|
|
Params: HashArray: TDynHashArray of PGtkWidget
|
|
|
|
Creates a topologically sorted TFPList of PGtkWidget.
|
|
-------------------------------------------------------------------------------}
|
|
function CreateTopologicalSortedWidgets(HashArray: TDynHashArray): TFPList;
|
|
type
|
|
PTopologicalEntry = ^TTopologicalEntry;
|
|
TTopologicalEntry = record
|
|
Widget: PGtkWidget;
|
|
ParentLevel: integer;
|
|
end;
|
|
|
|
function GetParentLevel(AControl: TControl): integer;
|
|
// nil has lvl -1
|
|
// a control without parent has lvl 0
|
|
begin
|
|
Result:=-1;
|
|
while AControl<>nil do begin
|
|
inc(Result);
|
|
AControl:=AControl.Parent;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
TopologicalList: PTopologicalEntry;
|
|
HashItem: PDynHashArrayItem;
|
|
i, Lvl, MaxLevel: integer;
|
|
LCLControl: TControl;
|
|
LevelCounts: PInteger;
|
|
begin
|
|
Result:=TFPList.Create;
|
|
if HashArray.Count=0 then exit;
|
|
|
|
// put all widgets into an array and calculate their parent levels
|
|
GetMem(TopologicalList,SizeOf(TTopologicalEntry)*HashArray.Count);
|
|
HashItem:=HashArray.FirstHashItem;
|
|
i:=0;
|
|
MaxLevel:=0;
|
|
//DebugLn('CreateTopologicalSortedWidgets HashArray.Count=',HashArray.Count);
|
|
while HashItem<>nil do begin
|
|
TopologicalList[i].Widget:=HashItem^.Item;
|
|
//DebugLn('CreateTopologicalSortedWidgets i=',i,' Widget=',DbgS(TopologicalList[i].Widget));
|
|
LCLControl:=TControl(GetLCLObject(TopologicalList[i].Widget));
|
|
if (LCLControl=nil) or (not (LCLControl is TControl)) then
|
|
RaiseGDBException('CreateTopologicalSortedWidgets: '
|
|
+'Widget without LCL control');
|
|
Lvl:=GetParentLevel(LCLControl);
|
|
TopologicalList[i].ParentLevel:=Lvl;
|
|
if MaxLevel<Lvl then
|
|
MaxLevel:=Lvl;
|
|
//DebugLn('CreateTopologicalSortedWidgets i=',i,' Lvl=',Lvl,' MaxLvl=',MaxLevel,' LCLControl=',LCLControl.Name,':',LCLControl.ClassName);
|
|
inc(i);
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
inc(MaxLevel);
|
|
|
|
// bucket sort the widgets
|
|
|
|
// count each number of levels (= bucketsizes)
|
|
GetMem(LevelCounts,SizeOf(Integer)*MaxLevel);
|
|
FillChar(LevelCounts^,SizeOf(Integer)*MaxLevel,0);
|
|
for i:=0 to HashArray.Count-1 do
|
|
inc(LevelCounts[TopologicalList[i].ParentLevel]);
|
|
|
|
// calculate bucketends
|
|
for i:=1 to MaxLevel-1 do
|
|
inc(LevelCounts[i],LevelCounts[i-1]);
|
|
|
|
// bucket sort the widgets in Result
|
|
Result.Count:=HashArray.Count;
|
|
for i:=0 to HashArray.Count-1 do
|
|
Result[i]:=nil;
|
|
for i:=0 to HashArray.Count-1 do begin
|
|
Lvl:=TopologicalList[i].ParentLevel;
|
|
dec(LevelCounts[Lvl]);
|
|
//DebugLn('CreateTopologicalSortedWidgets bucket sort i=',i,' Lvl=',Lvl,' LevelCounts[Lvl]=',LevelCounts[Lvl],
|
|
// ' Widget=',DbgS(TopologicalList[i].Widget));
|
|
Result[LevelCounts[Lvl]]:=TopologicalList[i].Widget;
|
|
end;
|
|
|
|
FreeMem(LevelCounts);
|
|
FreeMem(TopologicalList);
|
|
end;
|
|
|
|
procedure GetGTKDefaultWidgetSize(AWinControl: TWinControl;
|
|
var PreferredWidth, PreferredHeight: integer; WithThemeSpace: Boolean);
|
|
var
|
|
Widget: PGtkWidget;
|
|
Requisition: TGtkRequisition;
|
|
begin
|
|
Widget := PGtkWidget(AWinControl.Handle);
|
|
// set size to default
|
|
//DebugLn(['GetGTKDefaultWidgetSize ',GetWidgetDebugReport(Widget)]);
|
|
{$IFDEF GTK1}
|
|
gtk_widget_set_usize(Widget, -1, -1); // deprecated in gtk2
|
|
{$ELSE}
|
|
gtk_widget_set_size_request(Widget, -1, -1);
|
|
{$ENDIF}
|
|
// ask default size
|
|
gtk_widget_size_request(Widget,@Requisition);
|
|
PreferredWidth:=Requisition.width;
|
|
PreferredHeight:=Requisition.height;
|
|
if WithThemeSpace then begin
|
|
{$IFDEF Gtk1}
|
|
//DebugLn(['GetGTKDefaultWidgetSize WithThemeSpace ',DbgSName(AWinControl),' ',GtkWidgetIsA(Widget,GTK_BUTTON_TYPE),' ',GetWidgetDebugReport(Widget),' ',2*gtk_widget_get_ythickness(Widget)]);
|
|
if gtk_class_get_type(gtk_object_get_class(Widget))=GTK_BUTTON_TYPE then
|
|
inc(PreferredHeight,2*gtk_widget_get_ythickness(Widget))
|
|
else if not GtkWidgetIsA(Widget,GTK_ENTRY_TYPE) then
|
|
dec(PreferredHeight,2*gtk_widget_get_ythickness(Widget));
|
|
{$ENDIF}
|
|
end else begin
|
|
//debugLn('GetGTKDefaultWidgetSize ',DbgSName(AWinControl),' ',dbgs(gtk_widget_get_xthickness(Widget)),' ythickness=',dbgs(gtk_widget_get_ythickness(Widget)));
|
|
//debugLn(['GetGTKDefaultWidgetSize ',GetWidgetDebugReport(Widget)]);
|
|
//dec(PreferredWidth,gtk_widget_get_xthickness(Widget));
|
|
{$IFDEF Gtk1}
|
|
//if not GtkWidgetIsA(Widget,GTK_ENTRY_TYPE) then
|
|
// dec(PreferredHeight,2*gtk_widget_get_ythickness(Widget));
|
|
{$ELSE}
|
|
//if gtk_class_get_type(gtk_object_get_class(Widget))=GTK_TYPE_BUTTON then
|
|
// dec(PreferredHeight,2*gtk_widget_get_ythickness(Widget));
|
|
{$ENDIF}
|
|
end;
|
|
{DebugLn(['GetGTKDefaultWidgetSize Allocation=',Widget^.allocation.x,',',Widget^.allocation.y,',',Widget^.allocation.width,',',Widget^.allocation.height,
|
|
' requisition=',Widget^.requisition.width,',',Widget^.requisition.height,
|
|
' PreferredWidth=',PreferredWidth,' PreferredHeight=',PreferredHeight,
|
|
' WithThemeSpace=',WithThemeSpace]);}
|
|
// set new size
|
|
{$IFDEF GTK1}
|
|
gtk_widget_set_usize(Widget, AWinControl.Width, AWinControl.Height);
|
|
{$ELSE}
|
|
gtk_widget_set_size_request(Widget, AWinControl.Width, AWinControl.Height);
|
|
{$ENDIF}
|
|
//debugln('GetGTKDefaultSize PreferredWidth=',dbgs(PreferredWidth),' PreferredHeight=',dbgs(PreferredHeight));
|
|
end;
|
|
|
|
procedure SendSizeNotificationToLCL(aWidget: PGtkWidget);
|
|
var
|
|
LCLControl: TWinControl;
|
|
LCLLeft, LCLTop, LCLWidth, LCLHeight: integer;
|
|
GtkLeft, GtkTop, GtkWidth, GtkHeight: integer;
|
|
TopLeftChanged, WidthHeightChanged, IsTopLevelWidget: boolean;
|
|
MessageDelivered: boolean;
|
|
SizeMsg: TLMSize;
|
|
MoveMsg: TLMMove;
|
|
PosMsg : TLMWindowPosChanged;
|
|
MainWidget: PGtkWidget;
|
|
FixedWidget: PGtkWidget;
|
|
|
|
procedure UpdateLCLPos;
|
|
begin
|
|
LCLLeft:=LCLControl.Left;
|
|
LCLTop:=LCLControl.Top;
|
|
TopLeftChanged:=(LCLLeft<>GtkLeft) or (LCLTop<>GtkTop);
|
|
end;
|
|
|
|
procedure UpdateLCLSize;
|
|
begin
|
|
LCLWidth:=LCLControl.Width;
|
|
LCLHeight:=LCLControl.Height;
|
|
WidthHeightChanged:=(LCLWidth<>GtkWidth) or (LCLHeight<>GtkHeight);
|
|
if LCLControl.ClientRectNeedsInterfaceUpdate then begin
|
|
WidthHeightChanged:=true;
|
|
//DebugLn(['UpdateLCLSize InvalidateClientRectCache ',DbgSName(LCLControl)]);
|
|
LCLControl.InvalidateClientRectCache(false);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
LCLControl:=TWinControl(GetLCLObject(aWidget));
|
|
if LCLControl=nil then exit;
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn('SendSizeNotificationToLCL checking ... ',DbgSName(LCLControl),' Widget=',WidgetFlagsToString(aWidget));
|
|
{$ENDIF}
|
|
MainWidget:=PGtkWidget(LCLControl.Handle);
|
|
FixedWidget:=PGtkWidget(GetFixedWidget(MainWidget));
|
|
|
|
FWidgetsResized.Remove(MainWidget);
|
|
FFixWidgetsResized.Remove(FixedWidget);
|
|
|
|
{$IF defined(Gtk1)}
|
|
if not GTK_WIDGET_REALIZED(aWidget) then begin
|
|
// the widget is not yet realized, so this GTK resize was not a user change.
|
|
// => ignore
|
|
{$IFDEF VerboseSizeMsg}
|
|
LCLControl:=TWinControl(GetLCLObject(aWidget));
|
|
DebugLn('SendSizeNotificationToLCL ',DbgSName(LCLControl),' aWidget=',WidgetFlagsToString(aWidget),' Ignored, because not realized ');
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
GetWidgetRelativePosition(MainWidget,GtkLeft,GtkTop);
|
|
|
|
{$ifdef gtk2}
|
|
gtk_widget_get_size_request(MainWidget, @GtkWidth, @GtkHeight);
|
|
|
|
if GtkWidth < 0 then
|
|
GtkWidth:=MainWidget^.Allocation.Width
|
|
else
|
|
MainWidget^.Allocation.Width:=GtkWidth;
|
|
if GtkHeight < 0 then
|
|
GtkHeight:=MainWidget^.Allocation.Height
|
|
else
|
|
MainWidget^.Allocation.Height:=GtkHeight;
|
|
//DebugLn(['SendSizeNotificationToLCL ',DbgSName(LCLControl),' gtk=',GtkLeft,',',GtkTop,',',GtkWidth,'x',GtkHeight,' Allocation=',MainWidget^.Allocation.Width,'x',MainWidget^.Allocation.Height]);
|
|
{$else}
|
|
GtkWidth:=MainWidget^.Allocation.Width;
|
|
GtkHeight:=MainWidget^.Allocation.Height;
|
|
{$endif}
|
|
|
|
if GtkWidth<0 then GtkWidth:=0;
|
|
if GtkHeight<0 then GtkHeight:=0;
|
|
|
|
IsTopLevelWidget:=(LCLControl is TCustomForm) and (LCLControl.Parent=nil);
|
|
if IsTopLevelWidget then begin
|
|
if not GTK_WIDGET_VISIBLE(MainWidget) then begin
|
|
// size/move messages of invisible windows are not reliable
|
|
// -> ignore
|
|
exit;
|
|
end;
|
|
if (GtkWidth=1) and (GtkHeight=1) then begin
|
|
// this is default size of the gtk. Ignore.
|
|
exit;
|
|
end;
|
|
//DebugLn(['SendSizeNotificationToLCL FORM ',GetWidgetDebugReport(MainWidget)]);
|
|
|
|
{$IFDEF VerboseFormPositioning}
|
|
DebugLn(['VFP SendSizeNotificationToLCL ',DbgSName(LCLControl),' ',
|
|
GtkLeft,',',GtkTop,',',GtkWidth,'x',GtkHeight,' ',GetWidgetDebugReport(MainWidget)]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
UpdateLCLPos;
|
|
UpdateLCLSize;
|
|
|
|
// first send a LM_WINDOWPOSCHANGED message
|
|
if TopLeftChanged or WidthHeightChanged then begin
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn('SendSizeNotificationToLCL ',DbgSName(LCLControl),
|
|
' GTK=',dbgs(GtkLeft)+','+dbgs(GtkTop)+','+dbgs(GtkWidth)+'x'+dbgs(GtkHeight),
|
|
' LCL=',dbgs(LCLLeft)+','+dbgs(LCLTop)+','+dbgs(LCLWidth)+'x'+dbgs(LCLHeight)
|
|
);
|
|
{$ENDIF}
|
|
PosMsg.Msg := LM_WINDOWPOSCHANGED; //LM_SIZEALLOCATE;
|
|
PosMsg.Result := 0;
|
|
New(PosMsg.WindowPos);
|
|
try
|
|
with PosMsg.WindowPos^ do begin
|
|
hWndInsertAfter := 0;
|
|
x := GtkLeft;
|
|
y := GtkTop;
|
|
cx := GtkWidth;
|
|
cy := GtkHeight;
|
|
flags:=0;
|
|
// flags := SWP_SourceIsInterface;
|
|
end;
|
|
MessageDelivered := DeliverMessage(LCLControl, PosMsg) = 0;
|
|
finally
|
|
Dispose(PosMsg.WindowPos);
|
|
end;
|
|
if (not MessageDelivered) then exit;
|
|
if FWidgetsWithResizeRequest.Contains(MainWidget) then exit;
|
|
UpdateLCLPos;
|
|
UpdateLCLSize;
|
|
end;
|
|
|
|
// then send a LM_SIZE message
|
|
if WidthHeightChanged then begin
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn('Send LM_SIZE To LCL ',LCLControl.Name,':',LCLControl.ClassName);
|
|
{$ENDIF}
|
|
with SizeMsg do
|
|
begin
|
|
Result := 0;
|
|
Msg := LM_SIZE;
|
|
{$IFDEF GTK1}
|
|
if GDK_WINDOW_GET_MAXIMIZED(PGdkWindowPrivate(MainWidget^.window)) then
|
|
SizeType := SIZEFULLSCREEN
|
|
else
|
|
SizeType := SIZENORMAL;
|
|
{$ELSE}
|
|
if LCLControl is TCustomForm then begin
|
|
// if the LCL gets an event without a State it resets it to SIZENORMAL
|
|
// so we send it the state it already is
|
|
case TCustomForm(LCLControl).WindowState of
|
|
wsNormal: SizeType := SIZENORMAL;
|
|
wsMinimized: SizeType := SIZEICONIC;
|
|
wsMaximized: SizeType := SIZEFULLSCREEN;
|
|
end;
|
|
end
|
|
else
|
|
SizeType := 0;
|
|
{$ENDIF}
|
|
SizeType := SizeType or Size_SourceIsInterface;
|
|
Width := SmallInt(GtkWidth);
|
|
Height := SmallInt(GtkHeight);
|
|
end;
|
|
MessageDelivered := (DeliverMessage(LCLControl, SizeMsg) = 0);
|
|
if not MessageDelivered then exit;
|
|
if FWidgetsWithResizeRequest.Contains(MainWidget) then exit;
|
|
UpdateLCLPos;
|
|
end;
|
|
|
|
// then send a LM_MOVE message
|
|
if TopLeftChanged then begin
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn('Send LM_MOVE To LCL ',LCLControl.Name,':',LCLControl.ClassName);
|
|
{$ENDIF}
|
|
with MoveMsg do
|
|
begin
|
|
Result := 0;
|
|
Msg := LM_MOVE;
|
|
MoveType := Move_SourceIsInterface;
|
|
XPos := SmallInt(GtkLeft);
|
|
YPos := SmallInt(GtkTop);
|
|
end;
|
|
MessageDelivered := (DeliverMessage(LCLControl, MoveMsg) = 0);
|
|
if not MessageDelivered then exit;
|
|
end;
|
|
|
|
{$ifndef gtk1}
|
|
if GtkWidgetIsA(aWidget, GTKAPIWidget_Type) and
|
|
not (wwiNoEraseBkgnd in GetWidgetInfo(aWidget)^.Flags) then
|
|
gtk_widget_queue_draw(aWidget);
|
|
{$endif}
|
|
end;
|
|
|
|
procedure SendCachedGtkResizeNotifications;
|
|
{ This proc sends all cached size messages from the gtk to lcl but in an
|
|
optimized order.
|
|
When sending the LCL a size/move/windowposchanged messages the LCL will
|
|
automatically realign all child controls. This realigning is based on the
|
|
clientrect.
|
|
Therefore, before a size message is sent to the lcl, all clientrect must be
|
|
updated.
|
|
If a size message results in resizing a widget that was also resized, then
|
|
the message for the dependent widget is not sent to the lcl, because the lcl
|
|
resize was after the gtk resize.
|
|
}
|
|
var
|
|
FixWidget, MainWidget: PGtkWidget;
|
|
LCLControl: TWinControl;
|
|
List: TFPList;
|
|
i: integer;
|
|
|
|
procedure RaiseInvalidLCLControl;
|
|
begin
|
|
RaiseGDBException(Format('SendCachedGtkResizeNotifications FixWidget=%p MainWidget=%p LCLControl=%p',
|
|
[FixWidget, MainWidget, Pointer(LCLControl)]));
|
|
end;
|
|
|
|
begin
|
|
if (FWidgetsResized.Count=0) and (FFixWidgetsResized.Count=0) then exit;
|
|
|
|
List:=TFPList.Create;
|
|
|
|
{ if any fixed widget was resized then a client area of a LCL control was
|
|
resized
|
|
-> invalidate client rectangles
|
|
}
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn('HHH1 SendCachedGtkClientResizeNotifications Invalidating ClientRects ... '
|
|
,' FixSizeMsgCount=',dbgs(FFixWidgetsResized.Count));
|
|
{$ENDIF}
|
|
FFixWidgetsResized.AssignTo(List);
|
|
for i:=0 to List.Count-1 do begin
|
|
FixWidget:=List[i];
|
|
MainWidget:=GetMainWidget(FixWidget);
|
|
LCLControl:=TWinControl(GetLCLObject(MainWidget));
|
|
if (LCLControl=nil) or (not (LCLControl is TWinControl)) then
|
|
RaiseInvalidLCLControl;
|
|
LCLControl.InvalidateClientRectCache(false);
|
|
end;
|
|
|
|
{ if any main widget (= not fixed widget) was resized
|
|
then a LCL control was resized
|
|
-> send WMSize, WMMove, and WMWindowPosChanged messages
|
|
}
|
|
{$IFDEF VerboseSizeMsg}
|
|
if FWidgetsResized.First<>nil then
|
|
DebugLn('HHH2 SendCachedGtkClientResizeNotifications SizeMsgCount=',dbgs(FWidgetsResized.Count));
|
|
{$ENDIF}
|
|
repeat
|
|
MainWidget:=FWidgetsResized.First;
|
|
if MainWidget<>nil then begin
|
|
FWidgetsResized.Remove(MainWidget);
|
|
if not FWidgetsWithResizeRequest.Contains(MainWidget) then begin
|
|
SendSizeNotificationToLCL(MainWidget);
|
|
end;
|
|
end else break;
|
|
until Application.Terminated;
|
|
|
|
{ if any client area was resized, which MainWidget Size was already in sync
|
|
with the LCL, no message was sent. So, tell each changed client area to
|
|
check its size.
|
|
}
|
|
{$IFDEF VerboseSizeMsg}
|
|
if FFixWidgetsResized.First<>nil then
|
|
DebugLn('HHH3 SendCachedGtkClientResizeNotifications Updating ClientRects ...');
|
|
{$ENDIF}
|
|
repeat
|
|
FixWidget:=FFixWidgetsResized.First;
|
|
if FixWidget<>nil then begin
|
|
FFixWidgetsResized.Remove(FixWidget);
|
|
MainWidget:=GetMainWidget(FixWidget);
|
|
LCLControl:=TWinControl(GetLCLObject(MainWidget));
|
|
LCLControl.DoAdjustClientRectChange(False);
|
|
end else begin
|
|
break;
|
|
end;
|
|
until Application.Terminated;
|
|
|
|
List.Free;
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn('HHH4 SendCachedGtkClientResizeNotifications completed.');
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure ResizeHandle(LCLControl: TWinControl);
|
|
var
|
|
Widget: PGtkWidget;
|
|
Later: Boolean;
|
|
{$IFDEF Gtk2}
|
|
IsTopLevelWidget: Boolean;
|
|
{$ENDIF}
|
|
begin
|
|
Widget := PGtkWidget(LCLControl.Handle);
|
|
if not WidgetSizeIsEditable(Widget) then
|
|
Exit;
|
|
Later := true;
|
|
{$IFDEF Gtk2}
|
|
// add resize request immediately
|
|
IsTopLevelWidget:= (LCLControl is TCustomForm) and
|
|
(LCLControl.Parent = nil) and
|
|
(LCLControl.ParentWindow = 0);
|
|
if not IsTopLevelWidget then
|
|
begin
|
|
SetWidgetSizeAndPosition(LCLControl);
|
|
Later := false;
|
|
end;
|
|
{$ENDIF}
|
|
if Later then
|
|
SetResizeRequest(Widget);
|
|
end;
|
|
|
|
procedure SetWidgetSizeAndPosition(LCLControl: TWinControl);
|
|
var
|
|
Requisition: TGtkRequisition;
|
|
FixedWidget: PGtkWidget;
|
|
{$IFDEF Gtk2}
|
|
allocation: TGtkAllocation;
|
|
{$ENDIF}
|
|
LCLLeft: LongInt;
|
|
LCLTop: LongInt;
|
|
LCLWidth: LongInt;
|
|
LCLHeight: LongInt;
|
|
Widget: PGtkWidget;
|
|
ParentWidget: PGtkWidget;
|
|
ParentFixed: PGtkWidget;
|
|
WinWidgetInfo: PWidgetInfo;
|
|
{$IFDEF VerboseSizeMsg}
|
|
LCLObject: TObject;
|
|
{$ENDIF}
|
|
|
|
procedure WriteBigWarning;
|
|
begin
|
|
DebugLn('WARNING: SetWidgetSizeAndPosition: resizing BIG ',
|
|
' Control=',LCLControl.Name,':',LCLControl.ClassName,
|
|
' NewSize=',dbgs(LCLWidth),',',dbgs(LCLHeight));
|
|
//RaiseException('');
|
|
end;
|
|
|
|
procedure WriteWarningParentWidgetNotFound;
|
|
begin
|
|
DebugLn('WARNING: SetWidgetSizeAndPosition - '
|
|
,'Parent''s Fixed Widget not found');
|
|
DebugLn(' Control=',LCLControl.Name,':',LCLControl.ClassName,
|
|
' Parent=',LCLControl.Parent.Name,':',LCLControl.Parent.ClassName,
|
|
' ParentWidget=',DbgS(ParentWidget),
|
|
'');
|
|
end;
|
|
|
|
begin
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn(['SetWidgetSizeAndPosition ',DbgSName(LCLControl)]);
|
|
{$ENDIF}
|
|
Widget:=PGtkWidget(LCLControl.Handle);
|
|
|
|
LCLLeft := LCLControl.Left;
|
|
LCLTop := LCLControl.Top;
|
|
|
|
// move widget on the fixed widget of parent control
|
|
if ((LCLControl.Parent <> nil) and (LCLControl.Parent.HandleAllocated)) or
|
|
((LCLControl.Parent = nil) and (LCLControl.ParentWindow <> 0)) then
|
|
begin
|
|
if LCLControl.Parent <> nil then
|
|
ParentWidget := PGtkWidget(LCLControl.Parent.Handle)
|
|
else
|
|
ParentWidget := PGtkWidget(LCLControl.ParentWindow);
|
|
ParentFixed := GetFixedWidget(ParentWidget);
|
|
if GtkWidgetIsA(ParentFixed,GTK_FIXED_GET_TYPE) or
|
|
GtkWidgetIsA(ParentFixed,GTK_LAYOUT_GET_TYPE) then
|
|
begin
|
|
//DebugLn(['SetWidgetSizeAndPosition ',DbgSName(LCLControl),' Widget=[',GetWidgetDebugReport(Widget),'] ParentFixed=[',GetWidgetDebugReport(ParentFixed),']']);
|
|
FixedMoveControl(ParentFixed, Widget, LCLLeft, LCLTop);
|
|
end
|
|
else
|
|
begin
|
|
WinWidgetInfo := GetWidgetInfo(Widget, False);
|
|
if (WinWidgetInfo = nil) or (not (wwiNotOnParentsClientArea in WinWidgetInfo^.Flags)) then
|
|
WriteWarningParentWidgetNotFound;
|
|
end;
|
|
end;
|
|
|
|
// resize widget
|
|
LCLWidth := LCLControl.Width;
|
|
if LCLWidth <= 0 then
|
|
LCLWidth := 1;
|
|
LCLHeight := LCLControl.Height;
|
|
if LCLHeight <= 0 then
|
|
LCLHeight := 1;
|
|
if (LCLWidth > 10000) or (LCLHeight > 10000) then
|
|
begin
|
|
WriteBigWarning;
|
|
if LCLWidth > 10000 then
|
|
LCLWidth := 10000;
|
|
if LCLHeight > 10000 then
|
|
LCLHeight := 10000;
|
|
end;
|
|
|
|
{$IFDEF VerboseSizeMsg}
|
|
LCLObject:=GetNearestLCLObject(Widget);
|
|
DbgOut('TGtkWidgetSet.SetWidgetSizeAndPosition Widget='+DbgS(Widget)+WidgetFlagsToString(Widget)+
|
|
' New='+dbgs(LCLWidth)+','+dbgs(LCLHeight));
|
|
if (LCLObject<>nil) and (LCLObject is TControl) then begin
|
|
with TControl(LCLObject) do
|
|
DebugLn(' LCL=',Name,':',ClassName,' ',dbgs(Left),',',dbgs(Top),',',dbgs(Width),',',dbgs(Height));
|
|
end else begin
|
|
DebugLn(' LCL=',DbgS(LCLObject));
|
|
end;
|
|
{$ENDIF}
|
|
|
|
if GtkWidgetIsA(Widget,GTK_TYPE_SCROLLBAR) then
|
|
begin
|
|
// the width of a scrollbar is fixed and depends only on the theme
|
|
gtk_widget_size_request(widget, @Requisition);
|
|
if GtkWidgetIsA(Widget, GTK_TYPE_HSCROLLBAR) then
|
|
begin
|
|
LCLHeight:=Requisition.height;
|
|
end else begin
|
|
LCLWidth:=Requisition.width;
|
|
end;
|
|
//DebugLn('TGtkWidgetSet.SetWidgetSizeAndPosition A ',LCLwidth,',',LCLheight);
|
|
end;
|
|
|
|
gtk_widget_set_usize(Widget, LCLWidth, LCLHeight);
|
|
//DebugLn(['TGtkWidgetSet.SetWidgetSizeAndPosition ',GetWidgetDebugReport(Widget),' LCLWidth=',LCLWidth,' LCLHeight=',LCLHeight]);
|
|
|
|
{$IFDEF Gtk1}
|
|
if GtkWidgetIsA(Widget, GTK_TYPE_COMBO) then
|
|
begin
|
|
// the combobox has an entry, which height is not resized
|
|
// automatically. Do it manually.
|
|
gtk_widget_set_usize(PGtkCombo(Widget)^.entry,
|
|
PGtkCombo(Widget)^.entry^.allocation.width, LCLHeight);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
if GtkWidgetIsA(Widget,gtk_toolbar_get_type) then begin
|
|
FixedWidget:=GetFixedWidget(Widget);
|
|
if (FixedWidget<>nil) and (FixedWidget<>Widget) then begin
|
|
//DebugLn('WARNING: ToDo TGtkWidgetSet.SetWidgetSizeAndPosition for TToolBar ',LCLWidth,',',LCLHeight);
|
|
gtk_widget_set_usize(FixedWidget,LCLWidth,LCLHeight);
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF Gtk2}
|
|
if (Widget^.parent<>nil)
|
|
and GtkWidgetIsA(Widget^.parent,GTK_TYPE_FIXED)
|
|
and GTK_WIDGET_NO_WINDOW(Widget^.parent)
|
|
then begin
|
|
inc(LCLLeft, Widget^.parent^.allocation.x);
|
|
inc(LCLTop, Widget^.parent^.allocation.y);
|
|
end;
|
|
|
|
// commit size and position
|
|
allocation:=Widget^.allocation;
|
|
allocation.x:=LCLLeft;
|
|
allocation.y:=LCLTop;
|
|
allocation.width:=LCLWidth;
|
|
allocation.height:=LCLHeight;
|
|
//DebugLn(['SetWidgetSizeAndPosition ',DbgSName(LCLControl),' LCL=',dbgs(LCLControl.BoundsRect),' allocation=',dbgs(allocation),' ',GetWidgetDebugReport(Widget)]);
|
|
gtk_widget_size_allocate(Widget,@allocation);// Beware: this triggers callbacks
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: SetWindowSizeAndPosition
|
|
Params: Widget: PGtkWidget; AWinControl: TWinControl
|
|
Returns: Nothing
|
|
|
|
Set the size and position of a top level window.
|
|
------------------------------------------------------------------------------}
|
|
procedure SetWindowSizeAndPosition(Window: PGtkWindow;
|
|
AWinControl: TWinControl);
|
|
var
|
|
Width, Height: integer;
|
|
{$IFDEF Gtk2}
|
|
allocation: TGtkAllocation;
|
|
{$ENDIF}
|
|
//Info: PGtkWindowGeometryInfo;
|
|
begin
|
|
Width:=AWinControl.Width;
|
|
// 0 and negative values have a special meaning, so don't use them
|
|
if Width<=0 then Width:=1;
|
|
Height:=AWinControl.Height;
|
|
if Height<=0 then Height:=1;
|
|
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn(['TGtkWidgetSet.SetWindowSizeAndPosition START ',DbgSName(AWinControl),' ',AWinControl.Visible,' Old=',PGtkWidget(Window)^.allocation.Width,',',PGtkWidget(Window)^.allocation.Width,' New=',Width,',',Height]);
|
|
{$ENDIF}
|
|
// set geometry default size
|
|
//Info:=gtk_window_get_geometry_info(Window, TRUE);
|
|
//if (Info^.default_width<>Width) or (Info^.default_height<>Height) then
|
|
gtk_window_set_default_size(Window, Width, Height);
|
|
|
|
{$IFDEF Gtk2}
|
|
// resize
|
|
gtk_window_resize(Window, Width, Height);
|
|
// reposition
|
|
gtk_window_move(Window, AWinControl.Left, AWinControl.Top);
|
|
// force early resize
|
|
allocation := PGtkWidget(Window)^.allocation;
|
|
allocation.width := Width;
|
|
allocation.height := Height;
|
|
//DebugLn(['SetWindowSizeAndPosition ',DbgSName(AWinControl),' ',dbgs(allocation)]);
|
|
gtk_widget_size_allocate(PGtkWidget(Window), @allocation);// Beware: this triggers callbacks
|
|
|
|
if (PGtkWidget(Window)^.Window <> nil) then
|
|
begin
|
|
// resize gdkwindow directly (sometimes the gtk forgets this)
|
|
gdk_window_move_resize(PGtkWidget(Window)^.Window,AWinControl.Left,
|
|
AWinControl.Top,Width,Height)
|
|
end;
|
|
{$ELSE}
|
|
// resize
|
|
if assigned(PGtkWidget(Window)^.Window) then
|
|
// widget is realized, resize gdkwindow directly
|
|
gdk_window_move_resize(PGtkWidget(Window)^.Window,AWinControl.Left,
|
|
AWinControl.Top,Width,Height)
|
|
else begin
|
|
// widget is not yet realized, force resize needed for shrinking under gtk1
|
|
gtk_widget_set_usize(PGtkWidget(Window), -1,-1);
|
|
end;
|
|
// reposition
|
|
gtk_widget_set_usize(PGtkWidget(Window),Width,Height);
|
|
gtk_widget_set_uposition(PGtkWidget(Window),AWinControl.Left,AWinControl.Top);
|
|
{$ENDIF}
|
|
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn(['SetWindowSizeAndPosition B ',DbgSName(AWinControl),
|
|
' Visible=',AWinControl.Visible,
|
|
' Cur=',PGtkWidget(Window)^.allocation.X,',',PGtkWidget(Window)^.allocation.Y,
|
|
' New=',AWinControl.Left,',',AWinControl.Top,',',Width,'x',Height]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
GetWidgetRelativePosition
|
|
|
|
Returns the Left, Top, relative to the client origin of its parent
|
|
-------------------------------------------------------------------------------}
|
|
procedure GetWidgetRelativePosition(aWidget: PGtkWidget; var Left, Top: integer);
|
|
var
|
|
GdkWindow: PGdkWindow;
|
|
LCLControl: TWinControl;
|
|
GtkLeft, GtkTop: GInt;
|
|
begin
|
|
Left:=aWidget^.allocation.X;
|
|
Top:=aWidget^.allocation.Y;
|
|
{$IFDEF Gtk2}
|
|
if (aWidget^.parent<>nil)
|
|
and (not GtkWidgetIsA(aWidget^.parent,GTK_TYPE_FIXED))
|
|
and (not GtkWidgetIsA(aWidget^.parent,GTK_TYPE_LAYOUT))
|
|
then begin
|
|
// widget is not on a normal client area. e.g. TPage
|
|
Left:=0;
|
|
Top:=0;
|
|
end
|
|
else
|
|
if (aWidget^.parent<>nil)
|
|
and GtkWidgetIsA(aWidget^.parent,GTK_TYPE_FIXED)
|
|
and GTK_WIDGET_NO_WINDOW(aWidget^.parent)
|
|
then begin
|
|
// widget on a fixed, but fixed w/o window
|
|
Dec(Left, PGtkWidget(aWidget^.parent)^.allocation.x);
|
|
Dec(Top, PGtkWidget(aWidget^.parent)^.allocation.y);
|
|
end;
|
|
{$ENDIF}
|
|
if GtkWidgetIsA(aWidget,GTK_TYPE_WINDOW) then begin
|
|
GdkWindow:=GetControlWindow(aWidget);
|
|
if (GdkWindow<>nil) and (GTK_WIDGET_MAPPED(aWidget)) then begin
|
|
// window is mapped = window manager has put the window somewhere
|
|
gdk_window_get_root_origin(GdkWindow, @GtkLeft, @GtkTop);
|
|
Left := GtkLeft;
|
|
Top := GtkTop;
|
|
end else begin
|
|
// the gtk has not yet put the window to the final position
|
|
// => the gtk/gdk position is not reliable
|
|
// => use the LCL coords
|
|
LCLControl:=GetLCLObject(aWidget) as TWinControl;
|
|
Left:=LCLControl.Left;
|
|
Top:=LCLControl.Top;
|
|
end;
|
|
//DebugLn(['TGtkWidgetSet.GetWindowRelativePosition ',GetWidgetDebugReport(aWidget),' Left=',Left,' Top=',Top,' GdkWindow=',GdkWindow<>nil]);
|
|
end;
|
|
//DebugLn(['TGtkWidgetSet.GetWindowRelativePosition ',GetWidgetDebugReport(aWidget),' Left=',Left,' Top=',Top]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
UnsetResizeRequest
|
|
Params: Widget: PGtkWidget
|
|
|
|
Unset the mark for the Widget to send a ResizeRequest to the gtk.
|
|
LCL size requests for a widget are cached and only the last one is sent. Some
|
|
widgets like forms send a resize request immediately. To avoid sending resize
|
|
requests multiple times they can unset the mark with this procedure.
|
|
------------------------------------------------------------------------------}
|
|
procedure UnsetResizeRequest(Widget: PGtkWidget);
|
|
begin
|
|
{$IFDEF VerboseSizeMsg}
|
|
if FWidgetsWithResizeRequest.Contains(Widget) then begin
|
|
DebugLn(['UnsetResizeRequest ',GetWidgetDebugReport(Widget)]);
|
|
end;
|
|
{$ENDIF}
|
|
FWidgetsWithResizeRequest.Remove(Widget);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TGtkWidgetSet SetResizeRequest
|
|
Params: Widget: PGtkWidget
|
|
|
|
Marks the widget to send a ResizeRequest to the gtk.
|
|
When the LCL resizes a control the new bounds will not be set directly, but
|
|
cached. This is needed, because it is common behaviour to set the bounds step
|
|
by step. For example: Left:=10; Top:=10; Width:=100; Height:=50; results in
|
|
SetBounds(10,0,0,0);
|
|
SetBounds(10,10,0,0);
|
|
SetBounds(10,10,100,0);
|
|
SetBounds(10,10,100,50);
|
|
Because the gtk puts all size requests into a queue, it will process the
|
|
requests not immediately, but _after_ all requests. This results in changing
|
|
the widget size four times and everytime the LCL gets a message. If the
|
|
control has childs, this will resize the childs four times.
|
|
Therefore LCL size requests for a widget are cached and only the final one is
|
|
sent in: TGtkWidgetSet.SendCachedLCLMessages.
|
|
------------------------------------------------------------------------------}
|
|
procedure SetResizeRequest(Widget: PGtkWidget);
|
|
{$IFDEF VerboseSizeMsg}
|
|
var
|
|
LCLControl: TWinControl;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF Gtk2}
|
|
if not WidgetSizeIsEditable(Widget) then exit;
|
|
{$ENDIF}
|
|
{$IFDEF VerboseSizeMsg}
|
|
LCLControl:=TWinControl(GetLCLObject(Widget));
|
|
DbgOut('SetResizeRequest Widget=',DbgS(Widget));
|
|
if LCLControl is TWinControl then
|
|
DebugLn(' ',DbgSName(LCLControl),' LCLBounds=',dbgs(LCLControl.BoundsRect))
|
|
else
|
|
DebugLn(' ERROR: ',DbgSName(LCLControl));
|
|
{$ENDIF}
|
|
if not FWidgetsWithResizeRequest.Contains(Widget) then
|
|
FWidgetsWithResizeRequest.Add(Widget);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function WidgetSizeIsEditable(Widget: PGtkWidget): boolean;
|
|
|
|
True if the widget can be resized.
|
|
False if the size is under complete control of the gtk.
|
|
------------------------------------------------------------------------------}
|
|
function WidgetSizeIsEditable(Widget: PGtkWidget): boolean;
|
|
begin
|
|
if Widget=nil then exit(false);
|
|
if (GtkWidgetIsA(Widget,GTK_TYPE_WINDOW))
|
|
or (GtkWidgetIsA(Widget^.Parent,gtk_fixed_get_type))
|
|
or (GtkWidgetIsA(Widget^.Parent,gtk_layout_get_type))
|
|
then
|
|
Result:=true
|
|
else
|
|
Result:=false;
|
|
end;
|
|
|
|
procedure ReportNotObsolete(const Texts : String);
|
|
Begin
|
|
DebugLn('*********************************************');
|
|
DebugLn('*********************************************');
|
|
DebugLn('*************Non-Obsolete report*************');
|
|
DebugLn('*********************************************');
|
|
DebugLn('*************'+Texts+'*is being used yet.****');
|
|
DebugLn('*******Please remove this function from******');
|
|
DebugLn('*******the obsolete section in gtkproc.inc***');
|
|
DebugLn('*********************************************');
|
|
DebugLn('*********************************************');
|
|
DebugLn('*********************************************');
|
|
DebugLn('*********************************************');
|
|
end;
|
|
|
|
function TGDKColorToTColor(const value : TGDKColor) : TColor;
|
|
begin
|
|
Result := ((Value.Blue shr 8) shl 16) + ((Value.Green shr 8) shl 8)
|
|
+ (Value.Red shr 8);
|
|
end;
|
|
|
|
function TColortoTGDKColor(const value : TColor) : TGDKColor;
|
|
var
|
|
newColor : TGDKColor;
|
|
begin
|
|
if Value<0 then begin
|
|
FillChar(Result,SizeOf(Result),0);
|
|
exit;
|
|
end;
|
|
|
|
newColor.pixel := 0;
|
|
newColor.red := (value and $ff) * 257;
|
|
newColor.green := ((value shr 8) and $ff) * 257;
|
|
newColor.blue := ((value shr 16) and $ff) * 257;
|
|
|
|
Result := newColor;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: UpdateSysColorMap
|
|
Params: none
|
|
Returns: none
|
|
|
|
Reads the system colors.
|
|
------------------------------------------------------------------------------}
|
|
procedure UpdateSysColorMap(Widget: PGtkWidget; Lgs: TLazGtkStyle);
|
|
{$IFDEF VerboseUpdateSysColorMap}
|
|
function GdkColorAsString(c: TgdkColor): string;
|
|
begin
|
|
Result:='LCL='+DbgS(TGDKColorToTColor(c))
|
|
+' Pixel='+DbgS(c.Pixel)
|
|
+' Red='+DbgS(c.Red)
|
|
+' Green='+DbgS(c.Green)
|
|
+' Blue='+DbgS(c.Blue)
|
|
;
|
|
end;
|
|
{$ENDIF}
|
|
var
|
|
MainStyle: PGtkStyle;
|
|
begin
|
|
if Widget=nil then exit;
|
|
if not (Lgs in [lgsButton, lgsWindow, lgsMenuBar, lgsMenuitem,
|
|
lgsVerticalScrollbar, lgsHorizontalScrollbar, lgsTooltip]) then exit;
|
|
|
|
{$IFDEF NoStyle}
|
|
exit;
|
|
{$ENDIF}
|
|
//debugln('UpdateSysColorMap ',GetWidgetDebugReport(Widget));
|
|
gtk_widget_set_rc_style(Widget);
|
|
MainStyle := gtk_widget_get_style(Widget);
|
|
if MainStyle = nil then exit;
|
|
with MainStyle^ do
|
|
begin
|
|
{$IFDEF VerboseUpdateSysColorMap}
|
|
if rc_style<>nil then
|
|
begin
|
|
with rc_style^ do
|
|
begin
|
|
DebugLn('rc_style:');
|
|
DebugLn(' FG GTK_STATE_NORMAL ',GdkColorAsString(fg[GTK_STATE_NORMAL]));
|
|
DebugLn(' FG GTK_STATE_ACTIVE ',GdkColorAsString(fg[GTK_STATE_ACTIVE]));
|
|
DebugLn(' FG GTK_STATE_PRELIGHT ',GdkColorAsString(fg[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' FG GTK_STATE_SELECTED ',GdkColorAsString(fg[GTK_STATE_SELECTED]));
|
|
DebugLn(' FG GTK_STATE_INSENSITIVE ',GdkColorAsString(fg[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
DebugLn(' BG GTK_STATE_NORMAL ',GdkColorAsString(bg[GTK_STATE_NORMAL]));
|
|
DebugLn(' BG GTK_STATE_ACTIVE ',GdkColorAsString(bg[GTK_STATE_ACTIVE]));
|
|
DebugLn(' BG GTK_STATE_PRELIGHT ',GdkColorAsString(bg[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' BG GTK_STATE_SELECTED ',GdkColorAsString(bg[GTK_STATE_SELECTED]));
|
|
DebugLn(' BG GTK_STATE_INSENSITIVE ',GdkColorAsString(bg[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
DebugLn(' TEXT GTK_STATE_NORMAL ',GdkColorAsString(text[GTK_STATE_NORMAL]));
|
|
DebugLn(' TEXT GTK_STATE_ACTIVE ',GdkColorAsString(text[GTK_STATE_ACTIVE]));
|
|
DebugLn(' TEXT GTK_STATE_PRELIGHT ',GdkColorAsString(text[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' TEXT GTK_STATE_SELECTED ',GdkColorAsString(text[GTK_STATE_SELECTED]));
|
|
DebugLn(' TEXT GTK_STATE_INSENSITIVE ',GdkColorAsString(text[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
end;
|
|
end;
|
|
|
|
DebugLn('MainStyle:');
|
|
DebugLn(' FG GTK_STATE_NORMAL ',GdkColorAsString(fg[GTK_STATE_NORMAL]));
|
|
DebugLn(' FG GTK_STATE_ACTIVE ',GdkColorAsString(fg[GTK_STATE_ACTIVE]));
|
|
DebugLn(' FG GTK_STATE_PRELIGHT ',GdkColorAsString(fg[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' FG GTK_STATE_SELECTED ',GdkColorAsString(fg[GTK_STATE_SELECTED]));
|
|
DebugLn(' FG GTK_STATE_INSENSITIVE ',GdkColorAsString(fg[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
DebugLn(' BG GTK_STATE_NORMAL ',GdkColorAsString(bg[GTK_STATE_NORMAL]));
|
|
DebugLn(' BG GTK_STATE_ACTIVE ',GdkColorAsString(bg[GTK_STATE_ACTIVE]));
|
|
DebugLn(' BG GTK_STATE_PRELIGHT ',GdkColorAsString(bg[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' BG GTK_STATE_SELECTED ',GdkColorAsString(bg[GTK_STATE_SELECTED]));
|
|
DebugLn(' BG GTK_STATE_INSENSITIVE ',GdkColorAsString(bg[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
DebugLn(' TEXT GTK_STATE_NORMAL ',GdkColorAsString(text[GTK_STATE_NORMAL]));
|
|
DebugLn(' TEXT GTK_STATE_ACTIVE ',GdkColorAsString(text[GTK_STATE_ACTIVE]));
|
|
DebugLn(' TEXT GTK_STATE_PRELIGHT ',GdkColorAsString(text[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' TEXT GTK_STATE_SELECTED ',GdkColorAsString(text[GTK_STATE_SELECTED]));
|
|
DebugLn(' TEXT GTK_STATE_INSENSITIVE ',GdkColorAsString(text[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
DebugLn(' LIGHT GTK_STATE_NORMAL ',GdkColorAsString(light[GTK_STATE_NORMAL]));
|
|
DebugLn(' LIGHT GTK_STATE_ACTIVE ',GdkColorAsString(light[GTK_STATE_ACTIVE]));
|
|
DebugLn(' LIGHT GTK_STATE_PRELIGHT ',GdkColorAsString(light[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' LIGHT GTK_STATE_SELECTED ',GdkColorAsString(light[GTK_STATE_SELECTED]));
|
|
DebugLn(' LIGHT GTK_STATE_INSENSITIVE ',GdkColorAsString(light[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
DebugLn(' DARK GTK_STATE_NORMAL ',GdkColorAsString(dark[GTK_STATE_NORMAL]));
|
|
DebugLn(' DARK GTK_STATE_ACTIVE ',GdkColorAsString(dark[GTK_STATE_ACTIVE]));
|
|
DebugLn(' DARK GTK_STATE_PRELIGHT ',GdkColorAsString(dark[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' DARK GTK_STATE_SELECTED ',GdkColorAsString(dark[GTK_STATE_SELECTED]));
|
|
DebugLn(' DARK GTK_STATE_INSENSITIVE ',GdkColorAsString(dark[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
DebugLn(' MID GTK_STATE_NORMAL ',GdkColorAsString(mid[GTK_STATE_NORMAL]));
|
|
DebugLn(' MID GTK_STATE_ACTIVE ',GdkColorAsString(mid[GTK_STATE_ACTIVE]));
|
|
DebugLn(' MID GTK_STATE_PRELIGHT ',GdkColorAsString(mid[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' MID GTK_STATE_SELECTED ',GdkColorAsString(mid[GTK_STATE_SELECTED]));
|
|
DebugLn(' MID GTK_STATE_INSENSITIVE ',GdkColorAsString(mid[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
DebugLn(' BASE GTK_STATE_NORMAL ',GdkColorAsString(base[GTK_STATE_NORMAL]));
|
|
DebugLn(' BASE GTK_STATE_ACTIVE ',GdkColorAsString(base[GTK_STATE_ACTIVE]));
|
|
DebugLn(' BASE GTK_STATE_PRELIGHT ',GdkColorAsString(base[GTK_STATE_PRELIGHT]));
|
|
DebugLn(' BASE GTK_STATE_SELECTED ',GdkColorAsString(base[GTK_STATE_SELECTED]));
|
|
DebugLn(' BASE GTK_STATE_INSENSITIVE ',GdkColorAsString(base[GTK_STATE_INSENSITIVE]));
|
|
DebugLn('');
|
|
DebugLn(' BLACK ',GdkColorAsString(black));
|
|
DebugLn(' WHITE ',GdkColorAsString(white));
|
|
{$ENDIF}
|
|
|
|
{$IFNDEF DisableGtkSysColors}
|
|
// this map is taken from this research:
|
|
// http://www.endolith.com/wordpress/2008/08/03/wine-colors/
|
|
case Lgs of
|
|
lgsButton:
|
|
begin
|
|
SysColorMap[COLOR_ACTIVEBORDER] := TGDKColorToTColor(bg[GTK_STATE_INSENSITIVE]);
|
|
SysColorMap[COLOR_INACTIVEBORDER] := TGDKColorToTColor(bg[GTK_STATE_INSENSITIVE]);
|
|
SysColorMap[COLOR_WINDOWFRAME] := TGDKColorToTColor(mid[GTK_STATE_SELECTED]);
|
|
|
|
SysColorMap[COLOR_BTNFACE] := TGDKColorToTColor(bg[GTK_STATE_INSENSITIVE]);
|
|
SysColorMap[COLOR_BTNSHADOW] := TGDKColorToTColor(dark[GTK_STATE_INSENSITIVE]);
|
|
SysColorMap[COLOR_BTNTEXT] := TGDKColorToTColor(fg[GTK_STATE_INSENSITIVE]);
|
|
SysColorMap[COLOR_BTNHIGHLIGHT] := TGDKColorToTColor(light[GTK_STATE_INSENSITIVE]);
|
|
SysColorMap[COLOR_3DDKSHADOW] := TGDKColorToTColor(black);
|
|
SysColorMap[COLOR_3DLIGHT] := TGDKColorToTColor(bg[GTK_STATE_INSENSITIVE]);
|
|
end;
|
|
lgsWindow:
|
|
begin
|
|
// colors which can be only retrieved from the window manager (metacity)
|
|
SysColorMap[COLOR_ACTIVECAPTION] := TGDKColorToTColor(dark[GTK_STATE_SELECTED]);
|
|
SysColorMap[COLOR_INACTIVECAPTION] := TGDKColorToTColor(dark[GTK_STATE_NORMAL]);
|
|
SysColorMap[COLOR_GRADIENTACTIVECAPTION] := TGDKColorToTColor(light[GTK_STATE_SELECTED]);
|
|
SysColorMap[COLOR_GRADIENTINACTIVECAPTION] := TGDKColorToTColor(base[GTK_STATE_NORMAL]);
|
|
SysColorMap[COLOR_CAPTIONTEXT] := TGDKColorToTColor(white);
|
|
SysColorMap[COLOR_INACTIVECAPTIONTEXT] := TGDKColorToTColor(white);
|
|
// others
|
|
SysColorMap[COLOR_APPWORKSPACE] := TGDKColorToTColor(base[GTK_STATE_NORMAL]);
|
|
SysColorMap[COLOR_GRAYTEXT] := TGDKColorToTColor(fg[GTK_STATE_INSENSITIVE]);
|
|
SysColorMap[COLOR_HIGHLIGHT] := TGDKColorToTColor(base[GTK_STATE_SELECTED]);
|
|
SysColorMap[COLOR_HIGHLIGHTTEXT] := TGDKColorToTColor(fg[GTK_STATE_SELECTED]);
|
|
SysColorMap[COLOR_WINDOW] := TGDKColorToTColor(base[GTK_STATE_NORMAL]);
|
|
SysColorMap[COLOR_WINDOWTEXT] := TGDKColorToTColor(text[GTK_STATE_NORMAL]);
|
|
SysColorMap[COLOR_HOTLIGHT] := TGDKColorToTColor(light[GTK_STATE_NORMAL]);
|
|
SysColorMap[COLOR_BACKGROUND] := TGDKColorToTColor(bg[GTK_STATE_PRELIGHT]);
|
|
SysColorMap[COLOR_FORM] := TGDKColorToTColor(bg[GTK_STATE_NORMAL]);
|
|
end;
|
|
lgsMenuBar:
|
|
begin
|
|
SysColorMap[COLOR_MENUBAR] := TGDKColorToTColor(bg[GTK_STATE_NORMAL]);
|
|
end;
|
|
lgsMenuitem:
|
|
begin
|
|
SysColorMap[COLOR_MENU] := TGDKColorToTColor(light[GTK_STATE_ACTIVE]);
|
|
SysColorMap[COLOR_MENUTEXT] := TGDKColorToTColor(fg[GTK_STATE_NORMAL]);
|
|
SysColorMap[COLOR_MENUHILIGHT] := TGDKColorToTColor(bg[GTK_STATE_PRELIGHT]);
|
|
end;
|
|
lgsVerticalScrollbar,
|
|
lgsHorizontalScrollbar:
|
|
begin
|
|
SysColorMap[COLOR_SCROLLBAR] := TGDKColorToTColor(bg[GTK_STATE_ACTIVE]);
|
|
end;
|
|
lgsTooltip:
|
|
begin
|
|
SysColorMap[COLOR_INFOTEXT] := TGDKColorToTColor(fg[GTK_STATE_NORMAL]);
|
|
SysColorMap[COLOR_INFOBK] := TGDKColorToTColor(bg[GTK_STATE_NORMAL]);
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: WaitForClipbrdAnswerDummyTimer
|
|
|
|
this is a helper function for WaitForClipboardAnswer
|
|
------------------------------------------------------------------------------}
|
|
function WaitForClipbrdAnswerDummyTimer(Client: Pointer): {$IFDEF Gtk2}gboolean{$ELSE}gint{$ENDIF}; cdecl;
|
|
begin
|
|
if CLient=nil then ;
|
|
Result:=GdkTrue; // go on, make sure getting a message at least every second
|
|
end;
|
|
|
|
function GetScreenWidthMM(GdkValue: boolean): integer;
|
|
begin
|
|
Result:=gdk_screen_width_mm;
|
|
if (Result<=0) and not GdkValue then
|
|
Result:=300; // some TV-out screens don't know there size
|
|
end;
|
|
|
|
function GetScreenHeightMM(GdkValue: boolean): integer;
|
|
begin
|
|
Result:=gdk_screen_height_mm;
|
|
if (Result<=0) and not GdkValue then
|
|
Result:=300; // some TV-out screens don't know there size
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: WaitForClipboardAnswer
|
|
Params: none
|
|
Returns: true, if clipboard data arrived
|
|
|
|
waits til clipboard/selection answer arrived (max 1 second)
|
|
! While waiting the messagequeue will be processed !
|
|
------------------------------------------------------------------------------}
|
|
function WaitForClipboardAnswer(c: PClipboardEventData): boolean;
|
|
var
|
|
StartTime, CurTime: TSystemTime;
|
|
Timer: cardinal;
|
|
|
|
function ValidDateSelection : boolean;
|
|
begin
|
|
result := c^.Data.Selection<>0;
|
|
end;
|
|
|
|
begin
|
|
Result:=false;
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[WaitForClipboardAnswer] A');
|
|
{$ENDIF}
|
|
if (ValidDateSelection) or (c^.Waiting) or (c^.Stopping) then begin
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[WaitForClipboardAnswer] ValidDateSelection=',dbgs(ValidDateSelection),' Waiting=',dbgs(c^.Waiting),' Stopping=',dbgs(c^.Stopping));
|
|
{$ENDIF}
|
|
Result:=(ValidDateSelection);
|
|
exit;
|
|
end;
|
|
c^.Waiting:=true;
|
|
DateTimeToSystemTime(Time,StartTime);
|
|
//DebugLn('[WaitForClipboardAnswer] C');
|
|
Application.ProcessMessages;
|
|
//DebugLn('[WaitForClipboardAnswer] D');
|
|
if (ValidDateSelection) or (c^.Stopping) then begin
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[WaitForClipboardAnswer] E Yeah, Response received after processing messages');
|
|
{$ENDIF}
|
|
Result:=(ValidDateSelection);
|
|
exit;
|
|
end;
|
|
//DebugLn('[WaitForClipboardAnswer] F');
|
|
// start a timer to make sure not waiting forever
|
|
Timer := gtk_timeout_add(500, @WaitForClipbrdAnswerDummyTimer, nil);
|
|
try
|
|
repeat
|
|
// just wait ...
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[WaitForClipboardAnswer] G');
|
|
{$ENDIF}
|
|
Application.ProcessMessages;
|
|
if (ValidDateSelection) or (c^.Stopping) then begin
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[WaitForClipboardAnswer] H Yeah, Response received after waiting with timer');
|
|
{$ENDIF}
|
|
Result:=(ValidDateSelection);
|
|
exit;
|
|
end;
|
|
DateTimeToSystemTime(Time,CurTime);
|
|
until (CurTime.Second*1000+CurTime.MilliSecond
|
|
-StartTime.Second*1000-StartTime.MilliSecond
|
|
>1000);
|
|
finally
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[WaitForClipboardAnswer] H');
|
|
{$ENDIF}
|
|
// stop the timer
|
|
gtk_timeout_remove(Timer);
|
|
//DebugLn('[WaitForClipboardAnswer] END');
|
|
end;
|
|
{ $IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[WaitForClipboardAnswer] WARNING: no answer received in time');
|
|
{ $ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: RequestSelectionData
|
|
Params: ClipboardWidget - widget with connected signals 'selection_get'
|
|
and 'selection_clear_event'
|
|
ClipboardType
|
|
FormatID - the selection target format wanted
|
|
Returns: the TGtkSelectionData record
|
|
|
|
requests the format FormatID of clipboard of type ClipboardType and
|
|
waits til clipboard/selection answer arrived (max 1 second)
|
|
! While waiting the messagequeue will be processed !
|
|
------------------------------------------------------------------------------}
|
|
function RequestSelectionData(ClipboardWidget: PGtkWidget;
|
|
ClipboardType: TClipboardType; FormatID: PtrUInt): TGtkSelectionData;
|
|
|
|
function TimeIDExists(TimeID: guint32): boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
i:=ClipboardSelectionData.Count-1;
|
|
while (i>=0) do begin
|
|
if (PClipboardEventData(ClipboardSelectionData[i])^.TimeID=TimeID) then
|
|
exit(true);
|
|
dec(i);
|
|
end;
|
|
Result:=false;
|
|
end;
|
|
|
|
var
|
|
TimeID: cardinal;
|
|
c: PClipboardEventData;
|
|
sanity: Integer = 0;
|
|
begin
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[RequestSelectionData] FormatID=',dbgs(FormatID));
|
|
{$ENDIF}
|
|
FillChar(Result,SizeOf(TGtkSelectionData),0);
|
|
if (ClipboardWidget=nil) or (FormatID=0)
|
|
or (ClipboardTypeAtoms[ClipboardType]=0) then exit;
|
|
|
|
TimeID:= gdk_event_get_time(gtk_get_current_event);
|
|
// IMPORTANT: To retrieve data from xterm or kde applications
|
|
// the time id must be 0 or event^.time
|
|
repeat
|
|
while TimeIDExists(TimeID) do begin
|
|
inc(TimeID);
|
|
if TimeID>1010 then exit;
|
|
end;
|
|
New(c);
|
|
FillChar(c^,SizeOf(TClipboardEventData),0);
|
|
c^.TimeID:=TimeID;
|
|
ClipboardSelectionData.Add(c);
|
|
try
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[RequestSelectionData] TimeID=',dbgs(TimeID),' Type=',GdkAtomToStr(ClipboardTypeAtoms[ClipboardType]),' FormatID=',GdkAtomToStr(FormatID), ' Sanity=', IntToStr(Sanity));
|
|
{$ENDIF}
|
|
if gtk_selection_convert(ClipboardWidget, ClipboardTypeAtoms[ClipboardType],
|
|
FormatID, TimeID)<>GdkFalse
|
|
then begin
|
|
if not WaitForClipboardAnswer(c) then exit;
|
|
Result:=c^.Data;
|
|
break;
|
|
end;
|
|
finally
|
|
ClipboardSelectionData.Remove(c);
|
|
Dispose(c);
|
|
end;
|
|
Inc(sanity);
|
|
sleep(100);
|
|
until false or (sanity > 10);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: FreeClipboardTargetEntries
|
|
Params: ClipboardType
|
|
Returns: -
|
|
|
|
frees the memory of a ClipboardTargetEntries list
|
|
------------------------------------------------------------------------------}
|
|
procedure FreeClipboardTargetEntries(ClipboardType: TClipboardType);
|
|
var i: integer;
|
|
begin
|
|
if ClipboardTargetEntries[ClipboardType]<>nil then begin
|
|
for i:=0 to ClipboardTargetEntryCnt[ClipboardType]-1 do
|
|
StrDispose(ClipboardTargetEntries[ClipboardType][i].Target);
|
|
FreeMem(ClipboardTargetEntries[ClipboardType]);
|
|
end;
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------
|
|
function GdkAtomToStr(const Atom: TGdkAtom): string;
|
|
|
|
Returns the associated string
|
|
------------------------------------------------------------------------------}
|
|
function GdkAtomToStr(const Atom: TGdkAtom): string;
|
|
var
|
|
p: Pgchar;
|
|
begin
|
|
p:=gdk_atom_name(Atom);
|
|
Result:=p;
|
|
if p<>nil then g_free(p);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function CreateFormContents(AForm: TCustomForm;
|
|
var FormWidget: Pointer): Pointer;
|
|
|
|
Creates the contents for the form (normally a hbox plus a client area.
|
|
The hbox is needed for the menu.) The FormWidget is the main widget, for which
|
|
the client area is associated. If FormWidget=nil then the hbox will be used
|
|
as main widget.
|
|
-------------------------------------------------------------------------------}
|
|
function CreateFormContents(AForm: TCustomForm;
|
|
var FormWidget: Pointer; AWidgetInfo: PWidgetInfo = nil): Pointer;
|
|
var
|
|
ScrolledWidget, ClientAreaWidget: PGtkWidget;
|
|
WindowStyle: PGtkStyle;
|
|
Adjustment: PGtkAdjustment;
|
|
begin
|
|
// Create the VBox. We need that to place controls outside
|
|
// the client area (like menu)
|
|
Result := gtk_vbox_new(False, 0);
|
|
|
|
if FormWidget = nil then
|
|
FormWidget := Result;
|
|
|
|
// Create the form client area (a scrolled window with a gtklayout
|
|
// with the style of a window)
|
|
ScrolledWidget := gtk_scrolled_window_new(nil, nil);
|
|
gtk_box_pack_end(Result, ScrolledWidget, True, True, 0);
|
|
gtk_widget_show(ScrolledWidget);
|
|
|
|
ClientAreaWidget := gtk_layout_new(nil, nil);
|
|
WindowStyle := GetStyle(lgsWindow);
|
|
gtk_widget_set_style(ClientAreaWidget, WindowStyle);
|
|
//debugln('CreateFormContents Style=',GetStyleDebugReport(WindowStyle));
|
|
gtk_container_add(PGtkContainer(ScrolledWidget), ClientAreaWidget);
|
|
|
|
gtk_object_set_data(FormWidget, odnScrollArea, ScrolledWidget);
|
|
|
|
gtk_widget_show(ClientAreaWidget);
|
|
SetFixedWidget(FormWidget, ClientAreaWidget);
|
|
SetMainWidget(FormWidget, ClientAreaWidget);
|
|
|
|
if ScrolledWidget <> nil then
|
|
begin
|
|
GTK_WIDGET_UNSET_FLAGS(PGtkScrolledWindow(ScrolledWidget)^.hscrollbar,
|
|
GTK_CAN_FOCUS);
|
|
GTK_WIDGET_UNSET_FLAGS(PGtkScrolledWindow(ScrolledWidget)^.vscrollbar,
|
|
GTK_CAN_FOCUS);
|
|
gtk_scrolled_window_set_policy(PGtkScrolledWindow(ScrolledWidget),
|
|
GTK_POLICY_NEVER,GTK_POLICY_NEVER);
|
|
|
|
Adjustment := gtk_scrolled_window_get_vadjustment(PGTKScrolledWindow(ScrolledWidget));
|
|
if Adjustment <> nil then
|
|
gtk_object_set_data(PGTKObject(Adjustment), odnScrollBar,
|
|
PGTKScrolledWindow(ScrolledWidget)^.vscrollbar);
|
|
|
|
Adjustment := gtk_scrolled_window_get_hadjustment(PGTKScrolledWindow(ScrolledWidget));
|
|
if Adjustment <> nil then
|
|
gtk_object_set_data(PGTKObject(Adjustment), odnScrollBar,
|
|
PGTKScrolledWindow(ScrolledWidget)^.hscrollbar);
|
|
{$ifdef gtk2}
|
|
if (AWidgetInfo <> nil) and
|
|
(gtk_major_version >= 2) and (gtk_minor_version > 8) then
|
|
begin
|
|
g_signal_connect(PGTKScrolledWindow(ScrolledWidget)^.hscrollbar, 'change-value',
|
|
TGCallback(@Gtk2RangeScrollCB), AWidgetInfo);
|
|
g_signal_connect(PGTKScrolledWindow(ScrolledWidget)^.vscrollbar, 'change-value',
|
|
TGCallback(@Gtk2RangeScrollCB), AWidgetInfo);
|
|
end;
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
function IndexOfStyle(aStyle: TLazGtkStyle): integer;
|
|
begin
|
|
Result:=IndexOfStyleWithName(LazGtkStyleNames[aStyle]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: IndexOfWithNameStyle
|
|
Params: WName
|
|
Returns: Index of Style
|
|
|
|
Returns the Index within the Styles property of WNAME
|
|
------------------------------------------------------------------------------}
|
|
function IndexOfStyleWithName(const WName : String): integer;
|
|
begin
|
|
if Styles<>nil then begin
|
|
for Result:=0 to Styles.Count-1 do
|
|
if CompareText(WName,Styles[Result])=0 then exit;
|
|
end;
|
|
Result:=-1;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: ReleaseStyle
|
|
Params: WName
|
|
Returns: nothing
|
|
|
|
Tries to release a Style corresponding to the Widget Name passed, aka 'button',
|
|
'default', checkbox', etc. This should only be called on theme change or on
|
|
application terminate.
|
|
------------------------------------------------------------------------------}
|
|
function NewStyleObject : PStyleObject;
|
|
begin
|
|
New(Result);
|
|
FillChar(Result^, SizeOf(TStyleObject), 0);
|
|
end;
|
|
|
|
procedure FreeStyleObject(var StyleObject : PStyleObject);
|
|
// internal function to dispose a styleobject
|
|
// it does *not* remove it from the style lists
|
|
begin
|
|
if StyleObject <> nil then
|
|
begin
|
|
if StyleObject^.Obj <> nil then
|
|
gtk_object_destroy(StyleObject^.Obj);
|
|
if StyleObject^.Widget <> nil then
|
|
begin
|
|
// first unref
|
|
gtk_widget_unref(StyleObject^.Widget);
|
|
// then destroy
|
|
gtk_widget_destroy(StyleObject^.Widget);
|
|
end;
|
|
if StyleObject^.Style <> nil then
|
|
if StyleObject^.Style^.{$IFDEF Gtk2}attach_count{$ELSE}Ref_Count{$ENDIF} > 0 then
|
|
gtk_style_unref(StyleObject^.Style);
|
|
Dispose(StyleObject);
|
|
StyleObject := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure ReleaseAllStyles;
|
|
var
|
|
StyleObject: PStyleObject;
|
|
lgs: TLazGtkStyle;
|
|
i: Integer;
|
|
begin
|
|
if Styles=nil then exit;
|
|
{$IFDEF Gtk2}
|
|
if DefaultPangoLayout<>nil then begin
|
|
g_object_unref(DefaultPangoLayout);
|
|
DefaultPangoLayout:=nil;
|
|
end;
|
|
{$ENDIF}
|
|
for i:=Styles.Count-1 downto 0 do begin
|
|
StyleObject:=PStyleObject(Styles.Objects[i]);
|
|
FreeStyleObject(StyleObject);
|
|
end;
|
|
Styles.Clear;
|
|
for lgs:=Low(TLazGtkStyle) to High(TLazGtkStyle) do
|
|
StandardStyles[lgs]:=nil;
|
|
end;
|
|
|
|
procedure ReleaseStyle(aStyle: TLazGtkStyle);
|
|
var
|
|
StyleObject: PStyleObject;
|
|
l: Integer;
|
|
begin
|
|
if Styles=nil then exit;
|
|
if aStyle in [lgsUserDefined] then
|
|
RaiseGDBException('');// user styles are defined by name
|
|
StyleObject:=StandardStyles[aStyle];
|
|
if StyleObject<>nil then begin
|
|
l:=IndexOfStyle(aStyle);
|
|
Styles.Delete(l);
|
|
StandardStyles[aStyle]:=nil;
|
|
FreeStyleObject(StyleObject);
|
|
end;
|
|
end;
|
|
|
|
procedure ReleaseStyleWithName(const WName : String);
|
|
var
|
|
l : Longint;
|
|
s : PStyleObject;
|
|
begin
|
|
if Styles=nil then exit;
|
|
l := IndexOfStyleWithName(WName);
|
|
If l >= 0 then begin
|
|
If Styles.Objects[l] <> nil then
|
|
Try
|
|
s := PStyleObject(Styles.Objects[l]);
|
|
FreeStyleObject(S);
|
|
Except
|
|
DebugLn('[ReleaseStyle] : Unable To Unreference Style');
|
|
end;
|
|
Styles.Delete(l);
|
|
end;
|
|
end;
|
|
|
|
function GetStyle(aStyle: TLazGtkStyle): PGTKStyle;
|
|
begin
|
|
if Styles = nil then Exit(nil);
|
|
if aStyle in [lgsUserDefined] then
|
|
RaiseGDBException(''); // user styles are defined by name
|
|
if StandardStyles[aStyle] <> nil then // already created
|
|
Result := StandardStyles[aStyle]^.Style
|
|
else // create it
|
|
Result := GetStyleWithName(LazGtkStyleNames[aStyle]);
|
|
end;
|
|
|
|
procedure tooltip_window_style_set(Widget: PGtkWidget; PreviousStyle: PGtkStyle;
|
|
StyleObject: PStyleObject); cdecl;
|
|
begin
|
|
StyleObject^.Style := gtk_widget_get_style(Widget);
|
|
UpdateSysColorMap(Widget, lgsToolTip);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: GetStyleWithName
|
|
Params: none
|
|
Returns: Returns a Corresponding Style
|
|
|
|
Tries to get the Style corresponding to the Widget Name passed, aka 'button',
|
|
'default', checkbox', etc. for use within such routines as DrawFrameControl
|
|
to attempt to supply theme dependent drawing. Styles are stored in a TStrings
|
|
list which is only updated on theme change, to ensure fast efficient retrieval
|
|
of Styles.
|
|
------------------------------------------------------------------------------}
|
|
function GetStyleWithName(const WName: String) : PGTKStyle;
|
|
var
|
|
StyleObject : PStyleObject;
|
|
|
|
function CreateStyleNotebook: PGTKWidget;
|
|
var
|
|
NoteBookWidget: PGtkNotebook;
|
|
//NoteBookPageWidget: PGtkWidget;
|
|
NoteBookPageClientAreaWidget: PGtkWidget;
|
|
NoteBookTabLabel: PGtkWidget;
|
|
NoteBookTabMenuLabel: PGtkWidget;
|
|
begin
|
|
Result:=gtk_notebook_new;
|
|
NoteBookWidget := PGtkNoteBook(Result);
|
|
//NoteBookPageWidget := gtk_hbox_new(false, 0);
|
|
NoteBookPageClientAreaWidget := CreateFixedClientWidget{$IFNDEF GtkFixedWithWindow}(false){$ENDIF};
|
|
gtk_widget_show(NoteBookPageClientAreaWidget);
|
|
//gtk_container_add(GTK_CONTAINER(NoteBookPageWidget),
|
|
// NoteBookPageClientAreaWidget);
|
|
//gtk_widget_show(NoteBookPageWidget);
|
|
NoteBookTabLabel:=gtk_label_new('Lazarus');
|
|
gtk_widget_show(NoteBookTabLabel);
|
|
NoteBookTabMenuLabel:=gtk_label_new('Lazarus');
|
|
gtk_widget_show(NoteBookTabMenuLabel);
|
|
gtk_notebook_append_page_menu(NoteBookWidget,NoteBookPageClientAreaWidget,
|
|
NoteBookTabLabel,NoteBookTabMenuLabel);
|
|
gtk_widget_set_usize(Result,400,400);
|
|
end;
|
|
|
|
procedure ResizeWidget(CurWidget: PGTKWidget; NewWidth, NewHeight: integer);
|
|
{$IFDEF Gtk1}
|
|
begin
|
|
gtk_widget_set_usize(StyleObject^.Widget,NewWidth,NewHeight);
|
|
end;
|
|
{$ELSE}
|
|
var
|
|
allocation: TGtkAllocation;
|
|
begin
|
|
allocation.x:=0;
|
|
allocation.y:=0;
|
|
allocation.width:=NewWidth;
|
|
allocation.height:=NewHeight;
|
|
//gtk_widget_set_usize(StyleObject^.Widget,NewWidth,NewHeight);
|
|
gtk_widget_size_allocate(CurWidget,@allocation);
|
|
StyleObject^.FrameBordersValid:=false;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
var
|
|
Tp : Pointer;
|
|
l : Longint;
|
|
NoName: PGChar;
|
|
lgs: TLazGtkStyle;
|
|
WidgetName: String;
|
|
//VBox: PGtkWidget;
|
|
AddToStyleWindow: Boolean;
|
|
StyleWindowWidget: PGtkWidget;
|
|
Requisition: TGtkRequisition;
|
|
WindowFixedWidget: PGtkWidget;
|
|
VBox: PGtkWidget;
|
|
begin
|
|
Result := nil;
|
|
if Styles=nil then exit;
|
|
{$IFDEF NoStyle}
|
|
exit;
|
|
{$ENDIF}
|
|
|
|
if (WName='') then exit;
|
|
l:=IndexOfStyleWithName(WName);
|
|
//DebugLn(['GetStyleWithName START ',WName,' ',l]);
|
|
|
|
if l >= 0 then
|
|
begin
|
|
StyleObject:=PStyleObject(Styles.Objects[l]);
|
|
Result := StyleObject^.Style;
|
|
end else
|
|
begin
|
|
// create a new style object
|
|
StyleObject := NewStyleObject;
|
|
lgs := lgsUserDefined;
|
|
Tp := nil;
|
|
AddToStyleWindow := True;
|
|
WidgetName := 'LazStyle' + WName;
|
|
// create a style widget
|
|
If CompareText(WName,LazGtkStyleNames[lgsButton])=0 then begin
|
|
StyleObject^.Widget := GTK_BUTTON_NEW;
|
|
lgs:=lgsButton;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsLabel])=0 then begin
|
|
StyleObject^.Widget := GTK_LABEL_NEW('StyleLabel');
|
|
lgs:=lgsLabel;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsDefault])=0 then begin
|
|
lgs:=lgsDefault;
|
|
AddToStyleWindow:=false;
|
|
NoName:=nil;
|
|
StyleObject^.Widget :=
|
|
// GTK2 does not allow to instantiate the abstract base Widget
|
|
// so we use the "invisible" widget, which should never be defined
|
|
// by the theme
|
|
GTK_WIDGET_NEW(
|
|
{$IFDEF Gtk2}GTK_TYPE_INVISIBLE{$ELSE}GTK_WIDGET_TYPE{$ENDIF},
|
|
NoName,[]);
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsWindow])=0 then begin
|
|
lgs:=lgsWindow;
|
|
StyleObject^.Widget := GTK_WINDOW_NEW(GTK_WINDOW_TOPLEVEL);
|
|
AddToStyleWindow:=false;
|
|
gtk_widget_hide(StyleObject^.Widget);
|
|
// create the fixed widget
|
|
// (where to put all style widgets, that need a parent for realize)
|
|
VBox:=gtk_vbox_new(false,0);// vbox is needed for menu above and fixed widget below
|
|
gtk_widget_show(VBox);
|
|
gtk_container_add(PGtkContainer(StyleObject^.Widget), VBox);
|
|
gtk_object_set_data(PGtkObject(StyleObject^.Widget),'vbox',VBox);
|
|
WindowFixedWidget:=CreateFixedClientWidget;
|
|
gtk_widget_show(WindowFixedWidget);
|
|
gtk_container_add(PGtkContainer(VBox), WindowFixedWidget);
|
|
gtk_object_set_data(PGtkObject(StyleObject^.Widget),'fixedwidget',WindowFixedWidget);
|
|
gtk_widget_realize(StyleObject^.Widget);
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsCheckbox])=0 then begin
|
|
lgs:=lgsCheckbox;
|
|
StyleObject^.Widget := GTK_CHECK_BUTTON_NEW;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsRadiobutton])=0 then begin
|
|
lgs:=lgsRadiobutton;
|
|
StyleObject^.Widget := GTK_RADIO_BUTTON_NEW(nil);
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsMenu])=0 then begin
|
|
lgs:=lgsMenu;
|
|
{$IFDEF Gtk1}
|
|
AddToStyleWindow:=false;
|
|
{$ENDIF}
|
|
StyleObject^.Widget := gtk_menu_new;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsMenuBar])=0 then begin
|
|
lgs:=lgsMenuBar;
|
|
{$IFDEF Gtk1}
|
|
AddToStyleWindow:=false;
|
|
{$ENDIF}
|
|
StyleObject^.Widget := gtk_menu_bar_new;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsMenuitem])=0 then begin
|
|
lgs:=lgsMenuitem;
|
|
{$IFDEF Gtk1}
|
|
AddToStyleWindow:=false;
|
|
StyleObject^.Widget := gtk_menu_item_new;
|
|
{$ELSE}
|
|
// image menu item is needed to correctly return theme options
|
|
StyleObject^.Widget := gtk_image_menu_item_new;
|
|
{$ENDIF}
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsStatusBar])=0 then begin
|
|
lgs:=lgsStatusBar;
|
|
AddToStyleWindow:=true;
|
|
StyleObject^.Widget := gtk_statusbar_new;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsCalendar])=0 then begin
|
|
lgs:=lgsCalendar;
|
|
AddToStyleWindow:=true;
|
|
StyleObject^.Widget := gtk_calendar_new;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsList])=0 then begin
|
|
lgs:=lgsList;
|
|
StyleObject^.Widget := gtk_list_new;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsVerticalScrollbar])=0 then begin
|
|
lgs:=lgsVerticalScrollbar;
|
|
StyleObject^.Widget := gtk_vscrollbar_new(nil);
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsHorizontalScrollbar])=0 then begin
|
|
lgs:=lgsHorizontalScrollbar;
|
|
StyleObject^.Widget := gtk_hscrollbar_new(nil);
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsVerticalPaned])=0 then begin
|
|
lgs:=lgsVerticalPaned;
|
|
StyleObject^.Widget := gtk_vpaned_new;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsHorizontalPaned])=0 then begin
|
|
lgs:=lgsHorizontalPaned;
|
|
StyleObject^.Widget := gtk_hpaned_new;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsNotebook])=0 then begin
|
|
lgs:=lgsNotebook;
|
|
StyleObject^.Widget := CreateStyleNotebook;
|
|
end
|
|
else
|
|
if CompareText(WName,LazGtkStyleNames[lgsTooltip])=0 then
|
|
begin
|
|
lgs := lgsTooltip;
|
|
Tp := gtk_tooltips_new;
|
|
gtk_tooltips_force_window(Tp);
|
|
StyleObject^.Widget := PGTKTooltips(Tp)^.Tip_Window;
|
|
gtk_widget_ref(StyleObject^.Widget);// MG: why is this needed?
|
|
{$IFNDEF GTK1}
|
|
g_signal_connect(StyleObject^.Widget, 'style-set',
|
|
TGCallback(@tooltip_window_style_set), StyleObject);
|
|
{$ENDIF}
|
|
WidgetName := 'gtk-tooltip-lcl';
|
|
StyleObject^.Obj := Tp;
|
|
Tp := nil;
|
|
{$IFDEF GTK1}
|
|
AddToStyleWindow := False;
|
|
{$ENDIF}
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsHScale])=0 then begin
|
|
lgs:=lgsHScale;
|
|
TP := PGtkWidget( gtk_adjustment_new (0, 0, 100, 1, 10, 0));
|
|
StyleObject^.Widget := gtk_hscale_new (PGTKADJUSTMENT (TP));
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsVScale])=0 then begin
|
|
lgs:=lgsVScale;
|
|
TP := PGtkWidget( gtk_adjustment_new (0, 0, 100, 1, 10, 0));
|
|
StyleObject^.Widget := gtk_vscale_new (PGTKADJUSTMENT (TP));
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsGroupBox])=0 then begin
|
|
lgs:=lgsGroupBox;
|
|
StyleObject^.Widget := gtk_frame_new('GroupBox');
|
|
WindowFixedWidget:=CreateFixedClientWidget{$IFNDEF GtkFixedWithWindow}(false){$ENDIF};
|
|
gtk_widget_show(WindowFixedWidget);
|
|
gtk_container_add(PGtkContainer(StyleObject^.Widget), WindowFixedWidget);
|
|
gtk_object_set_data(PGtkObject(StyleObject^.Widget),'fixedwidget',WindowFixedWidget);
|
|
end
|
|
{$ifdef gtk2}
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsTreeView])=0 then begin
|
|
lgs:=lgsTreeView;
|
|
StyleObject^.Widget := gtk_tree_view_new;
|
|
gtk_tree_view_append_column(PGtkTreeView(StyleObject^.Widget), gtk_tree_view_column_new);
|
|
end
|
|
{$endif}
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsToolBar])=0 then begin
|
|
lgs:=lgsToolBar;
|
|
StyleObject^.Widget := gtk_toolbar_new;
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsToolButton])=0 then begin
|
|
lgs:=lgsToolButton;
|
|
StyleObject^.Widget := gtk_toolbar_append_item(PGtkToolBar(GetStyleWidget(lgsToolBar)), 'B', nil, nil, nil, nil, nil);
|
|
end
|
|
else
|
|
if CompareText(WName,LazGtkStyleNames[lgsScrolledWindow])=0 then begin
|
|
lgs:=lgsScrolledWindow;
|
|
StyleObject^.Widget := gtk_scrolled_window_new(nil, nil);
|
|
end
|
|
else
|
|
If CompareText(WName,LazGtkStyleNames[lgsGTK_Default])=0 then begin
|
|
lgs:=lgsGTK_Default;
|
|
AddToStyleWindow:=false;
|
|
StyleObject^.Widget := nil;
|
|
StyleObject^.Style := gtk_style_new;
|
|
end
|
|
else begin
|
|
// unknown style name -> bug
|
|
FreeStyleObject(StyleObject);
|
|
AddToStyleWindow:=false;
|
|
RaiseGDBException('');
|
|
end;
|
|
|
|
if (lgs<>lgsUserDefined) and (StandardStyles[lgs]<>nil) then begin
|
|
// consistency error
|
|
RaiseGDBException('');
|
|
end;
|
|
|
|
// ensure style of the widget
|
|
If (StyleObject^.Widget <> nil) then begin
|
|
gtk_widget_ref(StyleObject^.Widget);
|
|
|
|
// put style widget on style window, so that it can be realized
|
|
if AddToStyleWindow then
|
|
begin
|
|
gtk_widget_show_all(StyleObject^.Widget);
|
|
if GtkWidgetIsA(StyleObject^.Widget,GTK_TYPE_MENU) then
|
|
begin
|
|
// attach menu to window
|
|
gtk_menu_attach_to_widget(PGtkMenu(StyleObject^.Widget),
|
|
GetStyleWidget(lgsWindow), nil);
|
|
end
|
|
else
|
|
if GtkWidgetIsA(StyleObject^.Widget,GTK_TYPE_MENU_BAR) then
|
|
begin
|
|
StyleWindowWidget:=GetStyleWidget(lgsWindow);
|
|
// add menu above the forms client area (fixed widget)
|
|
VBox:=PGTKWidget(
|
|
gtk_object_get_data(PGtkObject(StyleWindowWidget),'vbox'));
|
|
gtk_box_pack_start(PGTKBox(VBox), StyleObject^.Widget, False, False, 0);
|
|
end
|
|
else
|
|
if GtkWidgetIsA(StyleObject^.Widget,GTK_TYPE_MENU_ITEM) then
|
|
begin
|
|
gtk_menu_bar_append({$IFDEF Gtk1}
|
|
PGtkMenuBar(GetStyleWidget(lgsMenuBar)),
|
|
{$ELSE}
|
|
GetStyleWidget(lgsMenuBar),
|
|
{$ENDIF}
|
|
StyleObject^.Widget);
|
|
end
|
|
else
|
|
{$ifdef gtk2}
|
|
if GtkWidgetIsA(StyleObject^.Widget, GTK_TYPE_TOOL_BUTTON) then
|
|
begin
|
|
//gtk_toolbar_insert();
|
|
gtk_toolbar_append_widget(GTK_TOOLBAR(GetStyleWidget(lgsToolBar)),
|
|
StyleObject^.Widget, nil, nil);
|
|
end
|
|
else
|
|
{$endif}
|
|
if (lgs = lgsToolButton) or
|
|
(lgs = lgsTooltip) then
|
|
begin
|
|
// already on a parent => nothing to do
|
|
end
|
|
else
|
|
begin
|
|
StyleWindowWidget:=GetStyleWidget(lgsWindow);
|
|
// add widget on client area of form
|
|
WindowFixedWidget:=PGTKWidget(
|
|
gtk_object_get_data(PGtkObject(StyleWindowWidget),'fixedwidget'));
|
|
//DebugLn('GetStyleWithName adding on hidden stylewindow ',WName,' ',GetWidgetDebugReport(StyleObject^.Widget));
|
|
if WindowFixedWidget <> nil then
|
|
gtk_fixed_put(PGtkFixed(WindowFixedWidget),StyleObject^.Widget,10,10);
|
|
end;
|
|
end;
|
|
|
|
gtk_widget_set_name(StyleObject^.Widget,PChar(WidgetName));
|
|
gtk_widget_ensure_style(StyleObject^.Widget);
|
|
|
|
// request default sizing
|
|
FillChar(Requisition,SizeOf(Requisition),0);
|
|
gtk_widget_size_request(StyleObject^.Widget, @Requisition);
|
|
|
|
StyleObject^.Style:=gtk_widget_get_style(StyleObject^.Widget);
|
|
// ToDo: find out, why sometimes the style is not initialized.
|
|
// for example: why the following occurs:
|
|
if CompareText(WName,'button')=0 then begin
|
|
if StyleObject^.Style^.light_gc[GTK_STATE_NORMAL]=nil then begin
|
|
//DebugLn('GetStyleWithName ',WName);
|
|
end;
|
|
end;
|
|
if AddToStyleWindow then begin
|
|
if not GtkWidgetIsA(StyleObject^.Widget,GTK_WINDOW_GET_TYPE) then begin
|
|
//DebugLn(['GetStyleWithName realizing ...']);
|
|
gtk_widget_realize(StyleObject^.Widget);
|
|
//DebugLn('AddToStyleWindow realized: ',WName,' ',GetWidgetDebugReport(StyleObject^.Widget));
|
|
end;
|
|
ResizeWidget(StyleObject^.Widget,200,200);
|
|
end;
|
|
end;
|
|
|
|
// increase refcount of style
|
|
if StyleObject^.Style <> nil then
|
|
if CompareText(WName,LazGtkStyleNames[lgsGTK_Default])<>0 then
|
|
StyleObject^.Style := GTK_Style_Ref(StyleObject^.Style);
|
|
|
|
// if successful add to style objects list
|
|
if StyleObject^.Style <> nil then
|
|
begin
|
|
Styles.AddObject(WName, TObject(StyleObject));
|
|
if lgs <> lgsUserDefined then
|
|
StandardStyles[lgs] := StyleObject;
|
|
Result := StyleObject^.Style;
|
|
UpdateSysColorMap(StyleObject^.Widget, lgs);
|
|
|
|
// ToDo: create all gc of the style
|
|
//gtk_widget_set_rc_style(StyleObject^.Widget);
|
|
|
|
if lgs = lgsTooltip then
|
|
gtk_widget_hide_all(StyleObject^.Widget);
|
|
end
|
|
else begin
|
|
// no success, clean up
|
|
FreeStyleObject(StyleObject);
|
|
DebugLn('WARNING: GetStyleWithName ',WName,' failed');
|
|
end;
|
|
|
|
// clean up
|
|
if Tp <> nil then
|
|
gtk_object_destroy(Tp);
|
|
end;
|
|
end;
|
|
|
|
function GetStyleWidget(aStyle: TLazGtkStyle) : PGTKWidget;
|
|
begin
|
|
if aStyle in [lgsUserDefined] then
|
|
RaiseGDBException('');// user styles are defined by name
|
|
if StandardStyles[aStyle]<>nil then
|
|
// already created
|
|
Result:=StandardStyles[aStyle]^.Widget
|
|
else
|
|
// create it
|
|
Result:=GetStyleWidgetWithName(LazGtkStyleNames[aStyle]);
|
|
end;
|
|
|
|
function GetStyleWidgetWithName(const WName : String) : PGTKWidget;
|
|
var
|
|
l : Longint;
|
|
begin
|
|
Result := nil;
|
|
// init style
|
|
GetStyleWithName(WName);
|
|
// return widget
|
|
l:=IndexOfStyleWithName(WName);
|
|
if l>=0 then
|
|
Result := PStyleObject(Styles.Objects[l])^.Widget;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: LoadDefaultFont(Desc)
|
|
Params: none
|
|
Returns: Returns the default Font
|
|
|
|
For Text/Font Routines: if the Font is invalid, this can be used instead, or
|
|
if the DT_internal flag is used(aka use system font) this is used. This is
|
|
also the font returned by GetStockObject(SYSTEM_FONT).
|
|
|
|
It attempts to get the font from the default Style, or if none is available,
|
|
a new style(aka try and get GTK builtin values), if that fails tries to get
|
|
a generic fixed font, if THAT fails, it gets whatever font is available.
|
|
If the result is not nil it MUST be GDK_FONT_UNREF'd when done.
|
|
------------------------------------------------------------------------------}
|
|
function LoadDefaultFont: TGtkIntfFont;
|
|
{$IFDEF Gtk1}
|
|
var
|
|
Style : PGTKStyle;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF Gtk2}
|
|
Result:=gtk_widget_create_pango_layout(GetStyleWidget(lgsdefault), nil);
|
|
{$ELSE Gtk1}
|
|
Result := nil;
|
|
Style := GetStyle(lgsDefault);
|
|
if Style = nil then
|
|
Style := GetStyle(lgsGTK_Default);
|
|
if Style <> nil then begin
|
|
Result := Style^.Font;
|
|
if Result = nil then
|
|
{$IFNDEF NoStyle}
|
|
if (Style^.RC_Style <> nil) then begin
|
|
if (Style^.RC_Style^.font_name <> nil) then
|
|
Result := gdk_font_load(Style^.RC_Style^.font_name);
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
If Result = nil then
|
|
Result := gdk_fontset_load('-*-fixed-*-*-*-*-*-120-*-*-*-*-*-*');
|
|
if Result = nil then
|
|
Result := gdk_fontset_load('-*-*-*-*-*-*-*-*-*-*-*-*-*-*');
|
|
{$ENDIF}
|
|
|
|
If Result <> nil then
|
|
ReferenceGtkIntfFont(Result);
|
|
end;
|
|
|
|
{$Ifdef GTK2}
|
|
function LoadDefaultFontDesc: PPangoFontDescription;
|
|
var
|
|
Style : PGTKStyle;
|
|
begin
|
|
Result := nil;
|
|
|
|
{$IFDEF VerboseGtkToDos}{$WARNING ToDo LoadDefaultFontDesc: get a working default pango font description}{$ENDIF}
|
|
Result := pango_font_description_from_string('sans 12');
|
|
|
|
exit;
|
|
|
|
Style := GetStyle(lgsLabel);
|
|
if Style = nil then
|
|
Style := GetStyle(lgsDefault);
|
|
if Style = nil then
|
|
Style := GetStyle(lgsGTK_Default);
|
|
|
|
If (Style <> nil) then begin
|
|
Result := pango_font_description_copy(Style^.font_desc);
|
|
end;
|
|
|
|
If Result = nil then
|
|
Result := pango_font_description_from_string('sans 12');
|
|
|
|
if Result = nil then
|
|
Result := pango_font_description_from_string('12');
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function GetDefaultFontName: string;
|
|
var
|
|
Style: PGtkStyle;
|
|
{$IFDEF GTK2}
|
|
PangoFontDesc: PPangoFontDescription;
|
|
{$ELSE}
|
|
p,t: pchar;
|
|
AFont: PGdkFont;
|
|
{$ENDIF}
|
|
begin
|
|
Result:='';
|
|
Style := GetStyle(lgsDefault);
|
|
if Style = nil then
|
|
Style := GetStyle(lgsGTK_Default);
|
|
|
|
If Style <> nil then begin
|
|
{$IFDEF GTK1}
|
|
{$IFNDEF NoStyle}
|
|
if (Style^.RC_Style <> nil) then
|
|
with style^.RC_Style^ do begin
|
|
if (font_name <> nil) then
|
|
Result := font_name;
|
|
if (Result='') and (fontset_name<>nil) then
|
|
begin
|
|
// fontset_name it's usually a comma separated list of font names
|
|
// try to get the first valid font.
|
|
p := fontset_name;
|
|
while p<>nil do begin
|
|
t := strscan(p, ',');
|
|
if t=nil then
|
|
result := p
|
|
else begin
|
|
result := copy(p, 1, t-p);
|
|
while (t<>nil) and (t^ in [',',' ',#9,#10,#13]) do
|
|
inc(t);
|
|
end;
|
|
AFont := gdk_font_load(pchar(result));
|
|
if AFont<>nil then begin
|
|
gdk_font_unref(AFont);
|
|
{$IFDEF VerboseFonts}
|
|
debugln('DefaultFont found in fontset: ',result);
|
|
{$ENDIF}
|
|
break;
|
|
end;
|
|
p := t;
|
|
end;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
{$IFDEF GTK2}
|
|
If (Style <> nil) then begin
|
|
PangoFontDesc := Style^.font_desc;
|
|
if PangoFontDesc<>nil then begin
|
|
Result:=pango_font_description_get_family(PangoFontDesc);
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
{$IFDEF VerboseFonts}
|
|
DebugLn('GetDefaultFontName: DefaultFont=',result);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure RealizeGDKColor(ColorMap: PGdkColormap; Color: PGDKColor);
|
|
var
|
|
AllocResult: gboolean;
|
|
begin
|
|
if ColorMap=nil then ColorMap:=gdk_colormap_get_system;
|
|
if (Color^.pixel = 0)
|
|
and ((Color^.red<>0) or (Color^.blue<>0) or (Color^.green<>0)) then
|
|
gdk_colormap_alloc_colors(ColorMap, Color, 1, false, true, @AllocResult)
|
|
else
|
|
gdk_colormap_query_color(ColorMap, Color^.pixel, Color);
|
|
end;
|
|
|
|
procedure RealizeGtkStyleColor(Style: PGTKStyle; Color: PGDKColor);
|
|
begin
|
|
if (Style<>nil) then
|
|
RealizeGDKColor(Style^.ColorMap,Color)
|
|
else
|
|
RealizeGDKColor(nil,Color);
|
|
end;
|
|
|
|
function GetSysGCValues(Color: TColorRef;
|
|
ThemeWidget: PGtkWidget): TGDKGCValues;
|
|
// ThemeWidget can be nil
|
|
|
|
function GetWidgetWithBackgroundWindow(Widget: PGtkWidget): PGtkWidget;
|
|
// returns the gtk widget which has the background gdk window
|
|
var
|
|
WindowOwnerWidget: PGtkWidget;
|
|
begin
|
|
Result:=Widget;
|
|
if Result=nil then exit;
|
|
if Result^.window=nil then exit;
|
|
gdk_window_get_user_data(Result^.window,PGPointer(@WindowOwnerWidget));
|
|
Result:=WindowOwnerWidget;
|
|
if Result=nil then exit;
|
|
end;
|
|
|
|
var
|
|
Style: PGTKStyle;
|
|
GC: PGDKGC;
|
|
Pixmap: PGDKPixmap;
|
|
SysColor: TColorRef;
|
|
BaseColor: TColorRef;
|
|
Red, Green, Blue: byte;
|
|
begin
|
|
// Set defaults in case something goes wrong
|
|
FillChar(Result, SizeOf(Result), 0);
|
|
Style := nil;
|
|
GC := nil;
|
|
Pixmap := nil;
|
|
|
|
SysColor := ColorToRGB(Color);
|
|
Result.Fill := GDK_Solid;
|
|
RedGreenBlue(TColor(SysColor), Red, Green, Blue);
|
|
Result.foreground.Red:=gushort(Red) shl 8 + Red;
|
|
Result.foreground.Green:=gushort(Green) shl 8 + Green;
|
|
Result.foreground.Blue:=gushort(Blue) shl 8 + Blue;
|
|
|
|
{$IfDef Disable_GC_SysColors}
|
|
exit;
|
|
{$EndIf}
|
|
BaseColor := Color and $FF;
|
|
case BaseColor of
|
|
{These are WM/X defined, but might be possible to get
|
|
|
|
COLOR_CAPTIONTEXT
|
|
COLOR_INACTIVECAPTIONTEXT}
|
|
|
|
{These Are incompatible or WM defined
|
|
|
|
COLOR_ACTIVECAPTION
|
|
COLOR_INACTIVECAPTION
|
|
COLOR_GRADIENTACTIVECAPTION
|
|
COLOR_GRADIENTINACTIVECAPTION
|
|
COLOR_WINDOWFRAME
|
|
COLOR_ACTIVEBORDER
|
|
COLOR_INACTIVEBORDER}
|
|
|
|
COLOR_BACKGROUND:
|
|
begin
|
|
Style := GetStyle(lgsDefault);
|
|
if Style = nil then
|
|
Style := GetStyle(lgsWindow);
|
|
if Style = nil then
|
|
exit;
|
|
Pixmap := Style^.bg_pixmap[GTK_STATE_NORMAL];
|
|
if Pixmap <> nil then
|
|
begin
|
|
Result.Fill := GDK_Tiled;
|
|
Result.Tile := Pixmap;
|
|
end
|
|
else
|
|
begin
|
|
GC := Style^.bg_gc[GTK_STATE_NORMAL];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.bg[GTK_STATE_PRELIGHT];
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
end;
|
|
|
|
COLOR_INFOBK :
|
|
begin
|
|
Style := GetStyle(lgsTooltip);
|
|
if Style = nil then
|
|
Style := GetStyle(lgsWindow);
|
|
if Style = nil then
|
|
exit;
|
|
|
|
Pixmap := Style^.bg_pixmap[GTK_STATE_NORMAL];
|
|
if Pixmap <> nil then
|
|
begin
|
|
Result.Fill := GDK_Tiled;
|
|
Result.Tile := Pixmap;
|
|
end
|
|
else
|
|
begin
|
|
GC := Style^.bg_gc[GTK_STATE_NORMAL];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
{$IFDEF Gtk1}
|
|
Result.foreground := Style^.bg[GTK_STATE_PRELIGHT];
|
|
{$ELSE}
|
|
Result.foreground := Style^.bg[GTK_STATE_NORMAL];
|
|
{$ENDIF}
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
end;
|
|
|
|
COLOR_INFOTEXT :
|
|
begin
|
|
Style := GetStyle(lgsTooltip);
|
|
|
|
if Style = nil then
|
|
Style := GetStyle(lgsWindow);
|
|
|
|
if Style = nil then
|
|
exit;
|
|
|
|
GC := Style^.fg_gc[GTK_STATE_NORMAL];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.fg[GTK_STATE_NORMAL];
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
|
|
COLOR_FORM,
|
|
COLOR_MENU,
|
|
COLOR_SCROLLBAR,
|
|
COLOR_BTNFACE :
|
|
begin
|
|
case BaseColor of
|
|
COLOR_FORM: Style := GetStyle(lgsWindow);
|
|
COLOR_BTNFACE: Style := GetStyle(lgsButton);
|
|
COLOR_MENU: Style := GetStyle(lgsMenu);
|
|
COLOR_SCROLLBAR: Style := GetStyle(lgsHorizontalScrollbar);
|
|
end;
|
|
if Style = nil then
|
|
exit;
|
|
Pixmap := Style^.bg_pixmap[GTK_STATE_NORMAL];
|
|
if Pixmap <> nil then
|
|
begin
|
|
Result.Fill := GDK_Tiled;
|
|
Result.Tile := Pixmap;
|
|
end else
|
|
begin
|
|
GC := Style^.bg_gc[GTK_STATE_NORMAL];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.bg[GTK_STATE_NORMAL];
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
end;
|
|
|
|
COLOR_3DDKSHADOW,
|
|
COLOR_BTNSHADOW :
|
|
begin
|
|
Style := GetStyle(lgsButton);
|
|
if Style = nil then
|
|
exit;
|
|
GC := Style^.dark_gc[GTK_STATE_NORMAL];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.dark[GTK_STATE_NORMAL];
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
|
|
COLOR_GRAYTEXT :
|
|
begin
|
|
Style := GetStyle(lgsDefault);
|
|
if Style = nil then
|
|
exit;
|
|
GC := Style^.text_gc[GTK_STATE_INSENSITIVE];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.text[GTK_STATE_NORMAL];
|
|
end else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
|
|
COLOR_MENUTEXT,
|
|
COLOR_BTNTEXT :
|
|
begin
|
|
case BaseColor of
|
|
COLOR_BTNTEXT : Style := GetStyle(lgsButton);
|
|
COLOR_MENUTEXT : Style := GetStyle(lgsMenuitem);
|
|
end;
|
|
if Style = nil then
|
|
exit;
|
|
GC := Style^.fg_gc[GTK_STATE_NORMAL];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.fg[GTK_STATE_NORMAL];
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
|
|
COLOR_WINDOWTEXT:
|
|
begin
|
|
Style := GetStyle(lgsDefault);
|
|
if Style = nil then
|
|
exit;
|
|
GC := Style^.text_gc[GTK_STATE_NORMAL];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.text[GTK_STATE_NORMAL];
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
|
|
COLOR_3DLIGHT,
|
|
COLOR_BTNHIGHLIGHT :
|
|
begin
|
|
Style := GetStyle(lgsButton);
|
|
if Style = nil then
|
|
exit;
|
|
GC := Style^.light_gc[GTK_STATE_NORMAL];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.light[GTK_STATE_NORMAL];
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
|
|
COLOR_WINDOW :
|
|
begin
|
|
Style := GetStyle(lgsList);
|
|
if Style = nil then
|
|
exit;
|
|
GC := Style^.base_gc[GTK_STATE_NORMAL];
|
|
if (GC = nil) then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
if Style^.base[GTK_STATE_NORMAL].Pixel<>0 then
|
|
begin
|
|
Result.foreground := Style^.base[GTK_STATE_NORMAL];
|
|
Result.background := Style^.base[GTK_STATE_NORMAL];
|
|
end;
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
|
|
COLOR_HIGHLIGHT :
|
|
begin
|
|
Style := GetStyle(lgsDefault);
|
|
if Style = nil then
|
|
exit;
|
|
GC := Style^.bg_gc[GTK_STATE_SELECTED];
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.bg[GTK_STATE_SELECTED];
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
|
|
COLOR_HIGHLIGHTTEXT :
|
|
begin
|
|
Style := GetStyle(lgsDefault);
|
|
if Style = nil then
|
|
exit;
|
|
{$IFDEF Gtk1}
|
|
GC := Style^.bg_gc[GTK_STATE_PRELIGHT];
|
|
{$ELSE}
|
|
GC := Style^.text_gc[GTK_STATE_SELECTED];
|
|
{$ENDIF}
|
|
if GC = nil then
|
|
begin
|
|
Result.Fill := GDK_Solid;
|
|
Result.foreground := Style^.bg[GTK_STATE_PRELIGHT];
|
|
end
|
|
else
|
|
GDK_GC_Get_Values(GC, @Result);
|
|
end;
|
|
|
|
{?????????????
|
|
COLOR_HOTLIGHT :
|
|
begin
|
|
end;
|
|
?????????????}
|
|
|
|
{?????????????????
|
|
COLOR_APPWORKSPACE :
|
|
begin
|
|
end;
|
|
?????????????????}
|
|
end;
|
|
|
|
RealizeGtkStyleColor(Style, @Result.foreground);
|
|
end;
|
|
|
|
function StyleForegroundColor(Color: TColorRef;
|
|
DefaultColor: PGDKColor): PGDKColor;
|
|
var
|
|
style : PGTKStyle;
|
|
begin
|
|
style := nil;
|
|
Result := DefaultColor;
|
|
|
|
Case TColor(Color) of
|
|
clINFOTEXT :
|
|
begin
|
|
Style := GetStyle(lgsTooltip);
|
|
|
|
If Style = nil then
|
|
exit;
|
|
|
|
Result := @Style^.fg[GTK_STATE_NORMAL];
|
|
end;
|
|
|
|
cl3DDKSHADOW,
|
|
clBTNSHADOW :
|
|
begin
|
|
Style := GetStyle(lgsButton);
|
|
If Style = nil then
|
|
exit;
|
|
Result := @Style^.dark[GTK_STATE_NORMAL];
|
|
end;
|
|
|
|
clGRAYTEXT :
|
|
begin
|
|
Style := GetStyle(lgsDefault);
|
|
If Style = nil then
|
|
exit;
|
|
Result := @Style^.text[GTK_STATE_INSENSITIVE];
|
|
end;
|
|
|
|
clMENUTEXT,
|
|
clBTNTEXT :
|
|
begin
|
|
Case TColor(Color) of
|
|
clBTNTEXT : Style := GetStyle(lgsButton);
|
|
clMENUTEXT : Style := GetStyle(lgsMenuitem);
|
|
end;
|
|
If Style = nil then
|
|
exit;
|
|
Result := @Style^.fg[GTK_STATE_NORMAL];
|
|
end;
|
|
|
|
clWINDOWTEXT:
|
|
begin
|
|
Style := GetStyle(lgsDefault);
|
|
If Style = nil then
|
|
exit;
|
|
Result := @Style^.text[GTK_STATE_NORMAL];
|
|
end;
|
|
|
|
cl3DLIGHT,
|
|
clBTNHIGHLIGHT :
|
|
begin
|
|
Style := GetStyle(lgsButton);
|
|
If Style = nil then
|
|
exit;
|
|
Result := @Style^.light[GTK_STATE_NORMAL];
|
|
end;
|
|
|
|
clHIGHLIGHTTEXT :
|
|
begin
|
|
DebugLn(['StyleForegroundColor clHIGHLIGHTTEXT']);
|
|
Style := GetStyle(lgsDefault);
|
|
If Style = nil then
|
|
exit;
|
|
Result := @Style^.text[GTK_STATE_PRELIGHT];
|
|
DebugLn(['StyleForegroundColor clHIGHLIGHTTEXT 2 ',Result<>nil]);
|
|
end;
|
|
end;
|
|
|
|
If Result = nil then
|
|
Result := DefaultColor;
|
|
|
|
if (Result <> nil) and (Result <> DefaultColor) then
|
|
RealizeGtkStyleColor(Style,Result);
|
|
end;
|
|
|
|
function GetStyleGroupboxFrameBorders: TRect;
|
|
const s = 200;
|
|
var
|
|
StyleObject: PStyleObject;
|
|
allocation: TGtkAllocation;
|
|
FrameWidget: PGtkFrame;
|
|
f: TRect;
|
|
begin
|
|
GetStyleWidget(lgsGroupBox);
|
|
StyleObject:=StandardStyles[lgsGroupBox];
|
|
if not StyleObject^.FrameBordersValid then begin
|
|
allocation.x:=0;
|
|
allocation.y:=0;
|
|
allocation.width:=s;
|
|
allocation.height:=s;
|
|
gtk_widget_size_allocate(StyleObject^.Widget,@allocation);
|
|
FrameWidget:=pGtkFrame(StyleObject^.Widget);
|
|
{$IFDEF Gtk1}
|
|
allocation:=FrameWidget^.bin.child^.allocation;
|
|
{$ELSE}
|
|
GTK_FRAME_GET_CLASS(FrameWidget)^.compute_child_allocation(
|
|
FrameWidget,@allocation);
|
|
{$ENDIF}
|
|
//DebugLn(['GetStyleGroupboxFrame BBB2 ',dbgs(allocation)]);
|
|
f.Left:=Min(s,Max(0,allocation.x));
|
|
f.Top:=Min(s,Max(0,allocation.y));
|
|
f.Right:=Max(0,Min(s-f.Left,s-allocation.x-allocation.width));
|
|
f.Bottom:=Max(0,Min(s-f.Top,s-allocation.x-allocation.width));
|
|
StyleObject^.FrameBorders:=f;
|
|
//DebugLn(['GetStyleGroupboxFrame FrameBorders=',dbgs(StyleObject^.FrameBorders)]);
|
|
StyleObject^.FrameBordersValid:=true;
|
|
end;
|
|
Result:=StyleObject^.FrameBorders;
|
|
end;
|
|
|
|
function GetStyleNotebookFrameBorders: TRect;
|
|
const s = 400;
|
|
var
|
|
StyleObject: PStyleObject;
|
|
allocation: TGtkAllocation;
|
|
f: TRect;
|
|
PageWidget: PGtkWidget;
|
|
begin
|
|
GetStyleWidget(lgsNotebook);
|
|
StyleObject:=StandardStyles[lgsNotebook];
|
|
if not StyleObject^.FrameBordersValid then begin
|
|
allocation.x:=0;
|
|
allocation.y:=0;
|
|
allocation.width:=s;
|
|
allocation.height:=s;
|
|
gtk_widget_size_allocate(StyleObject^.Widget,@allocation);
|
|
PageWidget:=gtk_notebook_get_nth_page(PGtkNoteBook(StyleObject^.Widget),0);
|
|
//DebugLn(['GetStyleNotebookFrameBorders BBB2 ',dbgs(allocation)]);
|
|
allocation:=PageWidget^.allocation;
|
|
f.Left:=Min(s,Max(0,allocation.x));
|
|
f.Top:=Min(s,Max(0,allocation.y));
|
|
f.Right:=Max(0,Min(s-f.Left,s-allocation.x-allocation.width));
|
|
f.Bottom:=Max(0,Min(s-f.Top,s-allocation.x-allocation.width));
|
|
StyleObject^.FrameBorders:=f;
|
|
//DebugLn(['GetStyleNotebookFrameBorders FrameBorders=',dbgs(StyleObject^.FrameBorders)]);
|
|
StyleObject^.FrameBordersValid:=true;
|
|
end;
|
|
Result:=StyleObject^.FrameBorders;
|
|
end;
|
|
|
|
{$IFDEF Gtk2}
|
|
function GetStyleFormFrameBorders(WithMenu: boolean): TRect;
|
|
const s = 400;
|
|
var
|
|
StyleObject: PStyleObject;
|
|
allocation: TGtkAllocation;
|
|
f: TRect;
|
|
InnerWidget: PGtkWidget;
|
|
Outer: TGdkRectangle;
|
|
Inner: TGdkRectangle;
|
|
begin
|
|
GetStyleWidget(lgsMenu);
|
|
StyleObject:=StandardStyles[lgsWindow];
|
|
if not StyleObject^.FrameBordersValid then begin
|
|
allocation.x:=0;
|
|
allocation.y:=0;
|
|
allocation.width:=s;
|
|
allocation.height:=s;
|
|
gtk_widget_size_allocate(StyleObject^.Widget,@allocation);
|
|
InnerWidget:=PGTKWidget(
|
|
gtk_object_get_data(PGtkObject(StyleObject^.Widget),'fixedwidget'));
|
|
allocation:=InnerWidget^.allocation;
|
|
//DebugLn(['GetStyleFormFrameBorders BBB2 ',dbgs(allocation),' WithMenu=',WithMenu,' ClientWidget=',GetWidgetDebugReport(InnerWidget)]);
|
|
f.Left:=Min(s,Max(0,allocation.x));
|
|
f.Top:=Min(s,Max(0,allocation.y));
|
|
f.Right:=Max(0,Min(s-f.Left,s-allocation.x-allocation.width));
|
|
f.Bottom:=Max(0,Min(s-f.Top,s-allocation.x-allocation.width));
|
|
StyleObject^.FrameBorders:=f;
|
|
//DebugLn(['GetStyleFormFrameBorders FrameBorders=',dbgs(StyleObject^.FrameBorders)]);
|
|
StyleObject^.FrameBordersValid:=true;
|
|
end;
|
|
|
|
if WithMenu then begin
|
|
InnerWidget:=PGTKWidget(
|
|
gtk_object_get_data(PGtkObject(StyleObject^.Widget),'vbox'));
|
|
end else begin
|
|
InnerWidget:=PGTKWidget(
|
|
gtk_object_get_data(PGtkObject(StyleObject^.Widget),'fixedwidget'));
|
|
end;
|
|
Outer:=StyleObject^.Widget^.allocation;
|
|
Inner:=InnerWidget^.allocation;
|
|
Result.Left:=Min(Outer.width,Max(0,Inner.x));
|
|
Result.Top:=Min(Outer.height,Max(0,Inner.y));
|
|
Result.Right:=Max(0,Min(Outer.width-f.Left,Outer.width-Inner.x-Inner.width));
|
|
Result.Bottom:=Max(0,Min(Outer.height-f.Top,Outer.height-Inner.x-Inner.width));
|
|
//DebugLn(['GetStyleFormFrameBorders BBB3 Inner=',dbgs(Inner),' Outer=',dbgs(Outer),' WithMenu=',WithMenu,' InnerWidget=',GetWidgetDebugReport(InnerWidget),' Result=',dbgs(Result)]);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure StyleFillRectangle(drawable : PGDKDrawable; GC : PGDKGC;
|
|
Color : TColorRef; x, y, width, height : gint);
|
|
var
|
|
style: PGTKStyle;
|
|
widget: PGTKWidget;
|
|
state: TGTKStateType;
|
|
shadow: TGtkShadowType;
|
|
detail: pgchar;
|
|
begin
|
|
style := nil;
|
|
shadow := GTK_SHADOW_NONE;
|
|
state := GTK_STATE_NORMAL;
|
|
|
|
case TColor(Color) of
|
|
{ clMenu:
|
|
begin
|
|
Style := GetStyle('menuitem');
|
|
widget := GetStyleWidget('menuitem');
|
|
detail := 'menuitem';
|
|
end;
|
|
|
|
clBtnFace :
|
|
begin
|
|
Style := GetStyle('button');
|
|
widget := GetStyleWidget('button');
|
|
detail := 'button';
|
|
end;
|
|
|
|
clWindow :
|
|
begin
|
|
Style := GetStyle('default');
|
|
widget := GetStyleWidget('default');
|
|
detail := 'list';
|
|
end; }
|
|
|
|
clBackground:
|
|
begin
|
|
Style := GetStyle(lgsWindow);
|
|
widget := GetStyleWidget(lgsWindow);
|
|
detail := 'window';
|
|
end;
|
|
|
|
clInfoBk :
|
|
begin
|
|
Style := GetStyle(lgsToolTip);
|
|
Widget := GetStyleWidget(lgsToolTip);
|
|
shadow := GTK_SHADOW_OUT;
|
|
detail := 'tooltip';
|
|
end;
|
|
|
|
clForm :
|
|
begin
|
|
Style := GetStyle(lgsWindow);
|
|
widget := GetStyleWidget(lgsWindow);
|
|
detail := 'window';
|
|
end;
|
|
end;
|
|
|
|
if Assigned(Style) then
|
|
gtk_paint_flat_box(style, drawable, state, shadow, nil, widget,
|
|
detail, x, y, width, height)
|
|
else
|
|
gdk_draw_rectangle(drawable, GC, 1, x, y, width, height);
|
|
end;
|
|
|
|
procedure UpdateWidgetStyleOfControl(AWinControl: TWinControl);
|
|
var
|
|
RCStyle : PGtkRCStyle;
|
|
Widget, FixWidget : PGTKWidget;
|
|
MainWidget: PGtkWidget;
|
|
FreeFontName: boolean;
|
|
FreeFontSetName: boolean;
|
|
|
|
procedure CreateRCStyle;
|
|
begin
|
|
if RCStyle=nil then
|
|
RCStyle:=gtk_rc_style_new;
|
|
end;
|
|
|
|
procedure SetRCFont(FontGdiObject: PGdiObject);
|
|
{$IFDEF GTK1}
|
|
var
|
|
FontDesc: TGtkFontCacheDescriptor;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF GTK1}
|
|
CreateRCStyle;
|
|
FontDesc:=FontCache.FindADescriptor(FontGdiObject^.GDIFontObject);
|
|
if (FontDesc<>nil) and (FontDesc.xlfd<>'') then begin
|
|
RCStyle:=gtk_rc_style_new;
|
|
g_free(RCStyle^.font_name);
|
|
RCStyle^.font_name:=g_strdup(PChar(FontDesc.xlfd));
|
|
g_free(RCStyle^.fontset_name);
|
|
RCStyle^.fontset_name:=g_strdup(PChar(FontDesc.xlfd));
|
|
FreeFontName:=true;
|
|
|
|
//DebugLn('UpdateWidgetStyleOfControl.SetRCFont ',DbgSName(AWinControl),' Widget=',GetWidgetDebugReport(Widget),' Style=',GetWidgetStyleReport(Widget));
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
begin
|
|
{$IFDEF NoStyle}
|
|
exit;
|
|
{$ENDIF}
|
|
|
|
if not AWinControl.HandleAllocated then exit;
|
|
|
|
MainWidget:=PGtkWidget(AWinControl.Handle);
|
|
FixWidget:=GetFixedWidget(MainWidget);
|
|
if (FixWidget <> nil) and (FixWidget <> MainWidget) then
|
|
Widget := FixWidget
|
|
else
|
|
Widget := MainWidget;
|
|
|
|
RCStyle:=nil;
|
|
FreeFontName:=false;
|
|
FreeFontSetName:=false;
|
|
try
|
|
// set default background
|
|
if (AWinControl.Color=clNone) then
|
|
begin
|
|
// clNone => remove default background
|
|
if (FixWidget<>nil) and (FixWidget^.Window<>nil) then
|
|
begin
|
|
gdk_window_set_back_pixmap(FixWidget^.Window, nil, GdkFalse);
|
|
end;
|
|
end
|
|
else
|
|
if not IsColorDefault(AWinControl) and ((AWinControl.Color and SYS_COLOR_BASE)=0) then
|
|
begin
|
|
// set background to user defined color
|
|
|
|
// don't set background for custom controls, which paint themselves
|
|
// (this prevents flickering)
|
|
if (csOpaque in AWinControl.ControlStyle)
|
|
and GtkWidgetIsA(MainWidget,GTKAPIWidget_Type) then exit;
|
|
|
|
{for i:=0 to 4 do begin
|
|
RCStyle^.bg[i]:=NewColor;
|
|
|
|
// Indicate which colors the GtkRcStyle will affect;
|
|
// unflagged colors will follow the theme
|
|
RCStyle^.color_flags[i]:=
|
|
RCStyle^.color_flags[i] or GTK_RC_BG;
|
|
end;}
|
|
|
|
//DebugLn('UpdateWidgetStyleOfControl ',DbgSName(AWinControl),' Color=',DbgS(AWinControl.Color));
|
|
end;
|
|
|
|
{if (AWinControl is TCustomForm) then begin
|
|
gdk_window_set_back_pixmap(FixWidget^.Window,nil,GdkFalse);
|
|
|
|
NewColor:=TColorToTGDKColor(clRed);
|
|
|
|
CreateRCStyle;
|
|
for i:=0 to 4 do begin
|
|
debugln('UpdateWidgetStyleOfControl i=',dbgs(i),' ',RCStyle^.bg_pixmap_name[i],' ',RCStyle^.Name);
|
|
RCStyle^.bg[i]:=NewColor;
|
|
|
|
// Indicate which colors the GtkRcStyle will affect;
|
|
// unflagged colors will follow the theme
|
|
RCStyle^.color_flags[i]:=
|
|
RCStyle^.color_flags[i] or GTK_RC_BG;
|
|
end;
|
|
end;}
|
|
|
|
// set font color
|
|
|
|
// set font (currently only TCustomLabel)
|
|
if (GtkWidgetIsA(Widget,gtk_label_get_type)
|
|
or GtkWidgetIsA(Widget,gtk_editable_get_type)
|
|
or GtkWidgetIsA(Widget,gtk_check_button_get_type))
|
|
and (not AWinControl.Font.IsDefault)
|
|
then begin
|
|
// allocate font (just read it)
|
|
if AWinControl.Font.Reference.Handle=0 then ;
|
|
end;
|
|
|
|
finally
|
|
if RCStyle<>nil then begin
|
|
//DebugLn('UpdateWidgetStyleOfControl Apply Modifications ',AWinControl.Name,' ',GetWidgetClassName(Widget));
|
|
gtk_widget_modify_style(Widget,RCStyle);
|
|
|
|
if FreeFontName then begin
|
|
{$ifdef gtk1}
|
|
g_free(RCStyle^.font_name);
|
|
RCStyle^.font_name:=nil;
|
|
{$else}
|
|
pango_font_description_free(RCStyle^.font_desc);
|
|
RCStyle^.font_desc:=nil;
|
|
{$endif}
|
|
end;
|
|
if FreeFontSetName then begin
|
|
{$ifdef gtk1}
|
|
g_free(RCStyle^.fontset_name);
|
|
RCStyle^.fontset_name:=nil;
|
|
{$endif}
|
|
end;
|
|
//DebugLn('UpdateWidgetStyleOfControl END ',DbgSName(AWinControl),' Widget=',GetWidgetDebugReport(Widget),' Style=',GetWidgetStyleReport(Widget));
|
|
gtk_rc_style_unref(RCStyle);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function DeleteAmpersands(var Str : String) : Longint;
|
|
// convert double ampersands to single & and delete single &
|
|
// return the position of the letter after the first deleted single ampersand
|
|
// in the new string
|
|
var
|
|
Tmp : String;
|
|
SrcPos, DestPos, SrcLen: integer;
|
|
begin
|
|
Result := -1;
|
|
|
|
// for speedup reasons check if Str must be changed
|
|
SrcLen:=length(Str);
|
|
SrcPos:=SrcLen;
|
|
while (SrcPos>=1) and (Str[SrcPos]<>'&') do dec(SrcPos);
|
|
if SrcPos<1 then exit;
|
|
|
|
// copy Str to Tmp and convert ampersands on the fly
|
|
SetLength(Tmp,SrcLen);
|
|
SrcPos:=1;
|
|
DestPos:=1;
|
|
while (SrcPos<=SrcLen) do begin
|
|
if Str[SrcPos]<>'&' then begin
|
|
// copy normal char
|
|
Tmp[DestPos]:=Str[SrcPos];
|
|
inc(SrcPos);
|
|
inc(DestPos);
|
|
end else begin
|
|
inc(SrcPos);
|
|
if (SrcPos<=SrcLen) and (Str[SrcPos]='&') then begin
|
|
// double ampersand
|
|
Tmp[DestPos]:='&';
|
|
inc(DestPos);
|
|
inc(SrcPos);
|
|
end else begin
|
|
// single ampersand
|
|
if Result<1 then Result:=DestPos;
|
|
end;
|
|
end;
|
|
end;
|
|
SetLength(Tmp,DestPos-1);
|
|
Str:=Tmp;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function Ampersands2Underscore(Src: PChar) : PChar;
|
|
|
|
Creates a new PChar. Deletes escaping ampersands, replaces the first single
|
|
ampersand with an underscore and deleting all other single ampersands.
|
|
-------------------------------------------------------------------------------}
|
|
function Ampersands2Underscore(Src: PChar) : PChar;
|
|
var
|
|
i, j: Longint;
|
|
ShortenChars, FirstAmpersand, NewLength, SrcLength: integer;
|
|
begin
|
|
// count ampersands and find first ampersand
|
|
ShortenChars:= 0; // chars to delete
|
|
FirstAmpersand:= -1;
|
|
SrcLength:= StrLen(Src);
|
|
|
|
{ Look for amperands. If found, check if it is an escaped ampersand.
|
|
If it is, don't count it in. }
|
|
i:=0;
|
|
while i<SrcLength do begin
|
|
if Src[i] = '&' then begin
|
|
if (i < SrcLength - 1) and (Src[i+1] = '&') then begin
|
|
// escaping ampersand found
|
|
inc(ShortenChars);
|
|
inc(i,2);
|
|
Continue;
|
|
end else begin
|
|
// single ampersand found
|
|
if (FirstAmpersand < 0) then
|
|
// the first will be replaced ...
|
|
FirstAmpersand:= i
|
|
else
|
|
// ... and all others will be deleted
|
|
inc(ShortenChars);
|
|
end;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
// create new PChar
|
|
NewLength:= SrcLength - ShortenChars;
|
|
|
|
Result:=StrAlloc(NewLength+1); // +1 for #0 char at end
|
|
|
|
// copy string without ampersands
|
|
i:=0;
|
|
j:=0;
|
|
while (j < NewLength) do begin
|
|
if Src[i] <> '&' then begin
|
|
// copy normal char
|
|
Result[j]:= Src[i];
|
|
end else begin
|
|
// ampersand
|
|
if (i < (SrcLength - 1)) and (Src[i+1] = '&') then begin
|
|
// escaping ampersand found
|
|
inc(i);
|
|
Result[j]:='&';
|
|
end else begin
|
|
// single ampersand found
|
|
if i = FirstAmpersand then begin
|
|
// replace first single ampersand with underscore
|
|
Result[j]:='_';
|
|
end else begin
|
|
// delete single ampersand
|
|
dec(j);
|
|
end;
|
|
end;
|
|
end;
|
|
Inc(i);
|
|
Inc(j);
|
|
end;
|
|
Result[NewLength]:=#0;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function Ampersands2Underscore(const ASource: String): String;
|
|
|
|
Deletes escaping ampersands, replaces the first single
|
|
ampersand with an underscore and deleting all other single ampersands.
|
|
-------------------------------------------------------------------------------}
|
|
function Ampersands2Underscore(const ASource: String): String;
|
|
var
|
|
n: Integer;
|
|
FirstFound: Boolean;
|
|
begin
|
|
//TODO: escape underscores
|
|
FirstFound := False;
|
|
Result := ASource;
|
|
n := 1;
|
|
while n <= Length(Result) do
|
|
begin
|
|
if Result[n] = '&'
|
|
then begin
|
|
if (n < Length(Result))
|
|
and (Result[n + 1] = '&')
|
|
then begin
|
|
// we got a &&, remove the first
|
|
Delete(Result, n, 1);
|
|
Inc(n);
|
|
Continue;
|
|
end;
|
|
if FirstFound
|
|
then begin
|
|
// simply remove it
|
|
Delete(Result, n, 1);
|
|
Continue;
|
|
end;
|
|
// if we are here it's our first
|
|
FirstFound := True;
|
|
Result[n] := '_';
|
|
end;
|
|
Inc(n);
|
|
end;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function RemoveAmpersands(Src: PChar; LineLength : Longint) : PChar;
|
|
|
|
Creates a new PChar removing all escaping ampersands.
|
|
-------------------------------------------------------------------------------}
|
|
function RemoveAmpersands(Src: PChar; LineLength : Longint) : PChar;
|
|
var
|
|
i, j: Longint;
|
|
ShortenChars, NewLength, SrcLength: integer;
|
|
begin
|
|
// count ampersands and find first ampersand
|
|
ShortenChars:= 0; // chars to delete
|
|
SrcLength:= LineLength;
|
|
|
|
{ Look for amperands. If found, check if it is an escaped ampersand.
|
|
If it is, don't count it in. }
|
|
i:=0;
|
|
while i<SrcLength do begin
|
|
if Src[i] = '&' then begin
|
|
if (i < SrcLength - 1) and (Src[i+1] = '&') then begin
|
|
// escaping ampersand found
|
|
inc(ShortenChars);
|
|
inc(i,2);
|
|
Continue;
|
|
end
|
|
else
|
|
inc(ShortenChars);
|
|
end;
|
|
inc(i);
|
|
end;
|
|
// create new PChar
|
|
NewLength:= SrcLength - ShortenChars;
|
|
|
|
Result:=StrAlloc(NewLength+1); // +1 for #0 char at end
|
|
|
|
// copy string without ampersands
|
|
i:=0;
|
|
j:=0;
|
|
while (j < NewLength) do begin
|
|
if Src[i] <> '&' then begin
|
|
// copy normal char
|
|
Result[j]:= Src[i];
|
|
end else begin
|
|
// ampersand
|
|
if (i < (SrcLength - 1)) and (Src[i+1] = '&') then begin
|
|
// escaping ampersand found
|
|
inc(i);
|
|
Result[j]:='&';
|
|
end else
|
|
// delete single ampersand
|
|
dec(j);
|
|
end;
|
|
Inc(i);
|
|
Inc(j);
|
|
end;
|
|
Result[NewLength]:=#0;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function RemoveAmpersands(const ASource: String): String;
|
|
|
|
Removing all escaping ampersands.
|
|
-------------------------------------------------------------------------------}
|
|
function RemoveAmpersands(const ASource: String): String;
|
|
var
|
|
n: Integer;
|
|
begin
|
|
Result := ASource;
|
|
n := 1;
|
|
while n <= Length(Result) do
|
|
begin
|
|
if Result[n] = '&'
|
|
then begin
|
|
if (n < Length(Result))
|
|
and (Result[n + 1] = '&')
|
|
then begin
|
|
// we got a &&, remove the first
|
|
Delete(Result, n, 1);
|
|
Inc(n);
|
|
Continue;
|
|
end;
|
|
// simply remove it
|
|
Delete(Result, n, 1);
|
|
Continue;
|
|
end;
|
|
Inc(n);
|
|
end;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure LabelFromAmpersands(var AText, APattern: String; var AAccelChar: Char)
|
|
|
|
Removes all escaping ampersands &&, creates an underscore pattern and returns
|
|
the first ampersand char as accelerator char
|
|
-------------------------------------------------------------------------------}
|
|
procedure LabelFromAmpersands(var AText, APattern: String; var AAccelChar: Char);
|
|
var
|
|
n: Integer;
|
|
FirstFound: Boolean;
|
|
begin
|
|
FirstFound := False;
|
|
APattern := StringOfChar(' ', Length(AText));
|
|
AAccelChar := #0;
|
|
n := 1;
|
|
while n <= Length(AText) do
|
|
begin
|
|
case AText[n] of
|
|
'&': begin
|
|
if (n < Length(AText))
|
|
and (AText[n + 1] = '&')
|
|
then begin
|
|
// we got a &&, remove the first
|
|
Delete(AText, n, 1);
|
|
Delete(APattern, n, 1);
|
|
Inc(n);
|
|
end else begin
|
|
Delete(AText, n, 1);
|
|
Delete(APattern, n, 1);
|
|
if FirstFound
|
|
then Continue; // simply remove it
|
|
|
|
// if we are here it's our first
|
|
FirstFound := True;
|
|
AAccelChar := System.lowerCase(AText[n]);
|
|
// is there a next char we can underline ?
|
|
if n <= Length(APattern)
|
|
then APattern[n] := '_';
|
|
end;
|
|
end;
|
|
'_': begin
|
|
AText[n] := ' ';
|
|
APattern[n] := '_';
|
|
end;
|
|
end;
|
|
Inc(n);
|
|
end;
|
|
end;
|
|
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function GetTextExtentIgnoringAmpersands(TheFont: PGDKFont;
|
|
Str : PChar; StrLength: integer;
|
|
MaxWidth: Longint; lbearing, rbearing, width, ascent, descent : Pgint);
|
|
|
|
Gets text extent of a string, ignoring escaped Ampersands.
|
|
That means, ampersands are not counted.
|
|
Negative MaxWidth means no limit.
|
|
-------------------------------------------------------------------------------}
|
|
procedure GetTextExtentIgnoringAmpersands(TheFont: TGtkIntfFont;
|
|
Str : PChar; StrLength: integer;
|
|
lbearing, rbearing, width, ascent, descent : Pgint);
|
|
var
|
|
NewStr : PChar;
|
|
i: integer;
|
|
begin
|
|
NewStr:=Str;
|
|
// first check if Str contains an ampersand:
|
|
if (Str<>nil) then begin
|
|
i:=0;
|
|
while (Str[i]<>'&') and (i<StrLength) do inc(i);
|
|
if i<StrLength then begin
|
|
NewStr := RemoveAmpersands(Str, StrLength);
|
|
StrLength:=StrLen(NewStr);
|
|
end;
|
|
end;
|
|
gdk_text_extents(TheFont, NewStr, StrLength,
|
|
lbearing, rBearing, width, ascent, descent);
|
|
if NewStr<>Str then
|
|
StrDispose(NewStr);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function FontIsDoubleByteCharsFont(TheFont: TGtkIntfFont): boolean;
|
|
|
|
This is only a heuristic
|
|
------------------------------------------------------------------------------}
|
|
function FontIsDoubleByteCharsFont(TheFont: TGtkIntfFont): boolean;
|
|
var
|
|
SingleCharLen, DoubleCharLen: integer;
|
|
begin
|
|
{$IFDEF Gtk1}
|
|
SingleCharLen:=gdk_text_width(TheFont, 'A', 1);
|
|
DoubleCharLen:=gdk_text_width(TheFont, #0'A', 2);
|
|
{$ELSE}
|
|
pango_layout_set_single_paragraph_mode(TheFont, TRUE);
|
|
pango_layout_set_width(TheFont, -1);
|
|
pango_layout_set_text(TheFont, 'A', 1);
|
|
pango_layout_get_pixel_size(TheFont, @SingleCharLen, nil);
|
|
pango_layout_set_text(TheFont, #0'A', 2);
|
|
pango_layout_get_pixel_size(TheFont, @DoubleCharLen, nil);
|
|
{$ENDIF}
|
|
Result:=(SingleCharLen=0) and (DoubleCharLen>0);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function FontIsMonoSpaceFont(TheFont: TGtkIntfFont): boolean;
|
|
|
|
This is only a heuristic
|
|
------------------------------------------------------------------------------}
|
|
function FontIsMonoSpaceFont(TheFont: TGtkIntfFont): boolean;
|
|
var
|
|
{$IFDEF Gtk1}
|
|
SingleCharLen: LongInt;
|
|
{$ENDIF}
|
|
MWidth: LongInt;
|
|
IWidth: LongInt;
|
|
begin
|
|
{$IFDEF Gtk1}
|
|
SingleCharLen:=gdk_text_width(TheFont, 'A', 1);
|
|
if SingleCharLen=0 then begin
|
|
// assume a double byte character font
|
|
MWidth:=gdk_text_width(TheFont, '#0m', 2);
|
|
IWidth:=gdk_text_width(TheFont, '#0i', 2);
|
|
end else begin
|
|
// assume a single byte character font
|
|
MWidth:=gdk_text_width(TheFont, 'm', 1);
|
|
IWidth:=gdk_text_width(TheFont, 'i', 1);
|
|
end;
|
|
{$ELSE}
|
|
pango_layout_set_single_paragraph_mode(TheFont, TRUE);
|
|
pango_layout_set_width(TheFont, -1);
|
|
pango_layout_set_text(TheFont, 'm', 1);
|
|
pango_layout_get_pixel_size(TheFont, @MWidth, nil);
|
|
pango_layout_set_text(TheFont, 'i', 1);
|
|
pango_layout_get_pixel_size(TheFont, @IWidth, nil);
|
|
{$ENDIF}
|
|
Result:=MWidth=IWidth;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: GDKPixel2GDIRGB
|
|
Params:
|
|
Pixel - a GDK Pixel, refers to Index in Colormap/Visual
|
|
Visual - a GDK Visual, if nil, the System Default is used
|
|
Colormap - a GDK Colormap, if nil, the System Default is used
|
|
Returns: TGDIRGB
|
|
|
|
A convenience function for use with GDK Image's. It takes a pixel value
|
|
retrieved from gdk_image_get_pixel, and uses the passed Visual and Colormap
|
|
to try and look up actual RGB values.
|
|
------------------------------------------------------------------------------}
|
|
function GDKPixel2GDIRGB(Pixel: Longint; Visual: PGDKVisual;
|
|
Colormap: PGDKColormap) : TGDIRGB;
|
|
var
|
|
Color: TGDKColor;
|
|
begin
|
|
FillChar(Result, SizeOf(TGDIRGB),0);
|
|
|
|
If (Visual = nil) or (Colormap = nil) then begin
|
|
Visual := GDK_Visual_Get_System;
|
|
Colormap := GDK_Colormap_Get_System;
|
|
end;
|
|
|
|
gdk_colormap_query_color(colormap, pixel, @color);
|
|
|
|
Result.Red := Color.Red shr 8;
|
|
Result.Green := Color.Green shr 8;
|
|
Result.Blue := Color.Blue shr 8;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function GetWindowDecorations(AForm : TCustomForm) : Longint;
|
|
|
|
------------------------------------------------------------------------------}
|
|
function GetWindowDecorations(AForm : TCustomForm) : Longint;
|
|
var
|
|
ABorderStyle: TFormBorderStyle;
|
|
begin
|
|
Result := 0;
|
|
|
|
if not (csDesigning in AForm.ComponentState) then
|
|
ABorderStyle:=AForm.BorderStyle
|
|
else
|
|
ABorderStyle:=bsSizeable;
|
|
|
|
{$IFDEF Gtk2}
|
|
|
|
case ABorderStyle of
|
|
bsNone: Result := 0;
|
|
|
|
bsSingle: Result := GDK_DECOR_TITLE or
|
|
GDK_DECOR_MENU or GDK_DECOR_MINIMIZE or
|
|
GDK_DECOR_MAXIMIZE;
|
|
|
|
bsSizeable: Result := GDK_DECOR_BORDER or GDK_DECOR_TITLE or
|
|
GDK_DECOR_MENU or GDK_DECOR_MINIMIZE or GDK_DECOR_MAXIMIZE
|
|
or GDK_DECOR_RESIZEH;
|
|
|
|
bsDialog: Result := GDK_DECOR_BORDER or GDK_DECOR_TITLE or
|
|
GDK_DECOR_MENU or GDK_DECOR_MINIMIZE;
|
|
|
|
bsToolWindow: Result := GDK_DECOR_TITLE or GDK_DECOR_MENU;
|
|
|
|
bsSizeToolWin: Result := GDK_DECOR_BORDER or GDK_DECOR_TITLE or
|
|
GDK_DECOR_MENU or GDK_DECOR_RESIZEH;
|
|
end;
|
|
|
|
if not (csDesigning in AForm.ComponentState) then
|
|
begin
|
|
if not (biMinimize in AForm.BorderIcons) then
|
|
Result := Result and not GDK_DECOR_MINIMIZE;
|
|
if not (biMaximize in AForm.BorderIcons) then
|
|
Result := Result and not GDK_DECOR_MAXIMIZE;
|
|
if not (biSystemMenu in AForm.BorderIcons) then
|
|
Result := Result and not GDK_DECOR_MENU;
|
|
end;
|
|
|
|
{$ELSE}
|
|
case ABorderStyle of
|
|
bsNone : Result := 0;
|
|
|
|
bsSingle : Result := GDK_DECOR_TITLE or
|
|
GDK_DECOR_MENU or GDK_DECOR_MINIMIZE or
|
|
GDK_DECOR_MAXIMIZE;
|
|
|
|
bsSizeable : Result := GDK_DECOR_BORDER or GDK_DECOR_TITLE or
|
|
GDK_DECOR_MENU or GDK_DECOR_MINIMIZE or GDK_DECOR_MAXIMIZE
|
|
or GDK_DECOR_RESIZEH;
|
|
|
|
bsDialog : Result := GDK_DECOR_BORDER or GDK_DECOR_TITLE or
|
|
GDK_DECOR_MENU or GDK_DECOR_MINIMIZE;
|
|
|
|
bsToolWindow : Result := GDK_DECOR_TITLE or GDK_DECOR_MENU;
|
|
|
|
bsSizeToolWin :Result := GDK_DECOR_BORDER or GDK_DECOR_TITLE or
|
|
GDK_DECOR_MENU or GDK_DECOR_RESIZEH;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
//DebugLn('GetWindowDecorations ',DbgSName(AForm),' ',dbgs(ord(ABorderStyle)),' ',binStr(Result,8));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function GetWindowFunction(AForm : TCustomForm) : Longint;
|
|
|
|
------------------------------------------------------------------------------}
|
|
function GetWindowFunction(AForm : TCustomForm) : Longint;
|
|
var
|
|
ABorderStyle: TFormBorderStyle;
|
|
begin
|
|
Result:=0;
|
|
if not (csDesigning in AForm.ComponentState) then
|
|
ABorderStyle:=AForm.BorderStyle
|
|
else
|
|
ABorderStyle:=bsSizeable;
|
|
|
|
{$IFDEF Gtk2}
|
|
case ABorderStyle of
|
|
bsNone : Result := GDK_FUNC_RESIZE or GDK_FUNC_CLOSE {$ifndef windows}or GDK_FUNC_MOVE{$endif};
|
|
|
|
bsSingle : Result := GDK_FUNC_MOVE or GDK_FUNC_MINIMIZE or GDK_FUNC_CLOSE;
|
|
|
|
bsSizeable : Result := GDK_FUNC_RESIZE or GDK_FUNC_MOVE or
|
|
GDK_FUNC_MINIMIZE or GDK_FUNC_CLOSE or GDK_FUNC_MAXIMIZE;
|
|
|
|
bsDialog : Result := GDK_FUNC_CLOSE or GDK_FUNC_MINIMIZE
|
|
or GDK_FUNC_MOVE;
|
|
|
|
bsToolWindow : Result := GDK_FUNC_MOVE or GDK_FUNC_CLOSE;
|
|
|
|
bsSizeToolWin : Result := GDK_FUNC_RESIZE or GDK_FUNC_MOVE or GDK_FUNC_CLOSE;
|
|
end;
|
|
|
|
// X warns if marking a fixed size window resizeable:
|
|
if ((AForm.Constraints.MinWidth>0)
|
|
and (AForm.Constraints.MinWidth=AForm.Constraints.MaxWidth))
|
|
or ((AForm.Constraints.MinHeight>0)
|
|
and (AForm.Constraints.MinHeight=AForm.Constraints.MaxHeight)) then
|
|
Result:=Result-GDK_FUNC_RESIZE;
|
|
|
|
if (not (csDesigning in AForm.ComponentState)) then
|
|
begin
|
|
if not (biMinimize in AForm.BorderIcons) then
|
|
Result:=Result and not GDK_FUNC_MINIMIZE;
|
|
if not (biMaximize in AForm.BorderIcons) then
|
|
Result:=Result and not GDK_FUNC_MAXIMIZE;
|
|
end;
|
|
{$ELSE}
|
|
case ABorderStyle of
|
|
bsNone : Result := GDK_FUNC_RESIZE or GDK_FUNC_CLOSE;
|
|
|
|
bsSingle : Result := GDK_FUNC_MOVE or GDK_FUNC_MINIMIZE or GDK_FUNC_CLOSE;
|
|
|
|
bsSizeable : Result := GDK_FUNC_RESIZE or GDK_FUNC_MOVE or
|
|
GDK_FUNC_MINIMIZE or GDK_FUNC_CLOSE or GDK_FUNC_MAXIMIZE;
|
|
|
|
bsDialog : Result := GDK_FUNC_CLOSE or GDK_FUNC_MINIMIZE
|
|
or GDK_FUNC_MOVE;
|
|
|
|
bsToolWindow : Result := GDK_FUNC_MOVE or GDK_FUNC_CLOSE;
|
|
|
|
bsSizeToolWin : Result := GDK_FUNC_RESIZE or GDK_FUNC_MOVE or GDK_FUNC_CLOSE;
|
|
end;
|
|
|
|
// X warns if marking a fixed size window resizeable:
|
|
if ((AForm.Constraints.MinWidth>0)
|
|
and (AForm.Constraints.MinWidth=AForm.Constraints.MaxWidth))
|
|
or ((AForm.Constraints.MinHeight>0)
|
|
and (AForm.Constraints.MinHeight=AForm.Constraints.MaxHeight)) then
|
|
Result:=Result-GDK_FUNC_RESIZE;
|
|
{$ENDIF}
|
|
|
|
//DebugLn('GetWindowFunction ',DbgSName(AForm),' ',dbgs(ord(ABorderStyle)),' ',binStr(Result,8));
|
|
end;
|
|
|
|
procedure FillScreenFonts(ScreenFonts : TStrings);
|
|
var
|
|
{$ifdef gtk1}
|
|
theFonts : PPChar;
|
|
{$else}
|
|
Widget : PGTKWidget;
|
|
Context : PPangoContext;
|
|
families : PPPangoFontFamily;
|
|
{$endif}
|
|
Tmp: AnsiString;
|
|
I, N: Integer;
|
|
begin
|
|
ScreenFonts.Clear;
|
|
{$ifdef gtk1}
|
|
theFonts := XListFonts(gdk_display,PChar('-*-*-*-*-*-*-*-*-*-*-*-*-*-*'), 10000, @N);
|
|
debugln('FillScreenFonts N=',dbgs(N));
|
|
for I := 0 to N - 1 do
|
|
if theFonts[I] <> nil then begin
|
|
Tmp := ExtractFamilyFromXLFDName(theFonts[I]);
|
|
if Tmp <> '' then
|
|
if ScreenFonts.IndexOf(Tmp) < 0 then
|
|
ScreenFonts.Append(Tmp);
|
|
end;
|
|
XFreeFontNames(theFonts);
|
|
{$else}
|
|
Widget := GetStyleWidget(lgsDefault);
|
|
if Widget = nil then begin
|
|
exit;//raise an error here I guess
|
|
end;
|
|
Context := gtk_widget_get_pango_context(Widget);
|
|
if Context = nil then begin
|
|
exit;//raise an error here I guess
|
|
end;
|
|
families := nil;
|
|
pango_context_list_families(Context, @families, @n);
|
|
|
|
for I := 0 to N - 1 do
|
|
if families[I] <> nil then begin
|
|
Tmp := StrPas(pango_font_family_get_name(families[I]));
|
|
if Tmp <> '' then
|
|
if ScreenFonts.IndexOf(Tmp) < 0 then
|
|
ScreenFonts.Append(Tmp);
|
|
end;
|
|
if (families <> nil) then
|
|
g_free(families);
|
|
{$endif gtk2}
|
|
end;
|
|
|
|
function GetTextHeight(DCTextMetric: TDevContextTextMetric): integer;
|
|
// IMPORTANT: Before this call: UpdateDCTextMetric(TGtkDeviceContext(DC));
|
|
begin
|
|
{$IfDef Win32}
|
|
Result := DCTextMetric.TextMetric.tmHeight div 2;
|
|
{$Else}
|
|
Result := DCTextMetric.TextMetric.tmAscent;
|
|
{$EndIf}
|
|
end;
|
|
|
|
{$IFDEF GTK1}
|
|
{ Compile with UseXinerama defined to use the Xinerama extension to avoid dialog
|
|
boxes straddling two monitors. This is only required for GTK1, as it is built
|
|
into GTK2. The Xinerama library is not always available, so the libraries will
|
|
be dynamically loaded. (A single monitor is assumed if the load fails.) On
|
|
some systems only a static Xinerama library is available, so define
|
|
StaticXinerama also. MAC OSX is in this latter category, but it crashed the
|
|
X server when I tried it on a real two monitor display.
|
|
}
|
|
{$IFDEF UseXinerama}
|
|
{$IFDEF StaticXinerama}
|
|
{$LINKLIB Xinerama}
|
|
{$ENDIF}
|
|
var
|
|
FirstScreenCalled: Boolean = False;
|
|
FirstScreenResult: Boolean = False;
|
|
{ Copy record definition from Xinerama unit.
|
|
Can't use the unit itself, as it forces the executable to
|
|
refer to the libraray }
|
|
type
|
|
TXineramaScreenInfo = record
|
|
screen_number : cint;
|
|
x_org : cshort;
|
|
y_org : cshort;
|
|
width : cshort;
|
|
height : cshort;
|
|
end;
|
|
PXineramaScreenInfo = ^TXineramaScreenInfo;
|
|
|
|
function GetFirstScreen: Boolean;
|
|
var
|
|
nMonitors: cint;
|
|
XineramaScreenInfo: PXineramaScreenInfo;
|
|
opcode, firstevent, firsterror: cint;
|
|
XineramaLib: TLibHandle;
|
|
pXineramaIsActive: function (dpy: PDisplay):TBool;cdecl;
|
|
pXineramaQueryScreens: function (dpy: PDisplay;
|
|
number: Pcint): PXineramaScreenInfo;cdecl;
|
|
begin
|
|
if not FirstScreenCalled then begin
|
|
if XQueryExtension(gdk_display, 'XINERAMA', @opcode, @firstevent,
|
|
@firsterror)
|
|
then begin
|
|
XineramaLib := {$IFDEF StaticXinerama} 1 {Flag present} {$ELSE} LoadLibrary('libXinerama.so') {$ENDIF};
|
|
if XineramaLib <> 0 then begin
|
|
{$IFDEF StaticXinerama}
|
|
Pointer(pXineramaIsActive) := @XineramaIsActive;
|
|
Pointer(pXineramaQueryScreens) := @XineramaQueryScreens;
|
|
{$ELSE}
|
|
Pointer(pXineramaIsActive) :=
|
|
GetProcAddress(XineramaLib, 'XineramaIsActive');
|
|
Pointer(pXineramaQueryScreens) :=
|
|
GetProcAddress(XineramaLib, 'XineramaQueryScreens');
|
|
{$ENDIF}
|
|
if (pXineramaIsActive <> nil) and (pXineramaQueryScreens <> nil) and
|
|
pXineramaIsActive(gdk_display)
|
|
then begin
|
|
XineramaScreenInfo := pXineramaQueryScreens(gdk_display, @nMonitors);
|
|
if XineramaScreenInfo <> nil then begin
|
|
if (nMonitors > 0) and (nMonitors < 10) then begin
|
|
FirstScreen.x := XineramaScreenInfo^.width;
|
|
FirstScreen.y := XineramaScreenInfo^.height;
|
|
FirstScreenResult := True;
|
|
end;
|
|
XFree(XineramaScreenInfo);
|
|
end;
|
|
end;
|
|
// Do not FreeLibrary(XineramaLib) because it causes the X11 library to
|
|
// crash on exit
|
|
end;
|
|
end;
|
|
FirstScreenCalled := True;
|
|
end;
|
|
Result := FirstScreenResult;
|
|
end;
|
|
{$ENDIF UseXinerama}
|
|
{$ENDIF Gtk1}
|
|
|
|
{$IFDEF HasX}
|
|
function XGetWorkarea(var ax,ay,awidth,aheight:gint): gint;
|
|
|
|
var
|
|
XDisplay: PDisplay;
|
|
XScreen: PScreen;
|
|
XWindow: TWindow;
|
|
AtomType: x.TAtom;
|
|
Format: gint;
|
|
nitems: gulong;
|
|
bytes_after: gulong;
|
|
current_desktop: pguint;
|
|
res : Integer;
|
|
begin
|
|
Result := -1;
|
|
xdisplay := gdk_display;
|
|
xscreen := XDefaultScreenOfDisplay(xdisplay);
|
|
xwindow := XRootWindowOfScreen(xscreen);
|
|
res:=XGetWindowProperty (xdisplay, xwindow,
|
|
XInternAtom(xdisplay, '_NET_WORKAREA', false),
|
|
0, MaxInt, False, XA_CARDINAL, @atomtype, @format, @nitems,
|
|
@bytes_after, gpointer(@current_desktop));
|
|
if (atomtype = XA_CARDINAL) and (format = 32) and (nitems > 0) then begin
|
|
result:=res;
|
|
ax:=current_desktop[0];
|
|
ay:=current_desktop[1];
|
|
awidth:=current_desktop[2];
|
|
aheight:=current_desktop[3];
|
|
end;
|
|
if current_desktop <> nil then
|
|
XFree (current_desktop);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function FindFocusWidget(AWidget: PGtkWidget): PGtkWidget;
|
|
var
|
|
WinWidgetInfo: PWinWidgetInfo;
|
|
ImplWidget: PGtkWidget;
|
|
GList: PGlist;
|
|
LastFocusWidget: PGtkWidget;
|
|
begin
|
|
// Default to the widget, try to find other
|
|
Result := AWidget;
|
|
|
|
// Combo
|
|
if GtkWidgetIsA(AWidget, gtk_combo_get_type)
|
|
then begin
|
|
// handle is a gtk combo
|
|
{$IfDef VerboseFocus}
|
|
DebugLn(' D taking gtkcombo entry');
|
|
{$EndIf}
|
|
Result := PgtkWidget(PGtkCombo(AWidget)^.entry);
|
|
Exit;
|
|
end;
|
|
|
|
// check if widget has a WinWidgetInfo record
|
|
WinWidgetInfo := GetWidgetInfo(AWidget, false);
|
|
if WinWidgetInfo = nil then Exit;
|
|
|
|
ImplWidget:= WinWidgetInfo^.CoreWidget;
|
|
if ImplWidget = nil then Exit;
|
|
// set default to the implementation widget
|
|
Result := ImplWidget;
|
|
|
|
// handle has an ImplementationWidget
|
|
if GtkWidgetIsA(ImplWidget, gtk_list_get_type)
|
|
then begin
|
|
{$IfDef VerboseFocus}
|
|
DebugLn(' E using list');
|
|
{$EndIf}
|
|
// Try the last added selected
|
|
if not (selection_mode(PGtkList(ImplWidget)^) in [GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE])
|
|
and (PGtkList(ImplWidget)^.last_focus_child <> nil)
|
|
then begin
|
|
LastFocusWidget:=PGtkList(ImplWidget)^.last_focus_child;
|
|
if g_list_find(PGtkList(ImplWidget)^.selection,LastFocusWidget)<>nil
|
|
then begin
|
|
Result := PGtkList(ImplWidget)^.last_focus_child;
|
|
{$IfDef VerboseFocus}
|
|
DebugLn(' E.1 using last_focus_child');
|
|
{$EndIf}
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
// If there is a selection, try the first
|
|
GList := PGtkList(ImplWidget)^.selection;
|
|
if (GList <> nil) and (GList^.data <> nil)
|
|
then begin
|
|
Result := GList^.data;
|
|
{$IfDef VerboseFocus}
|
|
DebugLn(' E.2 using 1st selection');
|
|
{$EndIf}
|
|
Exit;
|
|
end;
|
|
|
|
// If not in browse mode, set focus to the first child
|
|
// in browsemode, the focused item cannot be selected by mouse
|
|
// if selection_mode(PGtkList(ImplWidget)^) = GTK_SELECTION_BROWSE
|
|
// then begin
|
|
// {$IfDef VerboseFocus}
|
|
// DebugLn(' E.3 Browse mode -> using ImplWidget');
|
|
// {$EndIf}
|
|
// Exit;
|
|
// end;
|
|
|
|
GList := PGtkList(ImplWidget)^.children;
|
|
if GList = nil then Exit;
|
|
if GList^.Data = nil then Exit;
|
|
Result := GList^.Data;
|
|
{$IfDef VerboseFocus}
|
|
DebugLn(' E.4 using 1st child');
|
|
{$EndIf}
|
|
|
|
Exit;
|
|
end;
|
|
|
|
{$IfDef VerboseFocus}
|
|
DebugLn(' E taking ImplementationWidget');
|
|
{$EndIf}
|
|
end;
|
|
|
|
|
|
{$IFDEF ASSERT_IS_ON}
|
|
{$UNDEF ASSERT_IS_ON}
|
|
{$C-}
|
|
{$ENDIF}
|
|
|
|
// included by gtkproc.pp
|