mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-01 05:43:40 +02:00
5919 lines
185 KiB
PHP
5919 lines
185 KiB
PHP
{%MainUnit gtkint.pp}
|
|
|
|
{******************************************************************************
|
|
TGtkWidgetSet
|
|
******************************************************************************
|
|
|
|
*****************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
*****************************************************************************
|
|
}
|
|
|
|
{$IFOPT C-}
|
|
// Uncomment for local trace
|
|
// {$C+}
|
|
// {$DEFINE ASSERT_IS_ON}
|
|
{$ENDIF}
|
|
{off $define GtkFixedWithWindow}
|
|
{------------------------------------------------------------------------------
|
|
Procedure: GLogFunc
|
|
|
|
Replaces the default glib loghandler. All errors, warnings etc, are logged
|
|
through this function.
|
|
Here are Fatals, Criticals and Errors translated to Exceptions
|
|
Comment Ex to skip exception, comment Level to skip logging
|
|
------------------------------------------------------------------------------}
|
|
procedure GLogFunc(ALogDomain: Pgchar; ALogLevel: TGLogLevelFlags;
|
|
AMessage: Pgchar; AData: gpointer);cdecl;
|
|
var
|
|
Flag, Level, Domain: String;
|
|
Ex: ExceptClass;
|
|
begin
|
|
(*
|
|
G_LOG_FLAG_RECURSION = 1 shl 0;
|
|
G_LOG_FLAG_FATAL = 1 shl 1;
|
|
G_LOG_LEVEL_ERROR = 1 shl 2;
|
|
G_LOG_LEVEL_CRITICAL = 1 shl 3;
|
|
G_LOG_LEVEL_WARNING = 1 shl 4;
|
|
G_LOG_LEVEL_MESSAGE = 1 shl 5;
|
|
G_LOG_LEVEL_INFO = 1 shl 6;
|
|
G_LOG_LEVEL_DEBUG = 1 shl 7;
|
|
G_LOG_LEVEL_MASK = (1 shl 8) - 2;
|
|
*)
|
|
if (AData=nil) then ;
|
|
|
|
Ex := nil;
|
|
Level := '';
|
|
Flag := '';
|
|
|
|
if ALogDomain = nil
|
|
then Domain := ''
|
|
else Domain := ALogDomain + ': ';
|
|
|
|
if ALogLevel and G_LOG_FLAG_RECURSION <> 0
|
|
then Flag := '[RECURSION] ';
|
|
|
|
if ALogLevel and G_LOG_FLAG_FATAL <> 0
|
|
then Flag := Flag + '[FATAL] ';
|
|
|
|
if ALogLevel and G_LOG_LEVEL_ERROR <> 0
|
|
then begin
|
|
Level := 'ERROR';
|
|
Ex := EInterfaceError;
|
|
end
|
|
else
|
|
if ALogLevel and G_LOG_LEVEL_CRITICAL <> 0
|
|
then begin
|
|
Level := 'CRITICAL';
|
|
Ex := EInterfaceCritical;
|
|
end
|
|
else
|
|
if ALogLevel and G_LOG_LEVEL_WARNING <> 0
|
|
then begin
|
|
Level := 'WARNING';
|
|
Ex := EInterfaceWarning;
|
|
end
|
|
else
|
|
if ALogLevel and G_LOG_LEVEL_INFO <> 0
|
|
then begin
|
|
Level := 'INFO';
|
|
end
|
|
else
|
|
if ALogLevel and G_LOG_LEVEL_DEBUG <> 0
|
|
then begin
|
|
Level := 'DEBUG';
|
|
end
|
|
else begin
|
|
Level := 'USER';
|
|
end;
|
|
|
|
if Ex = nil
|
|
then begin
|
|
if Level <> ''
|
|
then DebugLn('[', Level, '] ', Flag, Domain, AMessage);
|
|
end
|
|
else begin
|
|
if ALogLevel and G_LOG_FLAG_FATAL <> 0
|
|
then begin
|
|
// always create exception
|
|
//
|
|
// see callstack for more info
|
|
raise Ex.Create(Flag + Domain + AMessage);
|
|
end
|
|
else begin
|
|
// create a debugger trappable exception
|
|
// but for now let the app continue and log a line
|
|
// in future when all warnings etc. are gone they might raise
|
|
// a real exception
|
|
//
|
|
// see callstack for more info
|
|
try
|
|
raise Ex.Create(Flag + Domain + AMessage);
|
|
except
|
|
on Exception do begin
|
|
// just write a line
|
|
DebugLn('[', Level, '] ', Flag, Domain, AMessage);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
{$ifdef Unix}
|
|
|
|
// TThread.Synchronize support
|
|
var
|
|
threadsync_pipein, threadsync_pipeout: cint;
|
|
threadsync_giochannel: pgiochannel;
|
|
childsig_pending: boolean;
|
|
|
|
procedure ChildEventHandler(sig: longint; siginfo: psiginfo;
|
|
sigcontext: psigcontext); cdecl;
|
|
begin
|
|
childsig_pending := true;
|
|
WakeMainThread(nil);
|
|
end;
|
|
|
|
procedure InstallSignalHandler;
|
|
var
|
|
child_action: sigactionrec;
|
|
begin
|
|
child_action.sa_handler := @ChildEventHandler;
|
|
fpsigemptyset(child_action.sa_mask);
|
|
child_action.sa_flags := 0;
|
|
fpsigaction(SIGCHLD, @child_action, nil);
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.Create
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Constructor for the class.
|
|
------------------------------------------------------------------------------}
|
|
constructor TGtkWidgetSet.Create;
|
|
{$IFDEF EnabledGtkThreading}
|
|
{$IFNDEF Win32}
|
|
var
|
|
TM: TThreadManager;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
begin
|
|
if ClassType = TGtkWidgetSet
|
|
then raise EInvalidOperation.Create('Cannot create the base gtkwidgetset, use gtk1 or gtk2 instead');
|
|
|
|
inherited Create;
|
|
|
|
FAppActive := False;
|
|
FLastFocusIn := nil;
|
|
FLastFocusOut := nil;
|
|
|
|
LastWFPMousePos := Point(MaxInt, MaxInt);
|
|
|
|
{$IFDEF EnabledGtkThreading}
|
|
{$IFNDEF Win32}
|
|
if GetThreadManager(TM) and Assigned(TM.InitManager) and g_thread_supported then
|
|
begin
|
|
g_thread_init(nil);
|
|
gdk_threads_init;
|
|
gdk_threads_enter;
|
|
fMultiThreadingEnabled := True;
|
|
end;
|
|
{$ELSE}
|
|
g_thread_init(nil);
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
|
|
// DCs, GDIObjects
|
|
FDeviceContexts := TDynHashArray.Create(-1);
|
|
FDeviceContexts.Options:=FDeviceContexts.Options+[dhaoCacheContains];
|
|
FGDIObjects := TDynHashArray.Create(-1);
|
|
FGDIObjects.Options:=FGDIObjects.Options+[dhaoCacheContains];
|
|
GtkDef.ReleaseGDIObject:=@ReleaseGDIObject;
|
|
GtkDef.ReferenceGDIObject:=@ReferenceGDIObject;
|
|
|
|
{$Ifdef GTK2}
|
|
FDefaultFontDesc:= nil;
|
|
{$Else}
|
|
FDefaultFont:= nil;
|
|
{$EndIf}
|
|
// messages
|
|
FMessageQueue := TGtkMessageQueue.Create;
|
|
WaitingForMessages := false;
|
|
FWidgetsWithResizeRequest := TDynHashArray.Create(-1);
|
|
FWidgetsWithResizeRequest.Options:=
|
|
FWidgetsWithResizeRequest.Options+[dhaoCacheContains];
|
|
FWidgetsResized := TDynHashArray.Create(-1);
|
|
FWidgetsResized.Options:=FWidgetsResized.Options+[dhaoCacheContains];
|
|
FFixWidgetsResized := TDynHashArray.Create(-1);
|
|
|
|
FTimerData := TFPList.Create;
|
|
{$IFDEF Use_KeyStateList}
|
|
FKeyStateList_ := TFPList.Create;
|
|
{$ENDIF}
|
|
|
|
DestroyConnectedWidgetCB:=@DestroyConnectedWidget;
|
|
|
|
FRCFilename := ChangeFileExt(ParamStrUTF8(0),'.gtkrc');
|
|
FRCFileParsed := false;
|
|
|
|
// initialize app level gtk engine
|
|
gtk_set_locale ();
|
|
|
|
// call init and pass cmd line args
|
|
PassCmdLineOptions;
|
|
|
|
// set glib log handler
|
|
FLogHandlerID := g_log_set_handler(nil, -1, @GLogFunc, Self);
|
|
|
|
// read gtk rc file
|
|
ParseRCFile;
|
|
|
|
// Initialize Stringlist for holding styles
|
|
Styles := TStringlist.Create;
|
|
|
|
{$IFDEF Use_KeyStateList}
|
|
gtk_key_snooper_install(@GTKKeySnooper, FKeyStateList_);
|
|
{$ELSE}
|
|
gtk_key_snooper_install(@GTKKeySnooper, nil);
|
|
{$ENDIF}
|
|
|
|
// Init tooltips
|
|
FGTKToolTips := gtk_tooltips_new;
|
|
//gtk_object_ref(PGTKObject(FGTKToolTips));
|
|
gtk_toolTips_Enable(FGTKToolTips);
|
|
|
|
// Init stock objects;
|
|
InitStockItems;
|
|
InitSystemColors;
|
|
InitSystemBrushes;
|
|
|
|
// clipboard
|
|
ClipboardTypeAtoms[ctPrimarySelection]:=GDK_SELECTION_PRIMARY;
|
|
ClipboardTypeAtoms[ctSecondarySelection]:=GDK_SELECTION_SECONDARY;
|
|
ClipboardTypeAtoms[ctClipboard]:=gdk_atom_intern('CLIPBOARD',GdkFalse);
|
|
|
|
{$ifdef Unix}
|
|
InitSynchronizeSupport;
|
|
{$ifdef UseAsyncProcess}
|
|
DebugLn(['TGtkWidgetSet.Create Installing signal handler for TAsyncProcess']);
|
|
InstallSignalHandler;
|
|
{$endif}
|
|
{$endif}
|
|
|
|
GTKWidgetSet := Self;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.PassCmdLineOptions
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Passes command line options to the gtk engine
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.PassCmdLineOptions;
|
|
|
|
function SearchOption(const Option: string; Remove: boolean): boolean;
|
|
var
|
|
i: Integer;
|
|
ArgCount: LongInt;
|
|
begin
|
|
Result:=false;
|
|
if Option='' then exit;
|
|
i:=0;
|
|
ArgCount:=argc;
|
|
while i<ArgCount do begin
|
|
if AnsiStrComp(PChar(Option),argv[i])=0 then begin
|
|
// option exists
|
|
Result:=true;
|
|
if Remove then begin
|
|
// remove option from parameters, so that no other parameter parsed
|
|
// can see it.
|
|
dec(ArgCount);
|
|
while i<ArgCount do begin
|
|
argv[i]:=argv[i+1];
|
|
inc(i);
|
|
end;
|
|
argv[i]:=nil;
|
|
end;
|
|
exit;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
gtk_init(@argc,@argv);
|
|
UseTransientForModalWindows:=not SearchOption('--lcl-no-transient',true);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.FreeAllStyles;
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.FreeAllStyles;
|
|
begin
|
|
If Assigned(Styles) then begin
|
|
ReleaseAllStyles;
|
|
Styles.Free;
|
|
Styles:=nil;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef TraceGdiCalls}
|
|
procedure DumpBackTrace(BackTrace: TCallBacksArray);
|
|
var
|
|
func,source: shortString;
|
|
line: longint;
|
|
i: integer;
|
|
begin
|
|
for i:=0 to MaxCallBacks do begin
|
|
LineInfo.GetLineInfo(longWord(BackTrace[i]), Func, source, line);
|
|
DebugLn('$', Hexstr(LongInt(BackTrace[i]),8),' ', Func, ', line ',
|
|
dbgs(line),' of ',Source);
|
|
end;
|
|
end;
|
|
|
|
procedure FillStackAddrs(bp: pointer; BackTraces: PCallBacksArray);
|
|
var
|
|
prevbp: pointer;
|
|
caller_frame,
|
|
caller_addr : Pointer;
|
|
i: Integer;
|
|
begin
|
|
Prevbp := bp-1;
|
|
i:=0;
|
|
while (bp>prevbp)do begin
|
|
caller_addr := get_caller_addr(bp);
|
|
caller_frame := get_caller_frame(bp);
|
|
BackTraces^[i] := Caller_Addr;
|
|
inc(i);
|
|
if (caller_addr=nil) or
|
|
(caller_frame=nil) or
|
|
(i>MaxCallBacks) then
|
|
break;
|
|
prevbp:=bp;
|
|
bp:=caller_frame;
|
|
end;
|
|
end;
|
|
{$endif}
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.Destroy
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Destructor for the class.
|
|
------------------------------------------------------------------------------}
|
|
destructor TGtkWidgetSet.Destroy;
|
|
const
|
|
ProcName = '[TGtkWidgetSet.Destroy]';
|
|
var
|
|
n: Integer;
|
|
pTimerInfo : PGtkITimerinfo;
|
|
GDITypeCount: array[TGDIType] of Integer;
|
|
GDIType: TGDIType;
|
|
HashItem: PDynHashArrayItem;
|
|
QueueItem : TGtkMessageQueueItem;
|
|
NextQueueItem : TGtkMessageQueueItem;
|
|
begin
|
|
if FDockImage <> nil then
|
|
gtk_widget_destroy(FDockImage);
|
|
|
|
ReAllocMem(FExtUTF8OutCache,0);
|
|
FExtUTF8OutCacheSize:=0;
|
|
|
|
FreeAllStyles;
|
|
FreeStockItems;
|
|
FreeSystemBrushes;
|
|
|
|
if FGTKToolTips<>nil then begin
|
|
{$IFDEF Gtk2}
|
|
gtk_object_sink(PGTKObject(FGTKToolTips));
|
|
{$ELSE}
|
|
gtk_object_unref(PGTKObject(FGTKToolTips));
|
|
{$ENDIF}
|
|
FGTKToolTips := nil;
|
|
end;
|
|
|
|
// tidy up the paint messages
|
|
FMessageQueue.Lock;
|
|
try
|
|
QueueItem:=FMessageQueue.FirstMessageItem;
|
|
while (QueueItem<>nil) do begin
|
|
NextQueueItem := TGtkMessageQueueItem(QueueItem.Next);
|
|
if QueueItem.IsPaintMessage then
|
|
fMessageQueue.RemoveMessage(QueueItem,FPMF_All,true);
|
|
QueueItem := NextQueueItem;
|
|
end;
|
|
|
|
// warn about unremoved paint messages
|
|
if fMessageQueue.HasPaintMessages then begin
|
|
DebugLn(ProcName, Format(rsWarningUnremovedPaintMessages,
|
|
[IntToStr(fMessageQueue.NumberOfPaintMessages)]));
|
|
end;
|
|
finally
|
|
FMessageQueue.UnLock;
|
|
end;
|
|
|
|
// warn about unreleased DC
|
|
if (FDeviceContexts.Count > 0)
|
|
then begin
|
|
DebugLn(ProcName, Format(rsWarningUnreleasedDCsDump,
|
|
[FDeviceContexts.Count]));
|
|
|
|
n:=0;
|
|
DbgOut(ProcName,' DCs: ');
|
|
HashItem:=FDeviceContexts.FirstHashItem;
|
|
while (n<7) and (HashItem<>nil) do
|
|
begin
|
|
DbgOut(' ',DbgS(HashItem^.Item));
|
|
HashItem:=HashItem^.Next;
|
|
inc(n);
|
|
end;
|
|
DebugLn();
|
|
end;
|
|
|
|
// warn about unreleased gdi objects
|
|
if (FGDIObjects.Count > 0)
|
|
then begin
|
|
DebugLn(ProcName,Format(rsWarningUnreleasedGDIObjectsDump,
|
|
[FGDIObjects.Count]));
|
|
for GDIType := Low(TGDIType) to High(TGDIType) do
|
|
GDITypeCount[GDIType] := 0;
|
|
|
|
n:=0;
|
|
{$ifndef TraceGdiCalls}
|
|
DbgOut(ProcName,' GDIOs:');
|
|
{$endif}
|
|
HashItem := FGDIObjects.FirstHashItem;
|
|
while (HashItem <> nil) do
|
|
begin
|
|
{$ifndef TraceGdiCalls}
|
|
if n < 7
|
|
then
|
|
DbgOut(' ',DbgS(HashItem^.Item));
|
|
{$endif}
|
|
|
|
Inc(GDITypeCount[PGdiObject(HashItem^.Item)^.GDIType]);
|
|
HashItem := HashItem^.Next;
|
|
Inc(n);
|
|
end;
|
|
{$ifndef TraceGdiCalls}
|
|
DebugLn();
|
|
{$endif}
|
|
|
|
for GDIType := Low(GDIType) to High(GDIType) do
|
|
if GDITypeCount[GDIType] > 0 then
|
|
DebugLn(ProcName,Format(' %s: %d', [dbgs(GDIType), GDITypeCount[GDIType]]));
|
|
|
|
// tidy up messages
|
|
if FMessageQueue.Count > 0 then begin
|
|
DebugLn(ProcName, Format(rsWarningUnreleasedMessagesInQueue,[FMessageQueue.Count]));
|
|
while FMessageQueue.First<>nil do
|
|
fMessageQueue.RemoveMessage(fMessageQueue.FirstMessageItem,FPMF_All,true);
|
|
end;
|
|
end;
|
|
|
|
// warn about unreleased timers
|
|
n := FTimerData.Count;
|
|
if (n > 0) then
|
|
begin
|
|
DebugLn(ProcName,Format(rsWarningUnreleasedTimerInfos,[n]));
|
|
while (n > 0) do
|
|
begin
|
|
dec (n);
|
|
pTimerInfo := PGtkITimerinfo (FTimerData.Items[n]);
|
|
Dispose (pTimerInfo);
|
|
FTimerData.Delete (n);
|
|
end;
|
|
end;
|
|
|
|
{$ifdef TraceGdiCalls}
|
|
if FDeviceContexts.Count>0 then begin
|
|
//DebugLn('BackTrace for unreleased device contexts follows:');
|
|
n:=0;
|
|
HashItem:=FDeviceContexts.FirstHashItem;
|
|
while (HashItem<>nil) and (n<MaxTraces) do
|
|
begin
|
|
DebugLn('DC: ', Dbgs(HashItem^.Item));
|
|
DumpBackTrace(TGtkDeviceContext(HashItem^.Item).StackAddrs);
|
|
DebugLn();
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
if (n>=MaxTraces) then begin
|
|
DebugLn('... Truncated dump DeviceContext leakage dump.');
|
|
DebugLn();
|
|
end;
|
|
end;
|
|
|
|
if (FGDIObjects.Count > 0)
|
|
then begin
|
|
//DebugLn('BackTrace for unreleased gdi objects follows:');
|
|
for GDIType := Low(TGDIType) to High(TGDIType) do begin
|
|
if GDITypeCount[GDIType]<>0 then begin
|
|
n:=0;
|
|
HashItem := FGDIObjects.FirstHashItem;
|
|
while (HashItem <> nil) and (n<MaxTraces) do begin
|
|
DebugLn(dbgs(gdiType),': ', dbgs(HashItem^.Item));
|
|
DumpBackTrace(PgdiObject(HashItem^.Item)^.StackAddrs);
|
|
DebugLn();
|
|
HashItem := HashItem^.Next;
|
|
inc(n);
|
|
end;
|
|
if (n>=MaxTraces) then begin
|
|
DebugLn('... Truncated ',dbgs(GDIType),' leakage dump.');
|
|
DebugLn();
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
FreeAndNil(FWidgetsWithResizeRequest);
|
|
FreeAndNil(FWidgetsResized);
|
|
FreeAndNil(FFixWidgetsResized);
|
|
FreeAndNil(FMessageQueue);
|
|
FreeAndNil(FDeviceContexts);
|
|
FreeAndNil(FGDIObjects);
|
|
{$IFDEF Use_KeyStateList}
|
|
FreeAndNil(FKeyStateList_);
|
|
{$ENDIF}
|
|
FreeAndNil(FTimerData);
|
|
|
|
GtkDefDone;
|
|
FreeAndNil(FDCManager);
|
|
|
|
// finally remove our loghandler
|
|
g_log_remove_handler(nil, FLogHandlerID);
|
|
|
|
GTKWidgetSet := nil;
|
|
WakeMainThread := nil;
|
|
|
|
{$IFDEF EnabledGtkThreading}
|
|
if MultiThreadingEnabled then
|
|
begin
|
|
{$IFNDEF Win32}
|
|
gdk_threads_leave;
|
|
{$ENDIF}
|
|
fMultiThreadingEnabled := False;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{$ifdef Unix}
|
|
|
|
procedure TGtkWidgetSet.PrepareSynchronize(AObject: TObject);
|
|
{ This method is the WakeMainThread of the unit classes.
|
|
It is called in TThread.Synchronize to wake up the main thread = LCL GUI thread.
|
|
see: TGtkWidgetSet.InitSynchronizeSupport
|
|
}
|
|
var
|
|
thrash: char;
|
|
begin
|
|
// wake up GUI thread by sending a byte through the threadsync pipe
|
|
thrash:='l';
|
|
fpwrite(threadsync_pipeout, thrash, 1);
|
|
end;
|
|
|
|
procedure TGtkWidgetSet.ProcessChildSignal;
|
|
var
|
|
pid: tpid;
|
|
reason: TChildExitReason;
|
|
status: integer;
|
|
info: dword;
|
|
handler: PChildSignalEventHandler;
|
|
begin
|
|
repeat
|
|
pid := fpwaitpid(-1, status, WNOHANG);
|
|
if pid <= 0 then break;
|
|
if wifexited(status) then
|
|
begin
|
|
reason := cerExit;
|
|
info := wexitstatus(status);
|
|
end else
|
|
if wifsignaled(status) then
|
|
begin
|
|
reason := cerSignal;
|
|
info := wtermsig(status);
|
|
end else
|
|
continue;
|
|
|
|
handler := FChildSignalHandlers;
|
|
while handler <> nil do
|
|
begin
|
|
if handler^.pid = pid then
|
|
begin
|
|
handler^.OnEvent(handler^.UserData, reason, info);
|
|
break;
|
|
end;
|
|
handler := handler^.NextHandler;
|
|
end;
|
|
until false;
|
|
end;
|
|
|
|
function threadsync_iocallback(source: PGIOChannel; condition: TGIOCondition;
|
|
data: gpointer): gboolean; cdecl;
|
|
var
|
|
thrashspace: array[1..1024] of byte;
|
|
begin
|
|
// read the sent bytes
|
|
fpread(threadsync_pipein, thrashspace[1], 1);
|
|
|
|
Result := true;
|
|
// one of children signaled ?
|
|
if childsig_pending then
|
|
begin
|
|
childsig_pending := false;
|
|
TGtkWidgetSet(data).ProcessChildSignal;
|
|
end;
|
|
// execute the to-be synchronized method
|
|
if IsMultiThread then
|
|
CheckSynchronize;
|
|
end;
|
|
|
|
procedure TGtkWidgetSet.InitSynchronizeSupport;
|
|
{ When a thread calls its Synchronize, it calls
|
|
WakeMainThread (defined in the unit classes).
|
|
Set
|
|
}
|
|
begin
|
|
{ TThread.Synchronize ``glue'' }
|
|
WakeMainThread := @PrepareSynchronize;
|
|
assignpipe(threadsync_pipein, threadsync_pipeout);
|
|
threadsync_giochannel := g_io_channel_unix_new(threadsync_pipein);
|
|
g_io_add_watch(threadsync_giochannel, G_IO_IN, @threadsync_iocallback, Self);
|
|
end;
|
|
|
|
{$else}
|
|
|
|
{$message warn TThread.Synchronize will not work on Gtk/Win32 }
|
|
|
|
procedure InitSynchronizeSupport;
|
|
begin
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.UpdateTransientWindows;
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.UpdateTransientWindows;
|
|
|
|
type
|
|
PTransientWindow = ^TTransientWindow;
|
|
TTransientWindow = record
|
|
GtkWindow: PGtkWindow;
|
|
Component: TComponent;
|
|
IsModal: boolean;
|
|
SortIndex: integer;
|
|
TransientParent: PGtkWindow;
|
|
end;
|
|
|
|
var
|
|
AllWindows: TFPList;
|
|
List: PGList;
|
|
Window: PGTKWindow;
|
|
ATransientWindow: PTransientWindow;
|
|
LCLObject: TObject;
|
|
LCLComponent: TComponent;
|
|
i: Integer;
|
|
FirstModal: Integer;
|
|
j: Integer;
|
|
ATransientWindow1: PTransientWindow;
|
|
ATransientWindow2: PTransientWindow;
|
|
ParentTransientWindow: PTransientWindow;
|
|
OldTransientParent: PGtkWindow;
|
|
begin
|
|
if (not UseTransientForModalWindows) then exit;
|
|
if UpdatingTransientWindows then begin
|
|
DebugLn('TGtkWidgetSet.UpdateTransientWindows already updating');
|
|
exit;
|
|
end;
|
|
UpdatingTransientWindows:=true;
|
|
try
|
|
{$IFDEF VerboseTransient}
|
|
DebugLn('TGtkWidgetSet.UpdateTransientWindows');
|
|
{$ENDIF}
|
|
AllWindows:=nil;
|
|
|
|
// find all currently visible gtkwindows
|
|
List := gdk_window_get_toplevels;
|
|
while List <> nil do
|
|
begin
|
|
if (List^.Data <> nil)
|
|
then begin
|
|
gdk_window_get_user_data(PGDKWindow(List^.Data), Pgpointer(@Window));
|
|
if GtkWidgetIsA(PGtkWidget(Window), GTK_TYPE_WINDOW)
|
|
and gtk_widget_visible(PGtkWidget(Window))
|
|
then begin
|
|
// visible window found -> add to list
|
|
New(ATransientWindow);
|
|
FillChar(ATransientWindow^,SizeOf(TTransientWindow),0);
|
|
ATransientWindow^.GtkWindow:=Window;
|
|
LCLObject:=GetLCLObject(Window);
|
|
if (LCLObject<>nil) and (LCLObject is TComponent) then begin
|
|
LCLComponent:=TComponent(LCLObject);
|
|
ATransientWindow^.Component:=LCLComponent;
|
|
end;
|
|
if (ModalWindows<>nil) then
|
|
ATransientWindow^.SortIndex:=ModalWindows.IndexOf(Window)
|
|
else
|
|
ATransientWindow^.SortIndex:=-1;
|
|
ATransientWindow^.IsModal:=(ATransientWindow^.SortIndex>=0)
|
|
and (GTK_WIDGET_VISIBLE(PGtkWidget(Window)));
|
|
if not ATransientWindow^.IsModal then begin
|
|
if (LCLObject is TCustomForm)
|
|
and (TCustomForm(LCLObject).Parent=nil) then
|
|
ATransientWindow^.SortIndex:=
|
|
Screen.CustomFormZIndex(TCustomForm(LCLObject));
|
|
end;
|
|
|
|
if ATransientWindow^.SortIndex<0 then begin
|
|
// this window has no form. Move it to the back.
|
|
ATransientWindow^.SortIndex:=Screen.CustomFormCount;
|
|
end;
|
|
|
|
//DebugLn(['TGtkWidgetSet.UpdateTransientWindows LCLObject=',DbgSName(LCLObject),' ATransientWindow^.SortIndex=',ATransientWindow^.SortIndex]);
|
|
if AllWindows=nil then AllWindows:=TFPList.Create;
|
|
AllWindows.Add(ATransientWindow);
|
|
end;
|
|
end;
|
|
list := g_list_next(list);
|
|
end;
|
|
|
|
if AllWindows=nil then exit;
|
|
|
|
//for i:=0 to SCreen.CustomFormZOrderCount-1 do
|
|
// DebugLn(['TGtkWidgetSet.UpdateTransientWindows i=',i,'/',SCreen.CustomFormZOrderCount,' ',DbgSName(SCreen.CustomFormsZOrdered[i])]);
|
|
|
|
// sort
|
|
// move all modal windows to the end of the window list
|
|
i:=AllWindows.Count-1;
|
|
FirstModal:=AllWindows.Count;
|
|
while i>=0 do begin
|
|
ATransientWindow:=PTransientWindow(AllWindows[i]);
|
|
if ATransientWindow^.IsModal
|
|
and (i<FirstModal) then begin
|
|
dec(FirstModal);
|
|
if i<FirstModal then
|
|
AllWindows.Exchange(i,FirstModal);
|
|
end;
|
|
dec(i);
|
|
end;
|
|
|
|
if FirstModal=AllWindows.Count then begin
|
|
// there is no modal window
|
|
// -> break all transient window relation ships
|
|
for i:=AllWindows.Count-1 downto 0 do begin
|
|
ATransientWindow:=PTransientWindow(AllWindows[i]);
|
|
{$IFDEF VerboseTransient}
|
|
DbgOut('TGtkWidgetSet.UpdateTransientWindows Untransient ',i);
|
|
if ATransientWindow^.Component<>nil then
|
|
DbgOut(' ',ATransientWindow^.Component.Name,':',ATransientWindow^.Component.ClassName);
|
|
DebugLn('');
|
|
{$ENDIF}
|
|
gtk_window_set_transient_for(ATransientWindow^.GtkWindow,nil);
|
|
end;
|
|
end else begin
|
|
// there are modal windows
|
|
// -> sort windows in z order and setup transient relationships
|
|
|
|
//DebugLn(['TGtkWidgetSet.UpdateTransientWindows ModalWindows=',AllWindows.Count-FirstModal,' NonModalWindows=',FirstModal]);
|
|
|
|
// sort modal windows (bubble sort)
|
|
for i:=FirstModal to AllWindows.Count-2 do begin
|
|
for j:=i+1 to AllWindows.Count-1 do begin
|
|
ATransientWindow1:=PTransientWindow(AllWindows[i]);
|
|
ATransientWindow2:=PTransientWindow(AllWindows[j]);
|
|
if ATransientWindow1^.SortIndex>ATransientWindow2^.SortIndex then
|
|
AllWindows.Exchange(i,j);
|
|
end;
|
|
end;
|
|
|
|
// sort non modal windows for z order
|
|
// ToDo: How do we get the z order?
|
|
// For now, just use the inverse order in the Screen object
|
|
// that means: the lower in the Screen object, the later in the transient list
|
|
for i:=0 to FirstModal-2 do begin
|
|
for j:=i+1 to FirstModal-1 do begin
|
|
ATransientWindow1:=PTransientWindow(AllWindows[i]);
|
|
ATransientWindow2:=PTransientWindow(AllWindows[j]);
|
|
if ATransientWindow1^.SortIndex<ATransientWindow2^.SortIndex then
|
|
AllWindows.Exchange(i,j);
|
|
end;
|
|
end;
|
|
|
|
// set all transient relationships for LCL windows
|
|
ParentTransientWindow:=nil;
|
|
for i:=0 to AllWindows.Count-1 do begin
|
|
ATransientWindow:=PTransientWindow(AllWindows[i]);
|
|
if (ATransientWindow^.Component<>nil)
|
|
and GTK_WIDGET_VISIBLE(PgtkWidget(ATransientWindow^.GtkWindow)) then
|
|
begin
|
|
if ParentTransientWindow<>nil then begin
|
|
{$IFDEF VerboseTransient}
|
|
DebugLn('Define TRANSIENT ',
|
|
' Parent=',
|
|
ParentTransientWindow^.Component.Name,':',
|
|
ParentTransientWindow^.Component.ClassName,
|
|
' Index=',ParentTransientWindow^.SortIndex,
|
|
' Wnd=',DbgS(ParentTransientWindow^.GtkWindow),
|
|
' Child=',ATransientWindow^.Component.Name,':',
|
|
ATransientWindow^.Component.ClassName,
|
|
' Index=',ATransientWindow^.SortIndex,
|
|
' Wnd=',DbgS(ATransientWindow^.GtkWindow),
|
|
'');
|
|
{$ENDIF}
|
|
ATransientWindow^.TransientParent:=ParentTransientWindow^.GtkWindow;
|
|
end;
|
|
ParentTransientWindow:=ATransientWindow;
|
|
end;
|
|
end;
|
|
|
|
// Each transient relationship can reorder the visible forms
|
|
// To reduce flickering and creation of temporary circles
|
|
// do the setup in two separate steps:
|
|
|
|
// break unneeded transient relationships
|
|
for i:=AllWindows.Count-1 downto 0 do begin
|
|
ATransientWindow:=PTransientWindow(AllWindows[i]);
|
|
OldTransientParent:=ATransientWindow^.GtkWindow^.transient_parent;
|
|
if (OldTransientParent<>ATransientWindow^.TransientParent) then begin
|
|
{$IFDEF VerboseTransient}
|
|
DebugLn('Break old TRANSIENT i=',i,'/',AllWindows.Count,
|
|
' OldTransientParent=',DbgS(OldTransientParent),
|
|
' Child=',ATransientWindow^.Component.Name,':',
|
|
ATransientWindow^.Component.ClassName,
|
|
' Index=',ATransientWindow^.SortIndex,
|
|
' Wnd=',DbgS(ATransientWindow^.GtkWindow),
|
|
'');
|
|
{$ENDIF}
|
|
gtk_window_set_transient_for(ATransientWindow^.GtkWindow,nil);
|
|
end;
|
|
end;
|
|
|
|
// setup transient relationships
|
|
for i:=0 to AllWindows.Count-1 do begin
|
|
ATransientWindow:=PTransientWindow(AllWindows[i]);
|
|
if ATransientWindow^.TransientParent=nil then continue;
|
|
{$IFDEF VerboseTransient}
|
|
DebugLn('Set TRANSIENT i=',i,'/',AllWindows.Count,
|
|
' Child=',ATransientWindow^.Component.Name,':',
|
|
ATransientWindow^.Component.ClassName,
|
|
' Index=',ATransientWindow^.SortIndex,
|
|
' Wnd=',DbgS(ATransientWindow^.GtkWindow),
|
|
' Parent=',DbgS(ATransientWindow^.TransientParent),
|
|
'');
|
|
{$ENDIF}
|
|
gtk_window_set_transient_for(ATransientWindow^.GtkWindow,
|
|
ATransientWindow^.TransientParent);
|
|
end;
|
|
end;
|
|
|
|
// clean up
|
|
for i:=0 to AllWindows.Count-1 do begin
|
|
ATransientWindow:=PTransientWindow(AllWindows[i]);
|
|
Dispose(ATransientWindow);
|
|
end;
|
|
AllWindows.Free;
|
|
finally
|
|
UpdatingTransientWindows:=false;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.UntransientWindow(GtkWindow: PGtkWindow);
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.UntransientWindow(GtkWindow: PGtkWindow);
|
|
{$IFDEF VerboseTransient}
|
|
var
|
|
LCLObject: TObject;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF VerboseTransient}
|
|
DbgOut('TGtkWidgetSet.UntransientWindow ',DbgS(GtkWindow));
|
|
LCLObject:=GetLCLObject(PGtkWidget(GtkWindow));
|
|
if LCLObject<>nil then
|
|
DbgOut(' LCLObject=',LCLObject.ClassName)
|
|
else
|
|
DbgOut(' LCLObject=nil');
|
|
DebugLn('');
|
|
{$ENDIF}
|
|
// hide window, so that UpdateTransientWindows untransients it
|
|
if GTK_WIDGET_VISIBLE(PgtkWidget(GtkWindow)) then
|
|
gtk_widget_hide(PgtkWidget(GtkWindow));
|
|
UpdateTransientWindows;
|
|
// remove it from the modal window list
|
|
if ModalWindows<>nil then begin
|
|
ModalWindows.Remove(GtkWindow);
|
|
if ModalWindows.Count=0 then FreeAndNil(ModalWindows);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.SendCachedLCLMessages
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Some LCL messages are not sent directly to the gtk. Send them now.
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.SendCachedLCLMessages;
|
|
|
|
procedure SendCachedLCLResizeRequests;
|
|
var
|
|
Widget: PGtkWidget;
|
|
LCLControl: TWinControl;
|
|
IsTopLevelWidget: boolean;
|
|
TopologicalList: TFPList; // list of PGtkWidget;
|
|
i: integer;
|
|
|
|
procedure RaiseWidgetWithoutControl;
|
|
begin
|
|
RaiseGDBException('ERROR: TGtkWidgetSet.SendCachedLCLMessages Widget '
|
|
+DbgS(Widget)+' without LCL control');
|
|
end;
|
|
|
|
begin
|
|
if FWidgetsWithResizeRequest.Count=0 then exit;
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn('GGG1 SendCachedLCLResizeRequests SizeMsgCount=',dbgs(FWidgetsWithResizeRequest.Count));
|
|
{$ENDIF}
|
|
|
|
TopologicalList:=CreateTopologicalSortedWidgets(FWidgetsWithResizeRequest);
|
|
for i:=0 to TopologicalList.Count-1 do begin
|
|
Widget:=TopologicalList[i];
|
|
|
|
// resize widget
|
|
LCLControl:=TWinControl(GetLCLObject(Widget));
|
|
if (LCLControl=nil) or (not (LCLControl is TControl)) then begin
|
|
RaiseWidgetWithoutControl;
|
|
end;
|
|
{$IFDEF VerboseSizeMsg}
|
|
if CompareText(LCLControl.ClassName,'TScrollBar')=0 then
|
|
DebugLn('SendCachedLCLMessages ',LCLControl.Name,':',LCLControl.ClassName,
|
|
' ',dbgs(LCLControl.Left)+','+dbgs(LCLControl.Top)+','+dbgs(LCLControl.Width)+'x'+dbgs(LCLControl.Height));
|
|
{$ENDIF}
|
|
|
|
IsTopLevelWidget:= (LCLControl is TCustomForm)
|
|
and (LCLControl.Parent = nil);
|
|
|
|
if not IsTopLevelWidget then begin
|
|
SetWidgetSizeAndPosition(LCLControl);
|
|
end
|
|
else begin
|
|
// resize form
|
|
{$IFDEF VerboseFormPositioning}
|
|
DebugLn('VFP SendCachedLCLMessages1 ', dbgs(GetControlWindow(Widget)<>nil));
|
|
if (LCLControl is TCustomForm) then
|
|
DebugLn('VFP SendCachedLCLMessages2 ',LCLControl.ClassName,' ',
|
|
dbgs(LCLControl.Left),',',dbgs(LCLControl.Top),',',dbgs(LCLControl.Width),',',dbgs(LCLControl.Height));
|
|
{$ENDIF}
|
|
SetWindowSizeAndPosition(PgtkWindow(Widget),TWinControl(LCLControl));
|
|
end;
|
|
|
|
end;
|
|
TopologicalList.Free;
|
|
FWidgetsWithResizeRequest.Clear;
|
|
end;
|
|
|
|
begin
|
|
SendCachedLCLResizeRequests;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.LCLtoGtkMessagePending
|
|
Params: None
|
|
Returns: boolean
|
|
|
|
Returns true if any messages from the lcl to the gtk is in cache and needs
|
|
delivery.
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.LCLtoGtkMessagePending: boolean;
|
|
begin
|
|
Result:=(FWidgetsWithResizeRequest.Count>0);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.SendCachedGtkMessages
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Some Gtk messages are not sent directly to the LCL. Send them now.
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.SendCachedGtkMessages;
|
|
begin
|
|
SendCachedGtkResizeNotifications;
|
|
end;
|
|
|
|
{
|
|
Changes some colors of the widget style
|
|
|
|
IMPORTANT:
|
|
SystemColors like clBtnFace depend on the theme and widget class, so they
|
|
must be read from the theme. But many gtk themes do not provide all colors
|
|
and instead only provide bitmaps.
|
|
Since we don't have good fallbacks yet, and many controls use SystemColors
|
|
for Delphi compatibility: ignore SystemColors from the following list:
|
|
|
|
Gtk 2:
|
|
|
|
clNone (should be ignored anyway),
|
|
clBtnFace,
|
|
|
|
Gtk 1:
|
|
|
|
clNone,
|
|
Any system color
|
|
}
|
|
procedure TGtkWidgetSet.SetWidgetColor(const AWidget: PGtkWidget;
|
|
const FGColor, BGColor: TColor; const Mask: TGtkStateEnum);
|
|
var
|
|
i: integer;
|
|
xfg, xbg: TGdkColor;
|
|
ChangeFGColor: Boolean;
|
|
ChangeBGColor: Boolean;
|
|
{$IFDEF Gtk1}
|
|
WindowStyle, RCStyle: PGtkStyle;
|
|
begin
|
|
ChangeFGColor := (FGColor <> clNone);
|
|
ChangeBGColor := (BGColor <> clNone);
|
|
if (not ChangeFGColor) and (not ChangeBGColor) then Exit;
|
|
|
|
// the GTKAPIWidget is self drawn, so no use to change the widget style.
|
|
if GtkWidgetIsA(AWidget, GTKAPIWidget_GetType) then Exit;
|
|
|
|
{$IFDEF DisableWidgetColor}
|
|
exit;
|
|
{$ENDIF}
|
|
|
|
if (GTK_WIDGET_REALIZED(AWidget)) then
|
|
WindowStyle := gtk_style_copy(gtk_widget_get_style(AWidget))
|
|
else
|
|
WindowStyle := gtk_style_copy(gtk_rc_get_style(AWidget));
|
|
|
|
if (Windowstyle = nil) then
|
|
Windowstyle := gtk_style_new;
|
|
|
|
//DebugLn('TGtkWidgetSet.SetWidgetColor ',GetWidgetDebugReport(AWidget),' ',hexstr(FGColor,8),' ',hexstr(BGColor,8));
|
|
//RaiseGDBException('');
|
|
if ChangeFGColor then
|
|
begin
|
|
if (FGColor = clDefault) then
|
|
begin
|
|
RCStyle := gtk_rc_get_style(AWidget);
|
|
if RCStyle <> nil then
|
|
for i := GTK_STATE_NORMAL to GTK_STATE_INSENSITIVE do
|
|
begin
|
|
if i in mask then
|
|
begin
|
|
if GTK_STYLE_TEXT in mask then
|
|
windowStyle^.text[i] := RCStyle^.text[i]
|
|
else
|
|
windowStyle^.fg[i] := RCStyle^.fg[i];
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
xfg := AllocGDKColor(colorToRGB(FGColor));
|
|
for i := GTK_STATE_NORMAL to GTK_STATE_INSENSITIVE do
|
|
begin
|
|
if i in mask then
|
|
begin
|
|
if GTK_STYLE_TEXT in mask then
|
|
windowStyle^.text[i] := xfg
|
|
else
|
|
windowStyle^.fg[i] := xfg;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if ChangeBGColor then
|
|
begin
|
|
if (BGColor = clDefault) or (BGColor = clBtnFace) then
|
|
begin
|
|
RCStyle := gtk_rc_get_style(AWidget);
|
|
if RCStyle <> nil then
|
|
for i := GTK_STATE_NORMAL to GTK_STATE_INSENSITIVE do
|
|
begin
|
|
if i in mask then
|
|
begin
|
|
if GTK_STYLE_BASE in mask then
|
|
windowStyle^.base[i] := RCStyle^.base[i]
|
|
else
|
|
windowStyle^.bg[i] := RCStyle^.bg[i];
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
xbg := AllocGDKColor(colorToRGB(BGColor));
|
|
for i := GTK_STATE_NORMAL to GTK_STATE_INSENSITIVE do
|
|
begin
|
|
if i in mask then
|
|
begin
|
|
if GTK_STYLE_BASE in mask then
|
|
windowStyle^.base[i] := xbg
|
|
else
|
|
windowStyle^.bg[i] := xbg;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
gtk_widget_set_style(aWidget, windowStyle);
|
|
end;
|
|
{$ELSE}
|
|
NewColor: PGdkColor;
|
|
begin
|
|
ChangeFGColor := (FGColor <> clNone);
|
|
ChangeBGColor := (BGColor <> clNone);
|
|
|
|
if (not ChangeFGColor) and (not ChangeBGColor) then Exit;
|
|
|
|
// the GTKAPIWidget is self drawn, so no use to change the widget style.
|
|
if GtkWidgetIsA(AWidget, GTKAPIWidget_GetType) then Exit;
|
|
|
|
{$IFDEF DisableWidgetColor}
|
|
exit;
|
|
{$ENDIF}
|
|
|
|
//DebugLn('TGtkWidgetSet.SetWidgetColor ',GetWidgetDebugReport(AWidget),' ',hexstr(FGColor,8),' ',hexstr(BGColor,8));
|
|
//RaiseGDBException('');
|
|
if ChangeFGColor then
|
|
begin
|
|
if (FGColor = clDefault) then
|
|
NewColor := nil
|
|
else
|
|
begin
|
|
xfg := AllocGDKColor(ColorToRGB(FGColor));
|
|
NewColor := @xfg;
|
|
end;
|
|
for i := GTK_STATE_NORMAL to GTK_STATE_INSENSITIVE do
|
|
begin
|
|
if i in mask then
|
|
begin
|
|
if GTK_STYLE_TEXT in mask then
|
|
gtk_widget_modify_text(AWidget, i, NewColor)
|
|
else
|
|
gtk_widget_modify_fg(AWidget, i, NewColor);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if ChangeBGColor then
|
|
begin
|
|
if (BGColor = clDefault) or (BGColor = clBtnFace) then
|
|
NewColor := nil
|
|
else
|
|
begin
|
|
xbg := AllocGDKColor(ColorToRGB(BGColor));
|
|
NewColor := @xbg;
|
|
end;
|
|
for i := GTK_STATE_NORMAL to GTK_STATE_INSENSITIVE do
|
|
begin
|
|
if i in mask then
|
|
begin
|
|
if GTK_STYLE_BASE in mask then
|
|
gtk_widget_modify_base(AWidget, i, NewColor)
|
|
else
|
|
gtk_widget_modify_bg(AWidget, i, NewColor);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.AppProcessMessages
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Handle all pending messages of the GTK engine and of this interface
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.AppProcessMessages;
|
|
|
|
function PendingGtkMessagesExists: boolean;
|
|
begin
|
|
Result:=(gtk_events_pending<>0) or LCLtoGtkMessagePending;
|
|
end;
|
|
|
|
var
|
|
vlItem : TGtkMessageQueueItem;
|
|
vlMsg : PMSg;
|
|
i: Integer;
|
|
begin
|
|
repeat
|
|
// send cached LCL messages to the gtk
|
|
//DebugLn(['TGtkWidgetSet.AppProcessMessages SendCachedLCLMessages']);
|
|
SendCachedLCLMessages;
|
|
|
|
// let gtk handle up to 100 messages and call our callbacks
|
|
i:=100;
|
|
while (gtk_events_pending<>0) and (i>0) do begin
|
|
gtk_main_iteration_do(False);
|
|
dec(i);
|
|
end;
|
|
|
|
//DebugLn(['TGtkWidgetSet.AppProcessMessages SendCachedGtkMessages']);
|
|
// send cached gtk messages to the lcl
|
|
SendCachedGtkMessages;
|
|
|
|
// then handle our own messages
|
|
while not Application.Terminated do begin
|
|
fMessageQueue.Lock;
|
|
try
|
|
// fetch first message
|
|
vlItem := fMessageQueue.FirstMessageItem;
|
|
if vlItem = nil then break;
|
|
|
|
// remove message from queue
|
|
if vlItem.IsPaintMessage then begin
|
|
//DebugLn(['TGtkWidgetSet.AppProcessMessages Paint: ',DbgSName(GetLCLObject(Pointer(vlItem.Msg^.hwnd)))]);
|
|
// paint messages are the most expensive messages in the LCL,
|
|
// therefore they are sent after all other
|
|
if MovedPaintMessageCount<10 then begin
|
|
inc(MovedPaintMessageCount);
|
|
if fMessageQueue.HasNonPaintMessages then begin
|
|
// there are non paint messages -> move paint message to the end
|
|
fMessageQueue.MoveToLast(FMessageQueue.First);
|
|
continue;
|
|
end else begin
|
|
// there are only paint messages left in the queue
|
|
// -> check other queues
|
|
if PendingGtkMessagesExists then break;
|
|
end;
|
|
end else begin
|
|
// handle this paint message now
|
|
MovedPaintMessageCount:=0;
|
|
end;
|
|
end;
|
|
|
|
//DebugLn(['TGtkWidgetSet.AppProcessMessages SendMessage: ',DbgSName(GetLCLObject(Pointer(vlItem.Msg^.hwnd)))]);
|
|
vlMsg:=fMessageQueue.PopFirstMessage;
|
|
finally
|
|
fMessageQueue.UnLock;
|
|
end;
|
|
|
|
// Send message
|
|
try
|
|
with vlMsg^ do SendMessage(hWND, Message, WParam, LParam);
|
|
finally
|
|
Dispose(vlMsg);
|
|
end;
|
|
end;
|
|
|
|
// proceed until all messages are handled
|
|
until (not PendingGtkMessagesExists) or Application.Terminated;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.AppWaitMessage
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Passes execution control to the GTK engine till something happens
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.AppWaitMessage;
|
|
begin
|
|
WaitingForMessages:=true;
|
|
gtk_main_iteration_do(True);
|
|
WaitingForMessages:=false;
|
|
end;
|
|
|
|
|
|
procedure TGtkWidgetSet.FreeStockItems;
|
|
|
|
procedure DeleteAndNilObject(var h: HGDIOBJ);
|
|
begin
|
|
if h <> 0 then
|
|
begin
|
|
PGdiObject(h)^.Shared := False;
|
|
PGdiObject(h)^.RefCount := 1;
|
|
end;
|
|
DeleteObject(h);
|
|
h := 0;
|
|
end;
|
|
|
|
begin
|
|
DeleteAndNilObject(FStockNullBrush);
|
|
DeleteAndNilObject(FStockBlackBrush);
|
|
DeleteAndNilObject(FStockLtGrayBrush);
|
|
DeleteAndNilObject(FStockGrayBrush);
|
|
DeleteAndNilObject(FStockDkGrayBrush);
|
|
DeleteAndNilObject(FStockWhiteBrush);
|
|
|
|
DeleteAndNilObject(FStockNullPen);
|
|
DeleteAndNilObject(FStockBlackPen);
|
|
DeleteAndNilObject(FStockWhitePen);
|
|
|
|
DeleteAndNilObject(FStockSystemFont);
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.InitSystemColors;
|
|
begin
|
|
// we need to request style and inside UpdateSysColorMap will be indirectly called
|
|
GetStyle(lgsButton);
|
|
GetStyle(lgsWindow);
|
|
GetStyle(lgsMenuBar);
|
|
GetStyle(lgsMenuitem);
|
|
GetStyle(lgsVerticalScrollbar);
|
|
GetStyle(lgsTooltip);
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.InitSystemBrushes;
|
|
var
|
|
i: integer;
|
|
LogBrush: TLogBrush;
|
|
begin
|
|
FillChar(LogBrush, SizeOf(TLogBrush), 0);
|
|
for i := Low(FSysColorBrushes) to High(FSysColorBrushes) do
|
|
begin
|
|
LogBrush.lbColor := GetSysColor(i);
|
|
FSysColorBrushes[i] := CreateBrushIndirect(LogBrush);
|
|
PGDIObject(FSysColorBrushes[i])^.Shared := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.FreeSystemBrushes;
|
|
|
|
procedure DeleteAndNilObject(var h: HGDIOBJ);
|
|
begin
|
|
if h <> 0 then
|
|
begin
|
|
PGdiObject(h)^.Shared := False;
|
|
PGdiObject(h)^.RefCount := 1;
|
|
end;
|
|
DeleteObject(h);
|
|
h := 0;
|
|
end;
|
|
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := Low(FSysColorBrushes) to High(FSysColorBrushes) do
|
|
DeleteAndNilObject(FSysColorBrushes[i]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.AppTerminate
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
*Note: Tells GTK Engine to halt and destroy
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.AppTerminate;
|
|
begin
|
|
FreeAllStyles;
|
|
// MG: using gtk_main_quit is not a clean way to close
|
|
//gtk_main_quit;
|
|
end;
|
|
|
|
function TGTKWidgetSet.GetAppActive: Boolean;
|
|
begin
|
|
Result := FAppActive;
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.SetAppActive(const AValue: Boolean);
|
|
begin
|
|
if AValue <> FAppActive then
|
|
begin
|
|
FAppActive := AValue;
|
|
if FAppActive then
|
|
begin
|
|
Application.IntfAppActivate;
|
|
AppRestoreStayOnTopFlags(False);
|
|
end else
|
|
begin
|
|
Application.IntfAppDeactivate;
|
|
AppRemoveStayOnTopFlags(False);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function gtkAppFocusTimer(Data: gPointer):gBoolean; cdecl;
|
|
// needed by app activate/deactivate
|
|
begin
|
|
Result := CallBackDefaultReturn;
|
|
if TGtkWidgetSet(WidgetSet).LastFocusIn = nil then
|
|
TGtkWidgetSet(WidgetSet).AppActive := False;
|
|
gtk_timeout_remove(TGtkWidgetSet(WidgetSet).FocusTimer);
|
|
TGtkWidgetSet(WidgetSet).FocusTimer := 0;
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.StartFocusTimer;
|
|
begin
|
|
FLastFocusIn := nil;
|
|
if FocusTimer <> 0 then
|
|
gtk_timeout_remove(TGtkWidgetSet(WidgetSet).FocusTimer);
|
|
FocusTimer := gtk_timeout_add(50, TGtkFunction(@gtkAppFocusTimer), nil);
|
|
end;
|
|
|
|
function TGTKWidgetSet.CreateThemeServices: TThemeServices;
|
|
begin
|
|
Result := TGtkThemeServices.Create;
|
|
end;
|
|
|
|
procedure TGtkWidgetSet.InitStockItems;
|
|
var
|
|
LogBrush: TLogBrush;
|
|
logPen : TLogPen;
|
|
begin
|
|
FillChar(LogBrush, SizeOf(TLogBrush), 0);
|
|
LogBrush.lbStyle := BS_NULL;
|
|
FStockNullBrush := CreateBrushIndirect(LogBrush);
|
|
PGDIObject(FStockNullBrush)^.Shared := True;
|
|
LogBrush.lbStyle := BS_SOLID;
|
|
LogBrush.lbColor := $000000;
|
|
FStockBlackBrush := CreateBrushIndirect(LogBrush);
|
|
PGDIObject(FStockBlackBrush)^.Shared := True;
|
|
LogBrush.lbColor := $C0C0C0;
|
|
FStockLtGrayBrush := CreateBrushIndirect(LogBrush);
|
|
PGDIObject(FStockLtGrayBrush)^.Shared := True;
|
|
LogBrush.lbColor := $808080;
|
|
FStockGrayBrush := CreateBrushIndirect(LogBrush);
|
|
PGDIObject(FStockGrayBrush)^.Shared := True;
|
|
LogBrush.lbColor := $404040;
|
|
FStockDkGrayBrush := CreateBrushIndirect(LogBrush);
|
|
PGDIObject(FStockDkGrayBrush)^.Shared := True;
|
|
LogBrush.lbColor := $FFFFFF;
|
|
FStockWhiteBrush := CreateBrushIndirect(LogBrush);
|
|
PGDIObject(FStockWhiteBrush)^.Shared := True;
|
|
|
|
LogPen.lopnStyle := PS_NULL;
|
|
LogPen.lopnWidth.X := 1;
|
|
LogPen.lopnColor := $FFFFFF;
|
|
FStockNullPen := CreatePenIndirect(LogPen);
|
|
PGDIObject(FStockNullPen)^.Shared := True;
|
|
LogPen.lopnStyle := PS_SOLID;
|
|
FStockWhitePen := CreatePenIndirect(LogPen);
|
|
PGDIObject(FStockWhitePen)^.Shared := True;
|
|
LogPen.lopnColor := $000000;
|
|
FStockBlackPen := CreatePenIndirect(LogPen);
|
|
PGDIObject(FStockBlackPen)^.Shared := True;
|
|
|
|
FStockSystemFont := 0;//Styles aren't initialized yet
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.AppInit
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
*Note: Initialize GTK engine
|
|
(is called by TApplication.Initialize which is typically after all
|
|
finalization sections)
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.AppInit(var ScreenInfo: TScreenInfo);
|
|
begin
|
|
InitKeyboardTables;
|
|
{ Compute pixels per inch variable }
|
|
ScreenInfo.PixelsPerInchX :=
|
|
RoundToInt(gdk_screen_width / (GetScreenWidthMM / 25.4));
|
|
ScreenInfo.PixelsPerInchY :=
|
|
RoundToInt(gdk_screen_height / (GetScreenHeightMM / 25.4));
|
|
ScreenInfo.ColorDepth := gdk_visual_get_system^.depth;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.AppMinimize
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Minimizes the application
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.AppMinimize;
|
|
var
|
|
i: Integer;
|
|
AForm: TCustomForm;
|
|
begin
|
|
//debugln('TGtkWidgetSet.AppMinimize A');
|
|
if Screen=nil then exit;
|
|
for i:=0 to Screen.CustomFormCount-1 do begin
|
|
AForm:=Screen.CustomForms[i];
|
|
//debugln('TGtkWidgetSet.AppMinimize B ',DbgSName(AForm),' AForm.Parent=',DbgSName(AForm.Parent),' AForm.HandleAllocated=',dbgs(AForm.HandleAllocated));
|
|
if (AForm.Parent=nil) and AForm.HandleAllocated
|
|
and AForm.Visible then begin
|
|
ShowWindow(AForm.Handle, SW_MINIMIZE);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.AppRestore;
|
|
begin
|
|
DebugLn(['TGTKWidgetSet.AppRestore TODO']);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.AppBringToFront
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Shows the application above all other non-topmost windows
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.AppBringToFront;
|
|
begin
|
|
// TODO: Implement me!
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGTKWidgetSet.AppSetTitle(const ATitle: string);
|
|
-------------------------------------------------------------------------------}
|
|
procedure TGTKWidgetSet.AppSetTitle(const ATitle: string);
|
|
begin
|
|
|
|
end;
|
|
|
|
function TGTKWidgetSet.LCLPlatform: TLCLPlatform;
|
|
begin
|
|
Result:= lpGtk;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.RecreateWnd
|
|
Params: Sender: TObject - the lcl wincontrol, that is to recreated
|
|
Returns: none
|
|
|
|
Destroys Handle and child Handles and recreates them.
|
|
-------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.RecreateWnd(Sender: TObject): Integer;
|
|
var
|
|
aWinControl, aParent: TWinControl;
|
|
begin
|
|
aWinControl := TWinControl(Sender);
|
|
aParent := aWinControl.Parent;
|
|
if aParent <> nil then
|
|
begin
|
|
// remove and insert the control
|
|
// this will destroy and recreate all child handles
|
|
aWinControl.Parent := nil;
|
|
aWinControl.Parent := aParent;
|
|
end;
|
|
DebugLn(['TGtkWidgetSet.RecreateWnd ',DbgSName(Sender)]);
|
|
ResizeChild(Sender,aWinControl.Left,aWinControl.Top,
|
|
aWinControl.Width,aWinControl.Height);
|
|
SetVisible(Sender, aWinControl.HandleObjectShouldBeVisible);
|
|
Result := 0;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: CreateTimer
|
|
Params: Interval:
|
|
TimerFunc: Callback
|
|
Returns: a GTK-timer id (use this ID to destroy timer)
|
|
|
|
This function will create a GTK timer object and associate a callback to it.
|
|
|
|
Design: A callback to the TTimer class is implemented.
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.CreateTimer(Interval: integer;
|
|
TimerProc: TWSTimerProc) : THandle;
|
|
var
|
|
TimerInfo: PGtkITimerinfo;
|
|
begin
|
|
if ((Interval < 1) or (not Assigned(TimerProc)))
|
|
then
|
|
Result := 0
|
|
else begin
|
|
New(TimerInfo);
|
|
FillByte(TimerInfo^,SizeOf(TGtkITimerinfo),0);
|
|
TimerInfo^.TimerFunc := TimerProc;
|
|
{$IFDEF VerboseTimer}
|
|
DebugLn(['TGtkWidgetSet.CreateTimer Interval=',dbgs(Interval)]);
|
|
{$ENDIF}
|
|
Result:= gtk_timeout_add(Interval, @gtkTimerCB, TimerInfo);
|
|
if Result = 0 then
|
|
Dispose(TimerInfo)
|
|
else begin
|
|
TimerInfo^.TimerFunc := TimerProc;
|
|
TimerInfo^.TimerHandle:=Result;
|
|
FTimerData.Add(TimerInfo);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: DestroyTimer
|
|
Params: TimerHandle
|
|
Returns:
|
|
|
|
WARNING: There seems to be a bug in gtk-1.2.x which breaks gtk_timeout_remove
|
|
thus we can't dispose PGtkITimerinfo here (s.a. gtkTimerCB).
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.DestroyTimer(TimerHandle: THandle) : boolean;
|
|
var
|
|
n : integer;
|
|
TimerInfo : PGtkITimerinfo;
|
|
begin
|
|
//DebugLn('Trace:removing timer!!!');
|
|
n := FTimerData.Count;
|
|
while (n > 0) do begin
|
|
dec (n);
|
|
TimerInfo := PGtkITimerinfo(FTimerData.Items[n]);
|
|
if (TimerInfo^.TimerHandle=guint(TimerHandle)) then
|
|
begin
|
|
{$IFDEF VerboseTimer}
|
|
DebugLn(['TGtkWidgetSet.DestroyTimer TimerInfo=',DbgS(TimerInfo),' TimerHandle=',TimerInfo^.TimerHandle]);
|
|
{$ENDIF}
|
|
gtk_timeout_remove(TimerInfo^.TimerHandle);
|
|
FTimerData.Delete(n);
|
|
Dispose(TimerInfo);
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TGtkWidgetSet.InternalGetDIBits(DC: HDC; Bitmap: HBitmap;
|
|
StartScan, NumScans: UINT;
|
|
BitSize : Longint; Bits: Pointer;
|
|
var BitInfo: BitmapInfo; Usage: UINT; DIB : Boolean): Integer;
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.InternalGetDIBits(DC: HDC; Bitmap: HBitmap;
|
|
StartScan, NumScans: UINT; BitSize : Longint; Bits: Pointer;
|
|
var BitInfo: BitmapInfo; Usage: UINT; DIB : Boolean): Integer;
|
|
const
|
|
PadLine : array[0..12] of Byte = (0,0,0,0,0,0,0,0,0,0,1,0,0);
|
|
TempBuffer : array[0..2] of Byte = (0,0,0);
|
|
|
|
var
|
|
GdiObject: PGDIObject absolute Bitmap;
|
|
|
|
Source: PGDKPixbuf;
|
|
rowstride, PixelPos: Longint;
|
|
Pixels: PByte;
|
|
FDIB: TDIBSection;
|
|
X, Y: Longint;
|
|
PadSize, Pos, BytesPerPixel: Longint;
|
|
Buf16Bit: word;
|
|
|
|
procedure DataSourceInitialize(Bitmap : PGDIObject; Width : Longint);
|
|
begin
|
|
Source := nil;
|
|
|
|
case Bitmap^.GDIBitmapType of
|
|
gbBitmap:
|
|
if Bitmap^.GDIBitmapObject <> nil
|
|
then begin
|
|
{$ifdef VerboseGdkPixbuf} debugln('DataSourceInitialize A1');{$endif}
|
|
|
|
Source := CreatePixbufFromDrawable(Bitmap^.GDIBitmapObject, Bitmap^.Colormap, False, 0,StartScan,0,0,Width,StartScan + NumScans);
|
|
rowstride := gdk_pixbuf_get_rowstride(Source);
|
|
Pixels := PByte(gdk_pixbuf_get_pixels(Source));
|
|
|
|
{$ifdef VerboseGdkPixbuf} debugln('DataSourceInitialize A2');{$endif}
|
|
end;
|
|
gbPixmap:
|
|
if Bitmap^.GDIPixmapObject.Image <> nil
|
|
then begin
|
|
{$ifdef VerboseGdkPixbuf} debugln('DataSourceInitialize B1');{$endif}
|
|
|
|
Source := CreatePixbufFromDrawable(Bitmap^.GDIPixmapObject.Image, Bitmap^.Colormap, False, 0, StartScan, 0, 0, Width, StartScan + NumScans);
|
|
{$IFDEF VerboseGtkToDos}{$note TODO: Apply alpha based on mask when 32bit mode is added}{$ENDIF}
|
|
|
|
rowstride := gdk_pixbuf_get_rowstride(Source);
|
|
Pixels := PByte(gdk_pixbuf_get_pixels(Source));
|
|
|
|
{$ifdef VerboseGdkPixbuf} debugln('DataSourceInitialize B2');{$endif}
|
|
end;
|
|
gbPixbuf:
|
|
if Bitmap^.GDIPixbufObject <> nil
|
|
then begin
|
|
rowstride := gdk_pixbuf_get_rowstride(Bitmap^.GDIPixbufObject);
|
|
Pixels := PByte(gdk_pixbuf_get_pixels(Bitmap^.GDIPixbufObject));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function DataSourceGetGDIRGB(Bitmap : PGDIObject; X, Y : Longint) : TGDIRGB;
|
|
begin
|
|
if Bitmap <> nil then ; //Keep compiler happy..
|
|
|
|
PixelPos := rowstride*Y + X*3;
|
|
|
|
with Result do
|
|
begin
|
|
Red := Pixels[PixelPos + 0];
|
|
Green := Pixels[PixelPos + 1];
|
|
Blue := Pixels[PixelPos + 2];
|
|
end;
|
|
end;
|
|
|
|
procedure DataSourceFinalize;
|
|
begin
|
|
if Source <> nil
|
|
then gdk_pixbuf_unref(Source);
|
|
end;
|
|
|
|
procedure WriteData(Value : PByte; Size : Longint);
|
|
begin
|
|
System.Move(Value^, PByte(Bits)[Pos], Size);
|
|
Inc(Pos, Size);
|
|
end;
|
|
|
|
procedure WriteData(Value : Word);
|
|
begin
|
|
PByte(Bits)[Pos] := Lo(Value);
|
|
inc(Pos);
|
|
PByte(Bits)[Pos] := Hi(Value);
|
|
inc(Pos);
|
|
end;
|
|
|
|
begin
|
|
//DebugLn('trace:[TGtkWidgetSet.InternalGetDIBits]');
|
|
|
|
Result := 0;
|
|
if (DC=0) or (Usage=0) then ;
|
|
|
|
if not IsValidGDIObject(Bitmap)
|
|
then begin
|
|
DebugLn('WARNING: [TGtkWidgetSet.InternalGetDIBits] invalid Bitmap!');
|
|
Exit;
|
|
end;
|
|
|
|
if GdiObject^.GDIType <> gdiBitmap
|
|
then begin
|
|
DebugLn('WARNING: [TGtkWidgetSet.InternalGetDIBits] not a Bitmap!');
|
|
Exit;
|
|
end;
|
|
|
|
|
|
FillChar(FDIB, SizeOf(FDIB), 0);
|
|
GetObject(Bitmap, SizeOf(FDIB), @FDIB);
|
|
BitInfo.bmiHeader := FDIB.dsBmih;
|
|
|
|
with GdiObject^, BitInfo.bmiHeader do
|
|
begin
|
|
if not DIB
|
|
then begin
|
|
NumScans := biHeight;
|
|
StartScan := 0;
|
|
end;
|
|
BytesPerPixel := biBitCount div 8;
|
|
|
|
if BitSize <= 0 then
|
|
BitSize := longint(SizeOf(Byte))
|
|
*(longint(biSizeImage) div biHeight)
|
|
*longint(NumScans + StartScan);
|
|
if MemSize(Bits) < PtrInt(BitSize)
|
|
then begin
|
|
DebugLn('WARNING: [TGtkWidgetSet.InternalGetDIBits] not enough memory allocated for Bits!');
|
|
exit;
|
|
end;
|
|
|
|
// ToDo: other bitcounts
|
|
if (biBitCount<>24) and (biBitCount<>16)
|
|
then begin
|
|
DebugLn('WARNING: [TGtkWidgetSet.InternalGetDIBits] unsupported biBitCount=',dbgs(biBitCount));
|
|
exit;
|
|
end;
|
|
|
|
if NumScans = 0 then Exit;
|
|
|
|
Pos := 0;
|
|
PadSize := (Longint(biSizeImage) div biHeight) - biWidth * BytesPerPixel;
|
|
|
|
{$ifdef DebugGDK} BeginGDKErrorTrap; try{$ENDIF}
|
|
DataSourceInitialize(GdiObject, biWidth);
|
|
|
|
if DIB
|
|
then Y := NumScans - 1
|
|
else Y := 0;
|
|
|
|
case biBitCount of
|
|
24: repeat
|
|
for X := 0 to biwidth - 1 do
|
|
begin
|
|
with DataSourceGetGDIRGB(PGDIObject(Bitmap), X, Y) do
|
|
begin
|
|
TempBuffer[0] := Blue;
|
|
TempBuffer[1] := Green;
|
|
TempBuffer[2] := Red;
|
|
end;
|
|
WriteData(TempBuffer, BytesPerPixel);
|
|
end;
|
|
WriteData(PadLine, PadSize);
|
|
|
|
if DIB
|
|
then dec(y)
|
|
else inc(y);
|
|
until (Y < 0) or (y >= longint(NumScans));
|
|
|
|
16: repeat
|
|
for X := 0 to biwidth - 1 do
|
|
begin
|
|
with DataSourceGetGDIRGB(PGDIObject(Bitmap), X, Y) do
|
|
begin
|
|
Buf16Bit := (Blue shr 3) shl 11
|
|
+ (Green shr 2) shl 5
|
|
+ (Red shr 3);
|
|
end;
|
|
WriteData(Buf16Bit);
|
|
end;
|
|
WriteData(PadLine, PadSize);
|
|
|
|
if DIB
|
|
then dec(y)
|
|
else inc(y);
|
|
until (Y < 0) or (y >= longint(NumScans));
|
|
end;
|
|
end;
|
|
|
|
DataSourceFinalize;
|
|
|
|
|
|
{$ifdef DebugGDK}finally EndGDKErrorTrap; end;{$endif}
|
|
end;
|
|
|
|
function TGtkWidgetSet.RawImage_DescriptionFromDrawable(out ADesc: TRawImageDescription; ADrawable: PGdkDrawable; ACustomAlpha: Boolean): Boolean;
|
|
var
|
|
Visual: PGdkVisual;
|
|
Image: PGdkImage;
|
|
Width, Height, Depth: integer;
|
|
IsBitmap: Boolean;
|
|
begin
|
|
Visual := nil;
|
|
Width := 0;
|
|
Height := 0;
|
|
|
|
if ADrawable = nil
|
|
then begin
|
|
Visual := gdk_visual_get_system;
|
|
IsBitmap := False;
|
|
end
|
|
else begin
|
|
{$ifdef gtk1}
|
|
gdk_window_get_geometry(ADrawable, nil, nil, @Width, @Height, @Depth);
|
|
{$else}
|
|
gdk_drawable_get_size(ADrawable, @Width, @Height);
|
|
Depth := gdk_drawable_get_depth(ADrawable);
|
|
{$endif}
|
|
Visual := gdk_window_get_visual(ADrawable);
|
|
// pixmaps and bitmaps do not have a visual, but for pixmaps we need one
|
|
if Visual = nil
|
|
then Visual := gdk_visual_get_best_with_depth(Depth);
|
|
IsBitmap := Depth = 1;
|
|
end;
|
|
|
|
if (Visual = nil) and not IsBitmap // bitmaps don't have a visual
|
|
then begin
|
|
DebugLn('TGtkWidgetSet.RawImage_DescriptionFromDrawable: visual failed');
|
|
Exit(False);
|
|
end;
|
|
|
|
ADesc.Init;
|
|
ADesc.Width := cardinal(Width);
|
|
ADesc.Height := cardinal(Height);
|
|
ADesc.BitOrder := riboBitsInOrder;
|
|
|
|
if ACustomAlpha
|
|
then begin
|
|
// always give pixbuf description for alpha images
|
|
ADesc.Format:=ricfRGBA;
|
|
ADesc.Depth := 32;
|
|
ADesc.BitsPerPixel := 32;
|
|
ADesc.LineEnd := rileDWordBoundary;
|
|
ADesc.ByteOrder := riboLSBFirst;
|
|
|
|
ADesc.RedPrec := 8;
|
|
ADesc.RedShift := 0;
|
|
ADesc.GreenPrec := 8;
|
|
ADesc.GreenShift := 8;
|
|
ADesc.BluePrec := 8;
|
|
ADesc.BlueShift := 16;
|
|
ADesc.AlphaPrec := 8;
|
|
ADesc.AlphaShift := 24;
|
|
|
|
ADesc.MaskBitsPerPixel := 1;
|
|
ADesc.MaskShift := 0;
|
|
ADesc.MaskLineEnd := rileByteBoundary;
|
|
ADesc.MaskBitOrder := riboBitsInOrder;
|
|
|
|
Exit(True);
|
|
end;
|
|
|
|
// Format
|
|
if IsBitmap
|
|
then begin
|
|
ADesc.Format := ricfGray;
|
|
end
|
|
else begin
|
|
case Visual^.thetype of
|
|
GDK_VISUAL_STATIC_GRAY: ADesc.Format:=ricfGray;
|
|
GDK_VISUAL_GRAYSCALE: ADesc.Format:=ricfGray;
|
|
GDK_VISUAL_STATIC_COLOR: ADesc.Format:=ricfGray; // this is not really gray, but an index in a color map, but colormaps are not supported yet, so use gray
|
|
GDK_VISUAL_PSEUDO_COLOR: ADesc.Format:=ricfGray;
|
|
GDK_VISUAL_TRUE_COLOR: ADesc.Format:=ricfRGBA;
|
|
GDK_VISUAL_DIRECT_COLOR: ADesc.Format:=ricfRGBA;
|
|
else
|
|
DebugLn('TGtkWidgetSet.GetWindowRawImageDescription unknown Visual type ',
|
|
dbgs(Integer(Visual^.thetype)));
|
|
Exit(False);
|
|
end;
|
|
end;
|
|
|
|
// Palette
|
|
if not IsBitmap
|
|
and (Visual^.thetype in [GDK_VISUAL_GRAYSCALE,
|
|
GDK_VISUAL_STATIC_COLOR,GDK_VISUAL_PSEUDO_COLOR])
|
|
then begin
|
|
// has palette
|
|
// ToDo
|
|
ADesc.PaletteColorCount:=0;
|
|
end;
|
|
|
|
// Depth
|
|
if IsBitmap
|
|
then ADesc.Depth := 1
|
|
else ADesc.Depth := Visual^.Depth;
|
|
|
|
if IsBitmap or (Visual^.byte_order = GDK_MSB_FIRST)
|
|
then ADesc.ByteOrder := riboMSBFirst
|
|
else ADesc.ByteOrder := riboLSBFirst;
|
|
|
|
ADesc.LineOrder := riloTopToBottom;
|
|
|
|
case ADesc.Depth of
|
|
0..8: ADesc.BitsPerPixel := ADesc.Depth;
|
|
9..16: ADesc.BitsPerPixel := 16;
|
|
17..32: ADesc.BitsPerPixel := 32;
|
|
else
|
|
ADesc.BitsPerPixel := 64;
|
|
end;
|
|
|
|
if IsBitmap
|
|
then begin
|
|
ADesc.LineEnd := rileByteBoundary;
|
|
ADesc.RedPrec := 1;
|
|
ADesc.RedShift := 0;
|
|
end
|
|
else begin
|
|
// Try retrieving the lineend
|
|
Image := gdk_image_new(GDK_IMAGE_NORMAL, Visual, 1, 1);
|
|
if Image = nil
|
|
then begin
|
|
DebugLn('TGtkWidgetSet.GetWindowRawImageDescription testimage creation failed ');
|
|
Exit(False);
|
|
end;
|
|
try
|
|
// the minimum alignment we can detect is bpp
|
|
// that is no problem since a line consists of n x bytesperpixel bytes
|
|
case Image^.bpl of
|
|
1: ADesc.LineEnd := rileByteBoundary;
|
|
2: ADesc.LineEnd := rileWordBoundary;
|
|
4: ADesc.LineEnd := rileDWordBoundary;
|
|
8: ADesc.LineEnd := rileQWordBoundary;
|
|
else
|
|
DebugLn('TGtkWidgetSet.GetWindowRawImageDescription Unknown line end: %d', [Image^.bpl]);
|
|
Exit(False);
|
|
end;
|
|
finally
|
|
gdk_image_destroy(Image);
|
|
Image := nil;
|
|
end;
|
|
|
|
ADesc.RedPrec := Visual^.red_prec;
|
|
ADesc.RedShift := Visual^.red_shift;
|
|
ADesc.GreenPrec := Visual^.green_prec;
|
|
ADesc.GreenShift := Visual^.green_shift;
|
|
ADesc.BluePrec := Visual^.blue_prec;
|
|
ADesc.BlueShift := Visual^.blue_shift;
|
|
|
|
ADesc.MaskBitsPerPixel := 1;
|
|
ADesc.MaskShift := 0;
|
|
ADesc.MaskLineEnd := rileByteBoundary;
|
|
ADesc.MaskBitOrder := riboBitsInOrder;
|
|
end;
|
|
|
|
{$IFDEF VerboseRawImage}
|
|
DebugLn('TGtkWidgetSet.GetWindowRawImageDescription A ',RawImageDescriptionAsString(Desc));
|
|
{$ENDIF}
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function TGtkWidgetSet.RawImage_DescriptionFromPixbuf(out ADesc: TRawImageDescription; APixbuf: PGdkPixbuf): boolean;
|
|
var
|
|
Width, Height, Depth: integer;
|
|
HasAlpha: Boolean;
|
|
begin
|
|
Width := 0;
|
|
Height := 0;
|
|
|
|
if APixbuf = nil
|
|
then begin
|
|
HasAlpha := False;
|
|
Depth := 24;
|
|
end
|
|
else begin
|
|
Width := gdk_pixbuf_get_width(APixbuf);
|
|
Height := gdk_pixbuf_get_height(APixbuf);
|
|
Depth := gdk_pixbuf_get_bits_per_sample(APixbuf) * gdk_pixbuf_get_n_channels(APixbuf);
|
|
HasAlpha := gdk_pixbuf_get_has_alpha(APixbuf);
|
|
end;
|
|
|
|
ADesc.Init;
|
|
ADesc.Width := cardinal(Width);
|
|
ADesc.Height := cardinal(Height);
|
|
ADesc.BitOrder := riboBitsInOrder;
|
|
|
|
if HasAlpha
|
|
then begin
|
|
// always give pixbuf description for alpha images
|
|
ADesc.Format:=ricfRGBA;
|
|
ADesc.Depth := 32;
|
|
ADesc.BitsPerPixel := 32;
|
|
ADesc.LineEnd := rileDWordBoundary;
|
|
ADesc.ByteOrder := riboLSBFirst;
|
|
|
|
ADesc.RedPrec := 8;
|
|
ADesc.RedShift := 0;
|
|
ADesc.GreenPrec := 8;
|
|
ADesc.GreenShift := 8;
|
|
ADesc.BluePrec := 8;
|
|
ADesc.BlueShift := 16;
|
|
ADesc.AlphaPrec := 8;
|
|
ADesc.AlphaShift := 24;
|
|
|
|
ADesc.MaskBitsPerPixel := 0;
|
|
ADesc.MaskShift := 0;
|
|
ADesc.MaskLineEnd := rileByteBoundary;
|
|
ADesc.MaskBitOrder := riboBitsInOrder;
|
|
end
|
|
else
|
|
begin
|
|
ADesc.Depth := Depth;
|
|
ADesc.BitsPerPixel := 32;
|
|
ADesc.LineEnd := rileDWordBoundary;
|
|
ADesc.ByteOrder := riboLSBFirst;
|
|
ADesc.MaskBitsPerPixel := 0;
|
|
ADesc.MaskShift := 0;
|
|
ADesc.MaskLineEnd := rileByteBoundary;
|
|
ADesc.MaskBitOrder := riboBitsInOrder;
|
|
|
|
ADesc.RedPrec := 8;
|
|
ADesc.RedShift := 0;
|
|
ADesc.GreenPrec := 8;
|
|
ADesc.GreenShift := 8;
|
|
ADesc.BluePrec := 8;
|
|
ADesc.BlueShift := 16;
|
|
ADesc.AlphaPrec := 0;
|
|
ADesc.AlphaShift := 24;
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function TGtkWidgetSet.RawImage_FromDrawable(out ARawImage: TRawImage; ADrawable, AAlpha: PGdkDrawable; ARect: PRect): boolean;
|
|
var
|
|
ADesc: TRawImageDescription absolute ARawImage.Description;
|
|
|
|
function GetFromPixbuf(const ARect: TRect): Boolean;
|
|
var
|
|
Pixbuf: PGdkPixbuf;
|
|
pixels: pguchar;
|
|
begin
|
|
// create pixbuf with alpha channel first
|
|
Pixbuf := CreatePixbufFromDrawable(ADrawable, nil, True, ARect.Left, ARect.Top, 0, 0, ADesc.Width, ADesc.Height);
|
|
try
|
|
pixels := gdk_pixbuf_get_pixels(Pixbuf);
|
|
|
|
ARawImage.DataSize := PtrUInt(gdk_pixbuf_get_rowstride(Pixbuf)) * PtrUInt(ADesc.Height);
|
|
ReAllocMem(ARawImage.Data, ARawImage.DataSize);
|
|
if ARawImage.DataSize > 0 then
|
|
System.Move(pixels^, ARawImage.Data^, ARawImage.DataSize);
|
|
|
|
//DbgDumpPixmap(ADrawable, 'RawImage_FromDrawable - image');
|
|
//DbgDumpBitmap(AAlpha, 'RawImage_FromDrawable - alpha');
|
|
//DbgDumpPixbuf(Pixbuf, 'RawImage_FromDrawable - pixbuf');
|
|
finally
|
|
gdk_pixbuf_unref(Pixbuf);
|
|
end;
|
|
|
|
Result := RawImage_SetAlpha(ARawImage, AAlpha, @ARect);
|
|
end;
|
|
|
|
function GetFromImage(const ARect: TRect): Boolean;
|
|
var
|
|
Image: PGdkImage;
|
|
begin
|
|
Image := gdk_image_get(ADrawable, ARect.Left, ARect.Top, ADesc.Width, ADesc.Height);
|
|
if Image = nil
|
|
then begin
|
|
DebugLn('WARNING: TGtkWidgetSet.RawImage_FromDrawable: gdk_image_get failed');
|
|
exit(False);
|
|
end;
|
|
|
|
try
|
|
{$ifdef RawimageConsistencyCheks}
|
|
// consistency checks
|
|
if Description.Depth <> Image^.Depth then
|
|
RaiseGDBException('ARawImage.Description.Depth<>Image^.Depth '+IntToStr(ADesc.Depth)+'<>'+IntToStr(Image^.Depth));
|
|
if Description.BitsPerPixel <> GetPGdkImageBitsPerPixel(Image) then
|
|
RaiseGDBException('NewRawImage.Description.BitsPerPixel<>AnImage^.bpp');
|
|
{$endif}
|
|
|
|
ARawImage.DataSize := PtrUInt(Image^.bpl) * PtrUInt(Image^.Height);
|
|
{$IFDEF VerboseRawImage}
|
|
DebugLn('TGtkWidgetSet.RawImage_FromDrawable: G Width=',dbgs(Image^.Width),' Height=',dbgs(Image^.Height),
|
|
' BitsPerPixel=',dbgs(ADesc.BitsPerPixel),' bpl=',dbgs(Image^.bpl));
|
|
{$ENDIF}
|
|
|
|
// copy data
|
|
ADesc.Width := Image^.Width;
|
|
ADesc.Height := Image^.Height;
|
|
ReAllocMem(ARawImage.Data, ARawImage.DataSize);
|
|
if ARawImage.DataSize > 0
|
|
then begin
|
|
System.Move(Image^.Mem^, ARawImage.Data^, ARawImage.DataSize);
|
|
if Image^.Depth = 1
|
|
then CheckGdkImageBitOrder(Image, ARawImage.Data, ARawImage.DataSize);
|
|
end;
|
|
|
|
{$IFDEF VerboseRawImage}
|
|
DebugLn('TGtkWidgetSet.RawImage_FromDrawable: H ',
|
|
' Width=',dbgs(ADesc.Width),
|
|
' Height=',dbgs(ADesc.Height),
|
|
' Depth=',dbgs(ADesc.Depth),
|
|
' DataSize=',dbgs(ARawImage.DataSize));
|
|
{$ENDIF}
|
|
finally
|
|
gdk_image_destroy(Image);
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
var
|
|
R, R1: TRect;
|
|
UseAlpha: Boolean;
|
|
begin
|
|
Result := False;
|
|
if ADrawable = nil then
|
|
RaiseGDBException('TGtkWidgetSet.RawImage_FromDrawable');
|
|
|
|
ARawImage.Init;
|
|
|
|
UseAlpha := AAlpha <> nil;
|
|
|
|
// get raw image description
|
|
if not RawImage_DescriptionFromDrawable(ADesc, ADrawable, UseAlpha)
|
|
then begin
|
|
DebugLn('WARNING: TGtkWidgetSet.RawImage_FromDrawable: RawImage_DescriptionFromDrawable failed ');
|
|
Exit;
|
|
end;
|
|
|
|
R := Rect(0, 0, ADesc.Width, ADesc.Height);
|
|
if ARect <> nil
|
|
then begin
|
|
// get intersection
|
|
IntersectRect(R1, ARect^, R);
|
|
R := R1;
|
|
ADesc.Width := R.Right - R.Left;
|
|
ADesc.Height := R.Bottom - R.Top;
|
|
end;
|
|
|
|
{$IFDEF VerboseRawImage}
|
|
DebugLn('TGtkWidgetSet.RawImage_FromDrawable get image ',
|
|
dbgs(R.Left),',',dbgs(R.Top),',',dbgs(R.Right),',',dbgs(R.Bottom),
|
|
' GDKWindow=',DbgS(ADrawable));
|
|
{$ENDIF}
|
|
if (ADesc.Width <= 0) or (ADesc.Height <= 0)
|
|
then begin
|
|
//DebugLn('WARNING: TGtkWidgetSet.GetRawImageFromGdkWindow Intersection empty');
|
|
exit;
|
|
end;
|
|
|
|
if UseAlpha
|
|
then Result := GetFromPixbuf(R)
|
|
else Result := GetFromImage(R);
|
|
end;
|
|
|
|
function TGTKWidgetSet.RawImage_FromPixbuf(out ARawImage: TRawImage;
|
|
APixbuf: PGdkPixbuf; ARect: PRect): boolean;
|
|
var
|
|
ADesc: TRawImageDescription absolute ARawImage.Description;
|
|
Pixbuf: PGdkPixbuf;
|
|
pixels: pguchar;
|
|
Dest: PByte;
|
|
R, R1: TRect;
|
|
i: Integer;
|
|
SourceStride, DestStride: PtrUInt;
|
|
begin
|
|
Result := False;
|
|
if APixbuf = nil then
|
|
RaiseGDBException('TGtkWidgetSet.RawImage_FromPixbuf');
|
|
|
|
//DbgDumpPixbuf(APixbuf);
|
|
|
|
ARawImage.Init;
|
|
|
|
// get raw image description
|
|
if not RawImage_DescriptionFromPixbuf(ADesc, APixbuf)
|
|
then begin
|
|
DebugLn('WARNING: TGtkWidgetSet.RawImage_FromPixbuf: RawImage_DescriptionFromPixbuf failed ');
|
|
Exit;
|
|
end;
|
|
|
|
R := Rect(0, 0, ADesc.Width, ADesc.Height);
|
|
if ARect <> nil
|
|
then begin
|
|
// get intersection
|
|
IntersectRect(R1, ARect^, R);
|
|
R := R1;
|
|
ADesc.Width := R.Right - R.Left;
|
|
ADesc.Height := R.Bottom - R.Top;
|
|
end;
|
|
|
|
if (ADesc.Width <= 0) or (ADesc.Height <= 0)
|
|
then begin
|
|
exit;
|
|
end;
|
|
|
|
Pixbuf := gdk_pixbuf_new_subpixbuf(APixbuf, R.Left, R.Top, ADesc.Width, ADesc.Height);
|
|
try
|
|
pixels := gdk_pixbuf_get_pixels(Pixbuf);
|
|
SourceStride := PtrUInt(gdk_pixbuf_get_rowstride(Pixbuf));
|
|
DestStride := ADesc.BytesPerLine;
|
|
ARawImage.DataSize := DestStride * PtrUInt(ADesc.Height);
|
|
ReAllocMem(ARawImage.Data, ARawImage.DataSize);
|
|
if ARawImage.DataSize > 0 then
|
|
if SourceStride = DestStride then
|
|
System.Move(pixels^, ARawImage.Data^, ARawImage.DataSize)
|
|
else begin
|
|
{ Extra padding bytes - need to copy by line }
|
|
Dest := ARawImage.Data;
|
|
for i := 0 to ADesc.Height-1 do begin
|
|
System.Move(pixels^, Dest^, ADesc.BytesPerLine);
|
|
Inc(pixels, SourceStride);
|
|
Inc(Dest, DestStride);
|
|
end;
|
|
end;
|
|
finally
|
|
gdk_pixbuf_unref(Pixbuf);
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function TGTKWidgetSet.RawImage_SetAlpha(var ARawImage: TRawImage; AAlpha: PGdkPixmap; ARect: PRect): boolean;
|
|
// ARect must have the same dimension as the rawimage
|
|
var
|
|
ADesc: TRawImageDescription absolute ARawImage.Description;
|
|
|
|
procedure SetAlpha_32_1(AImage: PGdkImage; AWidth, AHeight: Cardinal);
|
|
var
|
|
SrcPtr, DstPtr, SrcLinePtr, DstLinePtr: PByte;
|
|
DstPtr32: PDWord absolute DstPtr;
|
|
SrcBytesPerLine: Integer;
|
|
DstBytesPerLine: Integer;
|
|
SrcBit, SrcStartBit, ShiftInc: ShortInt;
|
|
DstMask: DWord;
|
|
DstSet: DWord;
|
|
X, Y: Cardinal;
|
|
{$ifdef hasx}
|
|
XImage: PXimage;
|
|
{$endif}
|
|
begin
|
|
SrcLinePtr := AImage^.mem;
|
|
SrcBytesPerLine := AImage^.bpl;
|
|
DstLinePtr := ARawImage.Data;
|
|
DstBytesPerLine := ARawImage.Description.BytesPerLine;
|
|
|
|
if ADesc.ByteOrder = DefaultByteOrder
|
|
then DstSet := (not ($FFFFFFFF shl ADesc.AlphaPrec)) shl ADesc.AlphaShift
|
|
else DstSet := (not ($FFFFFFFF shr ADesc.AlphaPrec)) shr ADesc.AlphaShift;
|
|
DstMask := not DstSet;
|
|
|
|
// bit order for X11 can be normal or reversed order, win32 and direct_fb
|
|
// is constant in reversed order
|
|
SrcStartBit := 7;
|
|
ShiftInc := -1;
|
|
{$ifdef HasX}
|
|
XImage := gdk_x11_image_get_ximage(AImage);
|
|
if XImage^.bitmap_bit_order = LSBFirst
|
|
then begin
|
|
SrcStartBit := 0;
|
|
ShiftInc := 1;
|
|
end;
|
|
{$endif}
|
|
|
|
for Y := 0 to AHeight - 1 do
|
|
begin
|
|
SrcBit := SrcStartBit;
|
|
SrcPtr := SrcLinePtr;
|
|
DstPtr := DstLinePtr;
|
|
for x := 0 to AWidth - 1 do
|
|
begin
|
|
if SrcPtr^ and (1 shl SrcBit) = 0
|
|
then DstPtr32^ := DstPtr32^ and DstMask
|
|
else DstPtr32^ := (DstPtr32^ and DstMask) or DstSet;
|
|
Inc(DstPtr32);
|
|
SrcBit := SrcBit + ShiftInc;
|
|
if SrcBit and $F8 <> 0
|
|
then begin
|
|
SrcBit := SrcBit and 7;
|
|
Inc(SrcPtr);
|
|
end;
|
|
end;
|
|
Inc(SrcLinePtr, SrcBytesPerLine);
|
|
Inc(DstLinePtr, DstBytesPerLine);
|
|
end;
|
|
end;
|
|
|
|
procedure SetAlpha_32_8(AImage: PGdkImage; AWidth, AHeight: Cardinal);
|
|
var
|
|
SrcPtr, DstPtr, SrcLinePtr, DstLinePtr: PByte;
|
|
DstPtr32: PDWord absolute DstPtr;
|
|
SrcBytesPerLine: Integer;
|
|
DstBytesPerLine: Integer;
|
|
DstMask: DWord;
|
|
DstShift: Byte;
|
|
X, Y: Cardinal;
|
|
begin
|
|
SrcLinePtr := AImage^.mem;
|
|
SrcBytesPerLine := AImage^.bpl;
|
|
DstLinePtr := ARawImage.Data;
|
|
DstBytesPerLine := ARawImage.Description.BytesPerLine;
|
|
|
|
DstMask := not (((1 shl ADesc.AlphaPrec) - 1) shl ADesc.AlphaShift);
|
|
DstShift := ADesc.AlphaShift;
|
|
|
|
for Y := 0 to AHeight - 1 do
|
|
begin
|
|
SrcPtr := SrcLinePtr;
|
|
DstPtr := DstLinePtr;
|
|
for x := 0 to AWidth - 1 do
|
|
begin
|
|
DstPtr32^ := (DstPtr32^ and DstMask) or (Cardinal(SrcPtr^) shl DstShift);
|
|
Inc(DstPtr32);
|
|
Inc(SrcPtr);
|
|
end;
|
|
Inc(SrcLinePtr, SrcBytesPerLine);
|
|
Inc(DstLinePtr, DstBytesPerLine);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
Width, Height, H, W, D: cardinal;
|
|
Image: PGdkImage;
|
|
R: TRect;
|
|
begin
|
|
Result := False;
|
|
|
|
if ARawImage.Data = nil
|
|
then begin
|
|
{$ifdef RawimageConsistencyChecks}
|
|
RaiseGDBException('TGTKWidgetSet.RawImage_SetAlpha RawImage.Data = nil');
|
|
{$else}
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_SetAlpha RawImage.Data = nil');
|
|
{$endif}
|
|
Exit;
|
|
end;
|
|
|
|
if ADesc.AlphaPrec = 0
|
|
then begin
|
|
{$ifdef RawimageConsistencyChecks}
|
|
RaiseGDBException('TGTKWidgetSet.RawImage_SetAlpha RawImage.Description.AlphaPrec = 0');
|
|
{$else}
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_SetAlpha No alpha channel defined');
|
|
{$endif}
|
|
Exit;
|
|
end;
|
|
|
|
if AAlpha = nil
|
|
then begin
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_SetAlpha Alpha = nil');
|
|
Exit;
|
|
end;
|
|
|
|
{$ifdef gtk1}
|
|
gdk_window_get_geometry(AAlpha, nil, nil, @W, @H, @D);
|
|
{$else}
|
|
gdk_drawable_get_size(AAlpha, @W, @H);
|
|
D := gdk_drawable_get_depth(AAlpha);
|
|
{$endif}
|
|
if (D <> 1) and (D <> 8)
|
|
then begin
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_SetAlpha: Only a Depth of 1 or 8 is supported. (depth=%d)', [D]);
|
|
Exit;
|
|
end;
|
|
|
|
if ARect = nil
|
|
then R := Rect(0, 0, ADesc.Width, ADesc.Height)
|
|
else R := ARect^;
|
|
|
|
if (longint(W) < R.Right) or (longint(H) < R.Bottom)
|
|
then begin
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_SetAlpha: Rect(%d,%d %d,%d) outside alpha pixmap(0,0 %d,%d)', [R.Left, R.Top, R.Right, R.Bottom, W, H]);
|
|
Exit;
|
|
end;
|
|
|
|
Width := R.Right - R.Left;
|
|
Height := R.Bottom - R.Top;
|
|
|
|
if Width <> ADesc.Width
|
|
then begin
|
|
{$ifdef RawimageConsistencyChecks}
|
|
RaiseGDBException('TGTKWidgetSet.RawImage_SetAlpha: Width <> RawImage.Description.Width');
|
|
{$else}
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_SetAlpha: Width(=%d) <> RawImage.Description.Width(=%d)', [Width, ADesc.Width]);
|
|
{$endif}
|
|
Exit;
|
|
end;
|
|
|
|
if Height <> ADesc.Height
|
|
then begin
|
|
{$ifdef RawimageConsistencyChecks}
|
|
RaiseGDBException('TGTKWidgetSet.RawImage_SetAlpha: Height <> RawImage.Description.Height');
|
|
{$else}
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_SetAlpha: Height(=%d) <> RawImage.Description.Height(=%d)', [Height, ADesc.Height]);
|
|
{$endif}
|
|
Exit;
|
|
end;
|
|
|
|
// get gdk_image from gdkbitmap
|
|
Image := gdk_image_get(AAlpha, R.Left, R.Top, Width, Height);
|
|
if Image = nil
|
|
then begin
|
|
DebugLn('WARNING: TGtkWidgetSet.RawImage_SetAlpha: gdk_image_get failed');
|
|
Exit;
|
|
end;
|
|
|
|
try
|
|
case ADesc.BitsPerPixel of
|
|
32: begin
|
|
if D = 1
|
|
then SetAlpha_32_1(Image, Width, Height)
|
|
else SetAlpha_32_8(Image, Width, Height);
|
|
end;
|
|
else
|
|
DebugLn('WARNING: TGtkWidgetSet.RawImage_SetAlpha: RawImage.Description.BitsPerPixel=%d not supported', [ADesc.BitsPerPixel]);
|
|
Exit;
|
|
end;
|
|
|
|
finally
|
|
gdk_image_destroy(Image);
|
|
end;
|
|
|
|
Result:=true;
|
|
end;
|
|
|
|
function TGTKWidgetSet.RawImage_AddMask(var ARawImage: TRawImage; AMask: PGdkBitmap; ARect: PRect): boolean;
|
|
// ARect must have the same dimension as the rawimage
|
|
|
|
var
|
|
ADesc: TRawImageDescription absolute ARawImage.Description;
|
|
Left, Top, Width, Height, H: longint;
|
|
Image: PGdkImage;
|
|
BytesPerLine: Integer;
|
|
SrcPtr, DstPtr: PByte;
|
|
begin
|
|
Result := False;
|
|
|
|
if ARawImage.Mask <> nil
|
|
then begin
|
|
{$ifdef RawimageConsistencyChecks}
|
|
RaiseGDBException('TGTKWidgetSet.RawImage_AddMask RawImage.Mask <> nil');
|
|
{$else}
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_AddMask RawImage.Mask <> nil');
|
|
{$endif}
|
|
Exit;
|
|
end;
|
|
|
|
if AMask = nil
|
|
then begin
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_AddMask AMask = nil');
|
|
Exit;
|
|
end;
|
|
|
|
if ARect = nil
|
|
then begin
|
|
Left := 0;
|
|
Top := 0;
|
|
Width := ADesc.Width;
|
|
Height := ADesc.Height;
|
|
end
|
|
else begin
|
|
Left := ARect^.Left;
|
|
Top := ARect^.Top;
|
|
Width := Min(ADesc.Width, ARect^.Right - ARect^.Left);
|
|
Height := Min(ADesc.Height, ARect^.Bottom - ARect^.Top);
|
|
end;
|
|
|
|
if cardinal(Width) <> ADesc.Width
|
|
then begin
|
|
{$ifdef RawimageConsistencyChecks}
|
|
RaiseGDBException('TGTKWidgetSet.RawImage_AddMask: Width <> RawImage.Description.Width');
|
|
{$else}
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_AddMask: Width(=%d) <> RawImage.Description.Width(=%d)', [Width, ADesc.Width]);
|
|
{$endif}
|
|
Exit;
|
|
end;
|
|
|
|
if cardinal(Height) <> ADesc.Height
|
|
then begin
|
|
{$ifdef RawimageConsistencyChecks}
|
|
RaiseGDBException('TGTKWidgetSet.RawImage_AddMask: Height <> RawImage.Description.Height');
|
|
{$else}
|
|
DebugLn('WARNING: TGTKWidgetSet.RawImage_AddMask: Height(=%d) <> RawImage.Description.Height(=%d)', [Height, ADesc.Height]);
|
|
{$endif}
|
|
Exit;
|
|
end;
|
|
|
|
// get gdk_image from gdkbitmap
|
|
Image := gdk_image_get(AMask, Left, Top, Width, Height);
|
|
if Image = nil
|
|
then begin
|
|
DebugLn('WARNING: TGtkWidgetSet.RawImage_AddMask: gdk_image_get failed');
|
|
Exit;
|
|
end;
|
|
|
|
try
|
|
{$IFDEF VerboseRawImage}
|
|
DebugLn('TGtkWidgetSet.RawImage_AddMask: A BytesPerLine=',dbgs(Image^.bpl),
|
|
' theType=',dbgs({$IFDEF Gtk1}Image^.thetype{$ELSE}ord(Image^._type){$ENDIF}),
|
|
' depth=',dbgs(Image^.depth),' AnImage^.bpp=',dbgs(Image^.bpp));
|
|
DebugLn('RawImage=', ARawImage.Description.AsString);
|
|
{$ENDIF}
|
|
|
|
// See also GetWindowRawImageDescription
|
|
ADesc.MaskBitsPerPixel := GetGdkImageBitsPerPixel(Image);
|
|
ADesc.MaskLineEnd := rileByteBoundary;// gdk_bitmap_create_from_data expects rileByteBoundary
|
|
BytesPerLine := GetBytesPerLine(ADesc.Width, ADesc.MaskBitsPerPixel, ADesc.MaskLineEnd);
|
|
ARawImage.MaskSize := PtrUInt(BytesPerLine) * PtrUInt(Height);
|
|
|
|
ReAllocMem(ARawImage.Mask, ARawImage.MaskSize);
|
|
if ARawImage.MaskSize > 0
|
|
then begin
|
|
// copy data
|
|
if BytesPerLine = Image^.bpl
|
|
then begin
|
|
// we can copy all in one go
|
|
System.Move(Image^.Mem^, ARawImage.Mask^, ARawImage.MaskSize);
|
|
end
|
|
else begin
|
|
// copy line by line
|
|
SrcPtr := Image^.Mem;
|
|
DstPtr := ARawImage.Mask;
|
|
H := Height;
|
|
while H > 0 do
|
|
begin
|
|
System.Move(SrcPtr^, DstPtr^, BytesPerLine);
|
|
Inc(SrcPtr, Image^.bpl);
|
|
Inc(DstPtr, BytesPerLine);
|
|
Dec(H);
|
|
end;
|
|
end;
|
|
CheckGdkImageBitOrder(Image, ARawImage.Mask, ARawImage.MaskSize);
|
|
end;
|
|
|
|
|
|
{$IFDEF VerboseRawImage}
|
|
{DebugLn('TGtkWidgetSet.GetRawImageMaskFromGdkBitmap H ',
|
|
' Width=',dbgs(ARawImage.Description.Width),
|
|
' Height=',dbgs(ARawImage.Description.Height),
|
|
' AlphaBitsPerPixel=',dbgs(ARawImage.Description.AlphaBitsPerPixel),
|
|
' MaskSize=',dbgs(ARawImage.MaskSize));}
|
|
{$ENDIF}
|
|
finally
|
|
gdk_image_destroy(Image);
|
|
end;
|
|
|
|
Result:=true;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: TGtkWidgetSet.StretchCopyArea
|
|
Params: DestDC: The destination devicecontext
|
|
X, Y: The left/top corner of the destination rectangle
|
|
Width, Height: The size of the destination rectangle
|
|
SrcDC: The source devicecontext
|
|
XSrc, YSrc: The left/top corner of the source rectangle
|
|
SrcWidth, SrcHeight: The size of the source rectangle
|
|
Mask: An optional mask
|
|
XMask, YMask: Only used if Mask<>nil
|
|
Rop: The raster operation to be performed
|
|
Returns: True if succesful
|
|
|
|
The StretchBlt function copies a bitmap from a source rectangle into a
|
|
destination rectangle using the specified raster operation. If needed, it
|
|
resizes the bitmap to fit the dimensions of the destination rectangle.
|
|
Sizing is done according to the stretching mode currently set in the
|
|
destination device context.
|
|
If SrcDC contains a mask the pixmap will be copied with this transparency.
|
|
|
|
ToDo:
|
|
Mirroring
|
|
Extended NonDrawable support (Image, Bitmap, etc)
|
|
Scale mask
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.StretchCopyArea(DestDC: HDC; X, Y, Width, Height: Integer;
|
|
SrcDC: HDC; XSrc, YSrc, SrcWidth, SrcHeight: Integer;
|
|
Mask: HBITMAP; XMask, YMask: Integer;
|
|
Rop: Cardinal): Boolean;
|
|
var
|
|
SrcDevContext: TGtkDeviceContext absolute SrcDC;
|
|
DstDevContext: TGtkDeviceContext absolute DestDC;
|
|
TempPixmap: PGdkPixmap;
|
|
TempMaskBitmap: PGdkBitmap;
|
|
SizeChange, ROpIsSpecial: Boolean;
|
|
FlipHorz, FlipVert: Boolean;
|
|
|
|
function ScaleAndROP(DestGC: PGDKGC;
|
|
Src: PGDKDrawable; SrcPixmap: PGdkDrawable; SrcMaskBitmap: PGdkBitmap): Boolean;
|
|
var
|
|
Depth: Integer;
|
|
ScaleMethod: TGdkInterpType;
|
|
ShrinkWidth, ShrinkHeight: Boolean;
|
|
GC: PGDKGC;
|
|
begin
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
|
|
DebugLn('ScaleAndROP START DestGC=',DbgS(DestGC),
|
|
' SrcPixmap=',DbgS(SrcPixmap),
|
|
' SrcMaskPixmap=',DbgS(SrcMaskPixmap));
|
|
{$ENDIF}
|
|
Result := False;
|
|
|
|
if DestGC = nil
|
|
then begin
|
|
DebugLn('WARNING: [TGtkWidgetSet.StretchCopyArea] Uninitialized DestGC');
|
|
exit;
|
|
end;
|
|
|
|
// create a temporary graphic context for the scale and raster operations
|
|
// copy the destination GC values into the temporary GC
|
|
GC := gdk_gc_new(DstDevContext.Drawable);
|
|
gdk_gc_copy(GC, DestGC);
|
|
|
|
// clear any previous clipping in the temporary GC
|
|
gdk_gc_set_clip_region(GC, nil);
|
|
gdk_gc_set_clip_rectangle(GC, nil);
|
|
|
|
if SizeChange
|
|
then begin
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
Depth:=gdk_visual_get_system^.Depth;
|
|
DebugLn('ScaleAndROP Scaling buffer: '+dbgs(Width),' x '+dbgs(Height),' x '+dbgs(Depth));
|
|
{$ENDIF}
|
|
|
|
// calculate ScaleMethod
|
|
{$IFDEF VerboseGtkToDos}{$note use SetStretchBltMode(dc, mode) here}{$ENDIF}
|
|
//GDKPixbuf Scaling is not done in the same way as Windows
|
|
//but by rights ScaleMethod should really be chosen based
|
|
//on the destination device's internal flag
|
|
{GDK_INTERP_NEAREST,GDK_INTERP_TILES,
|
|
GDK_INTERP_BILINEAR,GDK_INTERP_HYPER);}
|
|
|
|
ShrinkWidth := Width < SrcWidth;
|
|
ShrinkHeight := Height < SrcHeight;
|
|
if ShrinkWidth and ShrinkHeight
|
|
then ScaleMethod := GDK_INTERP_TILES
|
|
else
|
|
if ShrinkWidth or ShrinkHeight
|
|
then ScaleMethod := GDK_INTERP_BILINEAR//GDK_INTERP_HYPER
|
|
else ScaleMethod := GDK_INTERP_BILINEAR;
|
|
|
|
// Scale the src part to a temporary pixmap with the size of the
|
|
// destination rectangle
|
|
|
|
Result := ScalePixmapAndMask(GC, ScaleMethod,
|
|
SrcPixmap, XSrc, YSrc, SrcWidth, SrcHeight,
|
|
nil, SrcMaskBitmap,
|
|
Width, Height, FlipHorz, FlipVert, TempPixmap, TempMaskBitmap);
|
|
if not Result
|
|
then DebugLn('WARNING: ScaleAndROP ScalePixmap for pixmap failed');
|
|
end
|
|
else begin
|
|
if ROpIsSpecial
|
|
then begin
|
|
// no scaling, but special ROp
|
|
|
|
Depth:=gdk_visual_get_system^.Depth;
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('ScaleAndROP Creating rop buffer: '+dbgs(Width),' x '+dbgs(Height),' x '+dbgs(Depth));
|
|
{$ENDIF}
|
|
TempPixmap := gdk_pixmap_new(nil,SrcWidth,SrcHeight,Depth);
|
|
gdk_window_copy_area(TempPixmap, GC, 0, 0,
|
|
Src, XSrc, YSrc, SrcWidth, SrcHeight);
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
// set raster operation in the destination GC
|
|
if Result
|
|
then SetGCRasterOperation(DestGC, ROP);
|
|
|
|
gdk_gc_unref(GC);
|
|
end;
|
|
|
|
procedure ROPFillBuffer(DC : hDC);
|
|
var
|
|
OldCurrentBrush: PGdiObject;
|
|
Brush : hBrush;
|
|
begin
|
|
if TempPixmap = nil then exit;
|
|
|
|
if not ((ROp=WHITENESS) or (ROp=BLACKNESS) or (ROp=DSTINVERT)) then Exit;
|
|
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('ROPFillBuffer ROp='+dbgs(ROp));
|
|
{$ENDIF}
|
|
with TGtkDeviceContext(DC) do
|
|
begin
|
|
// Temporarily hold the old brush to
|
|
// replace it with the given brush
|
|
OldCurrentBrush := CurrentBrush;
|
|
if ROP = WHITENESS
|
|
then
|
|
Brush := GetStockObject(WHITE_BRUSH)
|
|
else
|
|
Brush := GetStockObject(BLACK_BRUSH);
|
|
CurrentBrush := PGdiObject(Brush);
|
|
SelectedColors := dcscBrush;
|
|
|
|
if not IsNullBrush
|
|
then begin
|
|
gdk_draw_rectangle(TempPixmap, GC, 1, 0, 0, Width, Height);
|
|
end;
|
|
// Restore current brush
|
|
CurrentBrush := OldCurrentBrush;
|
|
end;
|
|
end;
|
|
|
|
function SrcDevBitmapToDrawable: Boolean;
|
|
var
|
|
SrcDrawable: PGdkDrawable;
|
|
MskBitmap: PGdkBitmap;
|
|
ClipMask: PGdkBitmap;
|
|
SrcGDIBitmap: PGdiObject;
|
|
begin
|
|
Result:=true;
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('SrcDevBitmapToDrawable Start');
|
|
{$ENDIF}
|
|
|
|
SrcGDIBitmap := SrcDevContext.CurrentBitmap;
|
|
if SrcGDIBitmap = nil
|
|
then begin
|
|
SrcDrawable := SrcDevContext.Drawable;
|
|
MskBitmap := nil;
|
|
if SrcDrawable = nil then
|
|
begin
|
|
DebugLn('SrcDevBitmapToDrawable NOTE: SrcDevContext.CurrentBitmap=nil, SrcDevContext.Drawable = nil');
|
|
exit;
|
|
end;
|
|
end
|
|
else begin
|
|
SrcDrawable := SrcGDIBitmap^.GDIPixmapObject.Image;
|
|
MskBitmap := CreateGdkMaskBitmap(HBITMAP(PtrUInt(SrcGDIBitmap)), Mask);
|
|
end;
|
|
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('SrcDevBitmapToDrawable SrcPixmap=[',GetWindowDebugReport(SrcDrawable),']',
|
|
' MaskPixmap=[',GetWindowDebugReport(MskBitmap),']');
|
|
{$ENDIF}
|
|
|
|
if (MskBitmap = nil) and (not SizeChange) and (ROP=SRCCOPY)
|
|
then begin
|
|
// simply copy the area
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('SrcDevBitmapToDrawable Simple copy');
|
|
{$ENDIF}
|
|
gdk_gc_set_function(DstDevContext.GC, GDK_COPY);
|
|
gdk_window_copy_area(DstDevContext.Drawable, DstDevContext.GC, X, Y,
|
|
SrcDrawable, XSrc, YSrc, Width, Height);
|
|
gdk_gc_set_function(DstDevContext.GC, DstDevContext.GetFunction);
|
|
Exit;
|
|
end;
|
|
|
|
|
|
// perform raster operation and scaling into Scale and fGC
|
|
DstDevContext.SelectedColors := dcscCustom;
|
|
if not ScaleAndROP(DstDevContext.GC, SrcDevContext.Drawable, SrcDrawable, MskBitmap)
|
|
then begin
|
|
DebugLn('WARNING: SrcDevBitmapToDrawable: ScaleAndROP failed');
|
|
Exit;
|
|
end;
|
|
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('SrcDevBitmapToDrawable TempPixmap=',DbgS(TempPixmap),' TempMaskPixmap=',DbgS(TempMaskBitmap));
|
|
{$ENDIF}
|
|
if TempPixmap <> nil
|
|
then begin
|
|
SrcDrawable := TempPixmap;
|
|
XSrc := 0;
|
|
YSrc := 0;
|
|
SrcWidth := Width;
|
|
SrcHeight := Height;
|
|
end;
|
|
if TempMaskBitmap <> nil
|
|
then begin
|
|
MskBitmap := TempMaskBitmap;
|
|
XMask := 0;
|
|
YMask := 0;
|
|
end;
|
|
|
|
case ROP of
|
|
WHITENESS, BLACKNESS :
|
|
ROPFillBuffer(DestDC);
|
|
end;
|
|
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('SrcDevBitmapToDrawable ',
|
|
' SrcPixmap=',DbgS(SrcPixmap),
|
|
' XSrc='+dbgs(XSrc),' YSrc='+dbgs(YSrc),' SrcWidth='+dbgs(SrcWidth),' SrcHeight='+dbgs(SrcHeight),
|
|
' MaskPixmap=',DbgS(MaskPixmap),
|
|
' XMask='+dbgs(XMask),' YMask='+dbgs(YMask),
|
|
'');
|
|
{$ENDIF}
|
|
|
|
// set clipping mask for transparency
|
|
MergeClipping(DstDevContext, DstDevContext.GC, X, Y, Width, Height,
|
|
MskBitmap, XMask, YMask, ClipMask);
|
|
|
|
// draw image
|
|
{$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
|
|
gdk_window_copy_area(DstDevContext.Drawable, DstDevContext.GC, X, Y,
|
|
SrcDrawable, XSrc, YSrc, SrcWidth, SrcHeight);
|
|
{$IFDEF DebugGDK}EndGDKErrorTrap;{$ENDIF}
|
|
|
|
// unset clipping mask for transparency
|
|
DstDevContext.ResetGCClipping;
|
|
if ClipMask <> nil
|
|
then gdk_bitmap_unref(ClipMask);
|
|
|
|
// restore raster operation to SRCCOPY
|
|
gdk_gc_set_function(DstDevContext.GC, GDK_Copy);
|
|
|
|
Result:=True;
|
|
end;
|
|
|
|
function DrawableToDrawable: Boolean;
|
|
begin
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('DrawableToDrawable Start');
|
|
{$ENDIF}
|
|
Result:=SrcDevBitmapToDrawable;
|
|
end;
|
|
|
|
function PixmapToDrawable: Boolean;
|
|
begin
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('PixmapToDrawable Start');
|
|
{$ENDIF}
|
|
Result:=SrcDevBitmapToDrawable;
|
|
end;
|
|
|
|
function PixmapToBitmap: Boolean;
|
|
begin
|
|
DebugLn('WARNING: [TGtkWidgetSet.StretchCopyArea] PixmapToBitmap unimplemented!');
|
|
Result:=false;
|
|
end;
|
|
|
|
function BitmapToPixmap: Boolean;
|
|
begin
|
|
DebugLn('WARNING: [TGtkWidgetSet.StretchCopyArea] BitmapToPixmap unimplemented!');
|
|
Result:=false;
|
|
end;
|
|
|
|
function Unsupported: Boolean;
|
|
begin
|
|
DebugLn('WARNING: [TGtkWidgetSet.StretchCopyArea] Destination and/or Source unsupported!!');
|
|
Result:=false;
|
|
end;
|
|
|
|
//----------
|
|
function NoDrawableToNoDrawable: Boolean;
|
|
begin
|
|
Result := Unsupported;
|
|
if SrcDevContext.CurrentBitmap = nil then Exit;
|
|
if DstDevContext.CurrentBitmap = nil then Exit;
|
|
|
|
case SrcDevContext.CurrentBitmap^.GDIBitmapType of
|
|
gbBitmap:
|
|
case DstDevContext.CurrentBitmap^.GDIBitmapType of
|
|
gbBitmap: Result:=DrawableToDrawable;
|
|
gbPixmap: Result:=BitmapToPixmap;
|
|
end;
|
|
gbPixmap:
|
|
case DstDevContext.CurrentBitmap^.GDIBitmapType of
|
|
gbBitmap: Result:=PixmapToBitmap;
|
|
gbPixmap: Result:=DrawableToDrawable;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function NoDrawableToDrawable: Boolean;
|
|
begin
|
|
Result := Unsupported;
|
|
if SrcDevContext.CurrentBitmap = nil then Exit;
|
|
|
|
case SrcDevContext.CurrentBitmap^.GDIBitmapType of
|
|
gbBitmap: Result:=PixmapToDrawable;
|
|
gbPixmap: Result:=PixmapToDrawable;
|
|
end;
|
|
end;
|
|
|
|
function DrawableToNoDrawable: Boolean;
|
|
begin
|
|
Result := Unsupported;
|
|
if DstDevContext.CurrentBitmap = nil then Exit;
|
|
|
|
case DstDevContext.CurrentBitmap^.GDIBitmapType of
|
|
gbBitmap: Result:=Unsupported;
|
|
gbPixmap: Result:=Unsupported;
|
|
end;
|
|
end;
|
|
|
|
procedure RaiseSrcDrawableNil;
|
|
begin
|
|
DebugLn(['RaiseSrcDrawableNil ',GetWidgetDebugReport(SrcDevContext.Widget)]);
|
|
RaiseGDBException(Format('TGtkWidgetSet.StretchCopyArea SrcDC=%p Drawable=nil', [Pointer(SrcDevContext)]));
|
|
end;
|
|
|
|
procedure RaiseDestDrawableNil;
|
|
begin
|
|
RaiseGDBException(Format('TGtkWidgetSet.StretchCopyArea DestDC=%p Drawable=nil', [Pointer(DstDevContext)]));
|
|
end;
|
|
|
|
var
|
|
NewSrcWidth: Integer;
|
|
NewSrcHeight: Integer;
|
|
NewWidth: Integer;
|
|
NewHeight: Integer;
|
|
SrcDCOrigin: TPoint;
|
|
DstDCOrigin: TPoint;
|
|
SrcWholeWidth, SrcWholeHeight: integer;
|
|
DstWholeWidth, DstWholeHeight: integer;
|
|
begin
|
|
Result := IsValidDC(DestDC) and IsValidDC(SrcDC);
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('StretchCopyArea Start '+dbgs(Result));
|
|
{$ENDIF}
|
|
if not Result then Exit;
|
|
|
|
if SrcDevContext.HasTransf then
|
|
begin
|
|
// TK: later with shear and rotation error here?
|
|
SrcDevContext.TransfPoint(XSrc, YSrc);
|
|
SrcDevContext.TransfExtent(SrcWidth, SrcHeight);
|
|
end;
|
|
|
|
if DstDevContext.HasTransf then
|
|
begin
|
|
// TK: later with shear and rotation error here?
|
|
DstDevContext.TransfPoint(X, Y);
|
|
DstDevContext.TransfExtent(Width, Height);
|
|
end;
|
|
|
|
FlipHorz := Width < 0;
|
|
if FlipHorz then
|
|
begin
|
|
Width := -Width;
|
|
X := X - Width;
|
|
end;
|
|
|
|
FlipVert := Height < 0;
|
|
if FlipVert then
|
|
begin
|
|
Height := -Height;
|
|
Y := Y - Height;
|
|
end;
|
|
|
|
if (Width = 0) or (Height = 0) then exit;
|
|
if (SrcWidth = 0) or (SrcHeight = 0) then exit;
|
|
|
|
SizeChange := (Width <> SrcWidth) or (Height <> SrcHeight) or FlipVert or FlipHorz;
|
|
ROpIsSpecial := (Rop <> SRCCOPY);
|
|
|
|
SrcDCOrigin := SrcDevContext.Offset;
|
|
Inc(XSrc, SrcDCOrigin.X);
|
|
Inc(YSrc, SrcDCOrigin.Y);
|
|
if SrcDevContext.Drawable = nil then RaiseSrcDrawableNil;
|
|
gdk_window_get_size(PGdkWindow(SrcDevContext.Drawable), @SrcWholeWidth, @SrcWholeHeight);
|
|
|
|
|
|
DstDCOrigin := DstDevContext.Offset;
|
|
Inc(X, DstDCOrigin.X);
|
|
Inc(Y, DstDCOrigin.Y);
|
|
if DstDevContext.Drawable = nil then RaiseDestDrawableNil;
|
|
gdk_window_get_size(PGdkWindow(DstDevContext.Drawable), @DstWholeWidth, @DstWholeHeight);
|
|
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('TGtkWidgetSet.StretchCopyArea BEFORE CLIPPING X='+dbgs(X),' Y='+dbgs(Y),' Width='+dbgs(Width),' Height='+dbgs(Height),
|
|
' XSrc='+dbgs(XSrc)+' YSrc='+dbgs(YSrc)+' SrcWidth='+dbgs(SrcWidth)+' SrcHeight='+dbgs(SrcHeight),
|
|
' SrcDrawable=',DbgS(TGtkDeviceContext(SrcDC).Drawable),
|
|
' SrcOrigin='+dbgs(SrcDCOrigin),
|
|
' DestDrawable='+DbgS(TGtkDeviceContext(DestDC).Drawable),
|
|
' DestOrigin='+dbgs(DestDCOrigin),
|
|
' Mask='+DbgS(Mask)+' XMask='+dbgs(XMask)+' YMask='+dbgs(YMask),
|
|
' SizeChange='+dbgs(SizeChange)+' ROpIsSpecial='+dbgs(ROpIsSpecial),
|
|
' DestWhole='+dbgs(DestWholeWidth)+','+dbgs(DestWholeHeight),
|
|
' SrcWhole='+dbgs(SrcWholeWidth)+','+dbgs(SrcWholeHeight),
|
|
'');
|
|
{$ENDIF}
|
|
{$IFDEF VerboseGtkToDos}{$note use intersectrect here}{$ENDIF}
|
|
if X >= DstWholeWidth then Exit;
|
|
if Y >= DstWholeHeight then exit;
|
|
if X + Width <= 0 then exit;
|
|
if Y + Height <=0 then exit;
|
|
if XSrc >= SrcWholeWidth then Exit;
|
|
if YSrc >= SrcWholeHeight then exit;
|
|
if XSrc + SrcWidth <= 0 then exit;
|
|
if YSrc + SrcHeight <=0 then exit;
|
|
|
|
// gdk does not allow copying areas, party laying out of bounds
|
|
// -> clip
|
|
|
|
// clip src to the left
|
|
if (XSrc<0) then begin
|
|
NewSrcWidth:=SrcWidth+XSrc;
|
|
NewWidth:=((Width*NewSrcWidth) div SrcWidth);
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('StretchCopyArea Cliping Src to left NewSrcWidth='+dbgs(NewSrcWidth),' NewWidth='+dbgs(NewWidth));
|
|
{$ENDIF}
|
|
if NewWidth = 0 then exit;
|
|
inc(X, Width-NewWidth);
|
|
if X >= DstWholeWidth then exit;
|
|
XSrc:=0;
|
|
SrcWidth := NewSrcWidth;
|
|
end;
|
|
|
|
// clip src to the top
|
|
if (YSrc<0) then begin
|
|
NewSrcHeight:=SrcHeight+YSrc;
|
|
NewHeight:=((Height*NewSrcHeight) div SrcHeight);
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('StretchCopyArea Cliping Src to top NewSrcHeight='+dbgs(NewSrcHeight),' NewHeight='+dbgs(NewHeight));
|
|
{$ENDIF}
|
|
if NewHeight = 0 then exit;
|
|
inc(Y, Height - NewHeight);
|
|
if Y >= DstWholeHeight then exit;
|
|
YSrc:=0;
|
|
SrcHeight := NewSrcHeight;
|
|
end;
|
|
|
|
// clip src to the right
|
|
if (XSrc+SrcWidth>SrcWholeWidth) then begin
|
|
NewSrcWidth:=SrcWholeWidth-XSrc;
|
|
Width:=((Width*NewSrcWidth) div SrcWidth);
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('StretchCopyArea Cliping Src to right NewSrcWidth='+dbgs(NewSrcWidth),' NewWidth='+dbgs(Width));
|
|
{$ENDIF}
|
|
if (Width=0) then exit;
|
|
if (X+Width<=0) then exit;
|
|
SrcWidth:=NewSrcWidth;
|
|
end;
|
|
|
|
// clip src to the bottom
|
|
if (YSrc+SrcHeight>SrcWholeHeight) then begin
|
|
NewSrcHeight:=SrcWholeHeight-YSrc;
|
|
Height:=((Height*NewSrcHeight) div SrcHeight);
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
DebugLn('StretchCopyArea Cliping Src to bottom NewSrcHeight='+dbgs(NewSrcHeight),' NewHeight='+dbgs(Height));
|
|
{$ENDIF}
|
|
if (Height=0) then exit;
|
|
if (Y+Height<=0) then exit;
|
|
SrcHeight:=NewSrcHeight;
|
|
end;
|
|
|
|
if Mask = 0
|
|
then begin
|
|
XMask := XSrc;
|
|
YMask := YSrc;
|
|
end;
|
|
|
|
// mark temporary scaling/rop buffers as uninitialized
|
|
TempPixmap := nil;
|
|
TempMaskBitmap := nil;
|
|
|
|
{$IFDEF VerboseStretchCopyArea}
|
|
write('TGtkWidgetSet.StretchCopyArea AFTER CLIPPING X='+dbgs(X)+' Y='+dbgs(Y)+' Width='+dbgs(Width)+' Height='+dbgs(Height),
|
|
' XSrc='+dbgs(XSrc),' YSrc='+dbgs(YSrc)+' SrcWidth='+dbgs(SrcWidth)+' SrcHeight='+dbgs(SrcHeight),
|
|
' SrcDrawable='+DbgS(SrcDevContext.Drawable),
|
|
' DestDrawable='+DbgS(DstDevContext.Drawable),
|
|
' Mask='+DbgS(Mask)+' XMask='+dbgs(XMask)+' YMask='+dbgs(YMask),
|
|
' SizeChange='+dbgs(SizeChange)+' ROpIsSpecial='+dbgs(ROpIsSpecial));
|
|
write(' ROp=');
|
|
case ROp of
|
|
SRCCOPY : DebugLn('SRCCOPY');
|
|
SRCPAINT : DebugLn('SRCPAINT');
|
|
SRCAND : DebugLn('SRCAND');
|
|
SRCINVERT : DebugLn('SRCINVERT');
|
|
SRCERASE : DebugLn('SRCERASE');
|
|
NOTSRCCOPY : DebugLn('NOTSRCCOPY');
|
|
NOTSRCERASE : DebugLn('NOTSRCERASE');
|
|
MERGECOPY : DebugLn('MERGECOPY');
|
|
MERGEPAINT : DebugLn('MERGEPAINT');
|
|
PATCOPY : DebugLn('PATCOPY');
|
|
PATPAINT : DebugLn('PATPAINT');
|
|
PATINVERT : DebugLn('PATINVERT');
|
|
DSTINVERT : DebugLn('DSTINVERT');
|
|
BLACKNESS : DebugLn('BLACKNESS');
|
|
WHITENESS : DebugLn('WHITENESS');
|
|
else
|
|
DebugLn('???');
|
|
end;
|
|
{$ENDIF}
|
|
|
|
{$IFDEF VerboseGtkToDos}{$note tode remove, earlier checks require drawable <> nil}{$ENDIF}
|
|
if SrcDevContext.Drawable = nil
|
|
then begin
|
|
if DstDevContext.Drawable = nil
|
|
then
|
|
Result := NoDrawableToNoDrawable
|
|
else
|
|
Result := NoDrawableToDrawable;
|
|
end
|
|
else begin
|
|
if DstDevContext.Drawable = nil
|
|
then
|
|
Result := DrawableToNoDrawable
|
|
else
|
|
Result := DrawableToDrawable;
|
|
end;
|
|
|
|
if TempPixmap <> nil
|
|
then gdk_pixmap_unref(TempPixmap);
|
|
if TempMaskBitmap <> nil
|
|
then gdk_pixmap_unref(TempMaskBitmap);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.SetSelectionMode(Sender: TObject; Widget: PGtkWidget;
|
|
MultiSelect, ExtendedSelect: boolean);
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.SetSelectionMode(Sender: TObject; Widget: PGtkWidget;
|
|
MultiSelect, ExtendedSelect: boolean);
|
|
var
|
|
AControl: TWinControl;
|
|
SelectionMode: TGtkSelectionMode;
|
|
GtkList: PGtkList;
|
|
begin
|
|
AControl:=TWinControl(Sender);
|
|
if (AControl is TWinControl) and
|
|
(AControl.fCompStyle in [csListBox, csCheckListBox]) then
|
|
begin
|
|
if MultiSelect then
|
|
begin
|
|
if ExtendedSelect
|
|
then SelectionMode:= GTK_SELECTION_EXTENDED
|
|
else SelectionMode:= GTK_SELECTION_MULTIPLE;
|
|
end
|
|
else
|
|
begin
|
|
SelectionMode:= GTK_SELECTION_BROWSE;
|
|
end;
|
|
|
|
GtkList:=PGtkList(GetWidgetInfo(Widget, True)^.CoreWidget);
|
|
if (GtkList^.selection=nil)
|
|
and (SelectionMode=GTK_SELECTION_BROWSE) then
|
|
SelectionMode:=GTK_SELECTION_SINGLE;
|
|
gtk_list_set_selection_mode(GtkList,SelectionMode);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.BringFormToFront(Sender: TObject);
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.BringFormToFront(Sender: TObject);
|
|
var
|
|
AWindow: PGdkWindow;
|
|
Widget: PGtkWidget;
|
|
begin
|
|
Widget := PgtkWidget(TCustomForm(Sender).Handle);
|
|
AWindow:=GetControlWindow(Widget);
|
|
if AWindow<>nil then begin
|
|
gdk_window_raise(AWindow);
|
|
end;
|
|
end;
|
|
|
|
procedure TGtkWidgetSet.SetDesigning(AComponent: TComponent);
|
|
{var
|
|
AWinControl: TWinControl absolute AComponent;
|
|
}
|
|
begin
|
|
// change cursor
|
|
{
|
|
Paul Ishenin:
|
|
this will never happen
|
|
|
|
if (AComponent is TWinControl) and (AWinControl.HandleAllocated) then
|
|
TGtkWSWinControl(AWinControl.WidgetSetClass).SetCursor(AWinControl, Screen.Cursors[crDefault]);
|
|
}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.ResizeChild
|
|
|
|
Params: sender - the object which invoked this function
|
|
Left,Top,Width,Height - new dimensions for the control
|
|
Returns: Nothing
|
|
|
|
*Note: Resize a child widget on the parents fixed widget
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.ResizeChild(Sender : TObject;
|
|
Left, Top, Width, Height : Integer);
|
|
var
|
|
LCLControl: TWinControl;
|
|
begin
|
|
//DebugLn('[TGtkWidgetSet.ResizeChild] START ',TControl(Sender).Name,':',Sender.Classname,' Left=',Left,' Top=',Top,' Width=',Width,' Height=',Height);
|
|
//DebugLn((Format('trace: [TGtkWidgetSet.ResizeChild] %s --> Resize', [Sender.ClassNAme])));
|
|
|
|
if Sender is TWinControl then begin
|
|
LCLControl:=TWinControl(Sender);
|
|
if LCLControl.HandleAllocated then begin
|
|
ResizeHandle(LCLControl);
|
|
//if (Sender is TCustomForm) then
|
|
//if CompareText(Sender.ClassName,'TScrollBar')=0 then
|
|
// DebugLn(' FFF ResizeChild ',Sender.ClassName,' ',Left,',',Top,',',Width,',',Height);
|
|
end;
|
|
end;
|
|
//DebugLn('[TGtkWidgetSet.ResizeChild] END ',Sender.Classname,' Left=',Left,' Top=',Top,' Width=',Width,' Height=',Height);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: TGtkWidgetSet.SetCallbackEx
|
|
Params: AMsg - message for which to set a callback
|
|
AGTKObject - object to which callback will be send
|
|
ALCLObject - for compatebility reasons provided, will be used when
|
|
AGTKObject = nil
|
|
Direct - true: connect the signal to the AGTKObject
|
|
false: choose smart what gtkobject to use
|
|
Returns: nothing
|
|
|
|
Applies a Message to the sender
|
|
------------------------------------------------------------------------------}
|
|
//TODO: remove ALCLObject when creation splitup is finished
|
|
procedure TGtkWidgetSet.SetCallbackEx(const AMsg: LongInt;
|
|
const AGTKObject: PGTKObject; const ALCLObject: TObject; Direct: boolean);
|
|
|
|
procedure ConnectSenderSignal(const AnObject:PGTKObject; const ASignal: PChar;
|
|
const ACallBackProc: Pointer);
|
|
begin
|
|
ConnectSignal(AnObject,ASignal,ACallBackProc,ALCLObject);
|
|
end;
|
|
|
|
procedure ConnectSenderSignalAfter(const AnObject:PGTKObject;
|
|
const ASignal: PChar; const ACallBackProc: Pointer);
|
|
begin
|
|
ConnectSignalAfter(AnObject,ASignal,ACallBackProc,ALCLObject);
|
|
end;
|
|
|
|
procedure ConnectSenderSignal(const AnObject:PGTKObject; const ASignal: PChar;
|
|
const ACallBackProc: Pointer; const AReqSignalMask: TGdkEventMask);
|
|
begin
|
|
ConnectSignal(AnObject,ASignal,ACallBackProc,ALCLObject, AReqSignalMask);
|
|
end;
|
|
|
|
procedure ConnectSenderSignalAfter(const AnObject:PGTKObject;
|
|
const ASignal: PChar; const ACallBackProc: Pointer;
|
|
const AReqSignalMask: TGdkEventMask);
|
|
begin
|
|
ConnectSignalAfter(AnObject,ASignal,ACallBackProc,ALCLObject,
|
|
AReqSignalMask);
|
|
end;
|
|
|
|
procedure ConnectFocusEvents(const AnObject: PGTKObject);
|
|
begin
|
|
ConnectSenderSignal(AnObject, 'focus-in-event', @gtkFocusCB);
|
|
ConnectSenderSignalAfter(AnObject, 'focus-in-event', @gtkFocusCBAfter);
|
|
ConnectSenderSignal(AnObject, 'focus-out-event', @gtkKillFocusCB);
|
|
ConnectSenderSignalAfter(AnObject, 'focus-out-event', @gtkKillFocusCBAfter);
|
|
end;
|
|
|
|
procedure ConnectKeyPressReleaseEvents(const AnObject: PGTKObject);
|
|
begin
|
|
//debugln('ConnectKeyPressReleaseEvents A ALCLObject=',DbgSName(ALCLObject));
|
|
ConnectSenderSignal(AnObject,
|
|
'key-press-event', @GTKKeyPress, GDK_KEY_PRESS_MASK);
|
|
ConnectSenderSignalAfter(AnObject,
|
|
'key-press-event', @GTKKeyPressAfter, GDK_KEY_PRESS_MASK);
|
|
ConnectSenderSignal(AnObject,
|
|
'key-release-event', @GTKKeyRelease, GDK_KEY_RELEASE_MASK);
|
|
ConnectSenderSignalAfter(AnObject,
|
|
'key-release-event', @GTKKeyReleaseAfter, GDK_KEY_RELEASE_MASK);
|
|
end;
|
|
|
|
function GetAdjustment(const gObject: PGTKObject; vertical: boolean):PGtkObject;
|
|
var
|
|
Scroll: PGtkObject;
|
|
begin
|
|
if Vertical then begin
|
|
if ALCLObject is TScrollBar then
|
|
result := PGtkObject(PgtkhScrollBar(gObject)^.Scrollbar.Range.Adjustment)
|
|
else if (ALCLObject is TScrollBox)
|
|
or (ALCLObject is TCustomForm)
|
|
or (ALCLObject is TCustomFrame)
|
|
then begin
|
|
Scroll := gtk_object_get_data(gObject, odnScrollArea);
|
|
Result := PGtkObject(gtk_scrolled_window_get_vadjustment(
|
|
PGTKScrolledWindow(Scroll)));
|
|
end
|
|
else if GtkWidgetIsA(PGtkWidget(gObject),gtk_scrolled_window_get_type) then
|
|
begin
|
|
Result := PGtkObject(gtk_scrolled_window_get_vadjustment(
|
|
PGTKScrolledWindow(gObject)))
|
|
end else
|
|
DebugLn(['TGtkWidgetSet.SetCallbackEx.GetAdjustment WARNING: invalid widget: ',GetWidgetDebugReport(PGtkWidget(gObject))]);
|
|
|
|
end else begin
|
|
if ALCLObject is TScrollBar then
|
|
Result := PgtkObject(PgtkhScrollBar(gObject)^.Scrollbar.Range.Adjustment)
|
|
else if (ALCLObject is TScrollBox)
|
|
or (ALCLObject is TCustomForm)
|
|
or (ALCLObject is TCustomFrame)
|
|
then begin
|
|
Scroll := gtk_object_get_data(gObject, odnScrollArea);
|
|
Result := PgtkObject(gtk_scrolled_window_get_hadjustment(
|
|
PGTKScrolledWindow(Scroll)));
|
|
end
|
|
else if GtkWidgetIsA(PGtkWidget(gObject),gtk_scrolled_window_get_type) then
|
|
begin
|
|
//DebugLn(['GetAdjustment ',GetWidgetDebugReport(PGtkWidget(gObject))]);
|
|
Result := PgtkObject(gtk_scrolled_window_get_hadjustment(
|
|
PGTKScrolledWindow(gObject)));
|
|
end else
|
|
DebugLn(['TGtkWidgetSet.SetCallbackEx.GetAdjustment WARNING: invalid widget: ',GetWidgetDebugReport(PGtkWidget(gObject))]);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
gObject, gFixed, gCore, Adjustment: PGTKObject;
|
|
{$IFDEF GTK2}
|
|
gTemp: PGTKObject;
|
|
{$ENDIF}
|
|
Info: PWidgetInfo;
|
|
gMain: PGtkObject;
|
|
gMouse: PGtkObject;
|
|
begin
|
|
//debugln('TGtkWidgetSet.SetCallback A ALCLObject=',DbgSName(ALCLObject),' AMsg=',dbgs(AMsg));
|
|
if Direct then
|
|
begin
|
|
gMain := AGTKObject;
|
|
gCore := AGTKObject;
|
|
gFixed := AGTKObject;
|
|
gMouse := AGTKObject;
|
|
gObject := AGTKObject;
|
|
end
|
|
else
|
|
begin
|
|
// gObject
|
|
if AGTKObject = nil then gObject := ObjectToGTKObject(ALCLObject)
|
|
else gObject := AGTKObject;
|
|
if gObject = nil then Exit;
|
|
|
|
Info:=GetWidgetInfo(gObject, True);
|
|
|
|
// gFixed is the widget with the client area (e.g. TGroupBox, TCustomForm have this)
|
|
gFixed := PGTKObject(GetFixedWidget(gObject));
|
|
if gFixed = nil then
|
|
gFixed := gObject;
|
|
|
|
// gCore is the working widget (e.g. TListBox has a scrolling widget (=main widget) and a tree widget (=core widget))
|
|
gCore:=PGtkObject(Info^.CoreWidget);
|
|
gMain:=GetMainWidget(gObject);
|
|
if (gMain=nil) then
|
|
gMain:=gObject;
|
|
if (gMain<>gObject) then
|
|
DebugLn(['TGtkWidgetSet.SetCallback WARNING: gObject<>MainWidget ',DbgSName(ALCLObject)]);
|
|
|
|
if (gFixed <> gMain) then
|
|
gMouse := gFixed
|
|
else
|
|
gMouse := gCore;
|
|
|
|
if gMouse=nil then
|
|
DebugLn(['TGtkWidgetSet.SetCallback WARNING: gMouseWidget=nil ',DbgSName(ALCLObject)]);
|
|
|
|
{$IFDEF GTK1}
|
|
if ALCLObject is TCustomListBox then
|
|
gMouse:=gMain;
|
|
{$ELSE}
|
|
if GTK_IS_FIXED(gMouse) and GTK_WIDGET_NO_WINDOW(gMouse) then
|
|
begin
|
|
gTemp := PGtkObject(gtk_widget_get_parent(PGtkWidget(gMouse)));
|
|
//DebugLn(gtk_type_name(g_object_type(gMouse)) + ' => ' + gtk_type_name(g_object_type(gTemp)));
|
|
if GTK_IS_EVENT_BOX(gTemp) then
|
|
gMouse := gTemp;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
//DebugLn(['TGtkWidgetSet.SetCallbackSmart MouseWidget=',GetWidgetDebugReport(PGtkWidget(gMouse))]);
|
|
|
|
case AMsg of
|
|
LM_SHOWWINDOW :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'show', @gtkshowCB);
|
|
ConnectSenderSignal(gObject, 'hide', @gtkhideCB);
|
|
end;
|
|
|
|
LM_DESTROY :
|
|
begin
|
|
//DebugLn(['TGtkWidgetSet.SetCallback ',DbgSName(ALCLObject)]);
|
|
ConnectSenderSignal(gObject, 'destroy', @gtkdestroyCB);
|
|
end;
|
|
|
|
LM_CLOSEQUERY :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'delete-event', @gtkdeleteCB);
|
|
end;
|
|
|
|
LM_ACTIVATE :
|
|
begin
|
|
if (ALCLObject is TCustomForm) and (TCustomForm(ALCLObject).Parent=nil)
|
|
then begin
|
|
{$IFDEF GTK2}
|
|
ConnectSenderSignal(gObject, 'focus-in-event', @gtkfrmactivateAfter);
|
|
ConnectSenderSignal(gObject, 'focus-out-event', @gtkfrmdeactivateAfter);
|
|
{$ELSE}
|
|
ConnectSenderSignalAfter(gObject, 'focus-in-event', @gtkfrmactivateAfter);
|
|
ConnectSenderSignalAfter(gObject, 'focus-out-event', @gtkfrmdeactivateAfter);
|
|
{$ENDIF}
|
|
end else if ALCLObject is TCustomMemo then
|
|
ConnectSenderSignal(gCore, 'activate', @gtkactivateCB)
|
|
else
|
|
ConnectSenderSignal(gObject, 'activate', @gtkactivateCB);
|
|
end;
|
|
|
|
LM_ACTIVATEITEM :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'activate-item', @gtkactivateCB);
|
|
end;
|
|
|
|
LM_CHANGED :
|
|
begin
|
|
if ALCLObject is TCustomTrackBar then
|
|
begin
|
|
ConnectSenderSignal(gtk_Object(
|
|
gtk_range_get_adjustment(GTK_RANGE(gObject))) ,
|
|
'value_changed', @gtkvaluechanged);
|
|
end
|
|
else
|
|
if ALCLObject is TCustomMemo then
|
|
ConnectSenderSignal(gCore, 'changed', @gtkchanged_editbox)
|
|
else if ALCLObject is TCustomCheckbox then
|
|
begin
|
|
ConnectSenderSignal(gObject, 'toggled', @gtktoggledCB)
|
|
{$IFDEF GTK2}
|
|
// in gtk2 callback signal of SpinEdit is 'value-changed' (in gtk1- 'changed')
|
|
end else
|
|
if ALCLObject is TCustomFloatSpinEdit then
|
|
begin
|
|
ConnectSenderSignalAfter(gObject, 'changed', @gtkchanged_spinbox);
|
|
ConnectSenderSignal(gObject, 'value-changed', @gtkchanged_editbox);
|
|
{$ENDIF}
|
|
end else
|
|
begin
|
|
{$IFDEF VerboseTWinControlRealText}
|
|
ConnectSenderSignalAfter(gObject, 'changed', @gtkchanged_editbox);
|
|
{$ELSE}
|
|
{$IFDEF gtk2}
|
|
if GTK_IS_ENTRY(gObject) then
|
|
begin
|
|
ConnectSenderSignal(gObject,'backspace', @gtkchanged_editbox_backspace);
|
|
if (gtk_major_version = 2) and (gtk_minor_version < 17) then
|
|
ConnectSenderSignal(gObject,'delete-from-cursor', @gtkchanged_editbox_delete);
|
|
end;
|
|
{$ENDIF}
|
|
ConnectSenderSignal(gObject, 'changed', @gtkchanged_editbox);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
LM_CLICKED:
|
|
begin
|
|
ConnectSenderSignal(gObject, 'clicked', @gtkclickedCB);
|
|
end;
|
|
|
|
LM_CONFIGUREEVENT :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'configure-event', @gtkconfigureevent);
|
|
end;
|
|
|
|
LM_DAYCHANGED : //calendar
|
|
Begin
|
|
ConnectSenderSignal(gCore, 'day-selected', @gtkdaychanged);
|
|
ConnectSenderSignal(gCore, 'day-selected-double-click', @gtkdaychanged);
|
|
end;
|
|
|
|
LM_PAINT :
|
|
begin
|
|
//DebugLn(['TGtkWidgetSet.SetCallback ',DbgSName(ALCLObject),' ',GetWidgetDebugReport(PGtkWIdget(gfixed))]);
|
|
{$Ifdef GTK1}
|
|
//ConnectSenderSignal(gFixed, 'draw', @gtkDrawCB);
|
|
ConnectSenderSignalAfter(gFixed, 'draw', @gtkDrawAfterCB);
|
|
{$EndIf}
|
|
{$Ifdef GTK2}
|
|
ConnectSenderSignal(gFixed,'expose-event', @GTKExposeEvent);
|
|
ConnectSenderSignalAfter(gFixed,'style-set', @GTKStyleChangedAfter);
|
|
{$EndIf}
|
|
ConnectSenderSignalAfter(gFixed,'expose-event', @GTKExposeEventAfter);
|
|
ConnectSenderSignal(gFixed,'style-set', @GTKStyleChanged);
|
|
end;
|
|
|
|
{$IFDEF GTK1}
|
|
LM_FOCUS :
|
|
begin
|
|
if (ALCLObject is TCustomComboBox) then begin
|
|
ConnectFocusEvents(PgtkObject(PgtkCombo(gObject)^.entry));
|
|
ConnectFocusEvents(PgtkObject(PgtkCombo(gObject)^.list));
|
|
end else
|
|
begin
|
|
ConnectFocusEvents(gCore);
|
|
end;
|
|
end;
|
|
|
|
LM_GRABFOCUS:
|
|
begin
|
|
ConnectSenderSignal(gObject, 'grab_focus', @gtkActivateCB);
|
|
end;
|
|
|
|
LM_KEYDOWN,
|
|
LM_CHAR,
|
|
LM_KEYUP,
|
|
LM_SYSKEYDOWN,
|
|
LM_SYSCHAR,
|
|
LM_SYSKEYUP:
|
|
begin
|
|
//debugln('TGtkWidgetSet.SetCallback A KEY ALCLObject=',DbgSName(ALCLObject),' AMsg=',dbgs(AMsg));
|
|
if (ALCLObject is TCustomComboBox) then begin
|
|
ConnectKeyPressReleaseEvents(PgtkObject(PgtkCombo(gObject)^.entry));
|
|
end
|
|
else
|
|
if (ALCLObject is TCustomForm) then begin
|
|
ConnectKeyPressReleaseEvents(gObject);
|
|
end;
|
|
ConnectKeyPressReleaseEvents(gCore);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
LM_MONTHCHANGED: //calendar
|
|
Begin
|
|
ConnectSenderSignal(gCore, 'month-changed', @gtkmonthchanged);
|
|
ConnectSenderSignal(gCore, 'prev-month', @gtkmonthchanged);
|
|
ConnectSenderSignal(gCore, 'next-month', @gtkmonthchanged);
|
|
end;
|
|
|
|
LM_MOUSEMOVE:
|
|
begin
|
|
{$IFDEF GTK1}
|
|
if (ALCLObject is TCustomComboBox) then
|
|
begin
|
|
ConnectSenderSignal(PgtkObject(PgtkCombo(gObject)^.entry),
|
|
'motion-notify-event',
|
|
@GTKMotionNotify, GDK_POINTER_MOTION_MASK);
|
|
ConnectSenderSignalAfter(PgtkObject(PgtkCombo(gObject)^.entry),
|
|
'motion-notify-event',
|
|
@GTKMotionNotifyAfter, GDK_POINTER_MOTION_MASK);
|
|
|
|
ConnectSenderSignal(PgtkObject(PgtkCombo(gObject)^.button),
|
|
'motion-notify-event',
|
|
@GTKMotionNotify, GDK_POINTER_MOTION_MASK);
|
|
ConnectSenderSignalAfter(PgtkObject(PgtkCombo(gObject)^.button),
|
|
'motion-notify-event',
|
|
@GTKMotionNotifyAfter, GDK_POINTER_MOTION_MASK);
|
|
end
|
|
else
|
|
{$ENDIF}
|
|
begin
|
|
ConnectSenderSignal(gMouse, 'motion-notify-event', @GTKMotionNotify,
|
|
GDK_POINTER_MOTION_MASK);
|
|
ConnectSenderSignalAfter(gMouse, 'motion-notify-event',
|
|
@GTKMotionNotifyAfter, GDK_POINTER_MOTION_MASK);
|
|
end;
|
|
end;
|
|
|
|
LM_LBUTTONDOWN,
|
|
LM_RBUTTONDOWN,
|
|
LM_MBUTTONDOWN,
|
|
LM_MOUSEWHEEL :
|
|
begin
|
|
{$IFDEF GTK1}
|
|
if (ALCLObject is TCustomComboBox) then
|
|
begin
|
|
ConnectSenderSignal(PgtkObject(PgtkCombo(gObject)^.entry),
|
|
'button-press-event', @gtkMouseBtnPress, GDK_BUTTON_PRESS_MASK);
|
|
ConnectSenderSignalAfter(PgtkObject(PgtkCombo(gObject)^.entry),
|
|
'button-press-event', @gtkMouseBtnPressAfter,
|
|
GDK_BUTTON_PRESS_MASK);
|
|
ConnectSenderSignal(PgtkObject(PgtkCombo(gObject)^.button) ,
|
|
'button-press-event', @gtkMouseBtnPress, GDK_BUTTON_PRESS_MASK);
|
|
ConnectSenderSignalAfter(PgtkObject(PgtkCombo(gObject)^.button) ,
|
|
'button-press-event', @gtkMouseBtnPressAfter, GDK_BUTTON_PRESS_MASK);
|
|
// Connecting the list seems to cause errors. Maybe we are returning the
|
|
// wrong boolean in the callback func
|
|
// ConnectSenderSignal(PgtkObject(PgtkCOmbo(gObject)^.list),
|
|
// 'button-press-event', @gtkMouseBtnPress, GDK_BUTTON_PRESS_MASK);
|
|
end
|
|
else
|
|
{$ENDIF}
|
|
begin
|
|
ConnectSenderSignal(gMouse, 'button-press-event', @gtkMouseBtnPress,
|
|
GDK_BUTTON_PRESS_MASK);
|
|
ConnectSenderSignalAfter(gMouse, 'button-press-event',
|
|
@gtkMouseBtnPressAfter, GDK_BUTTON_PRESS_MASK);
|
|
{$IFDEF Gtk2}
|
|
ConnectSenderSignal(gMouse, 'scroll-event', @gtkMouseWheelCB,
|
|
GDK_BUTTON_PRESS_MASK);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
LM_LBUTTONUP,
|
|
LM_RBUTTONUP,
|
|
LM_MBUTTONUP:
|
|
begin
|
|
{$IFDEF GTK1}
|
|
if (ALCLObject is TCustomComboBox) then
|
|
begin
|
|
ConnectSenderSignal(PgtkObject(PgtkCombo(gObject)^.entry),
|
|
'button-release-event', @gtkMouseBtnRelease, GDK_BUTTON_RELEASE_MASK);
|
|
ConnectSenderSignalAfter(PgtkObject(PgtkCombo(gObject)^.entry),
|
|
'button-release-event', @gtkMouseBtnReleaseAfter,
|
|
GDK_BUTTON_RELEASE_MASK);
|
|
ConnectSenderSignal(PgtkObject(PgtkCombo(gObject)^.button) ,
|
|
'button-release-event', @gtkMouseBtnRelease, GDK_BUTTON_RELEASE_MASK);
|
|
ConnectSenderSignalAfter(PgtkObject(PgtkCombo(gObject)^.button) ,
|
|
'button-release-event', @gtkMouseBtnReleaseAfter,
|
|
GDK_BUTTON_RELEASE_MASK);
|
|
// Connecting the list seems to cause errors. Maybe we are returning the
|
|
// wrong boolean in the callback func
|
|
// ConnectSenderSignal(PgtkObject(PgtkCombo(gObject)^.list),
|
|
// 'button-release-event', @gtkMouseBtnRelease,
|
|
// GDK_BUTTON_RELEASE_MASK);
|
|
end
|
|
else
|
|
{$ENDIF}
|
|
begin
|
|
ConnectSenderSignal(gMouse, 'button-release-event', @gtkMouseBtnRelease,
|
|
GDK_BUTTON_RELEASE_MASK);
|
|
ConnectSenderSignalAfter(gMouse, 'button-release-event',
|
|
@gtkMouseBtnReleaseAfter,GDK_BUTTON_RELEASE_MASK);
|
|
end;
|
|
end;
|
|
|
|
LM_ENTER :
|
|
begin
|
|
if ALCLObject is TCustomButton then
|
|
ConnectSenderSignal(gObject, 'enter', @gtkenterCB)
|
|
else
|
|
ConnectSenderSignal(gObject, 'focus-in-event', @gtkFocusInNotifyCB); //TODO: check this focus in is mapped to focus
|
|
end;
|
|
|
|
LM_EXIT :
|
|
begin
|
|
if ALCLObject is TCustomButton then
|
|
ConnectSenderSignal(gObject, 'leave', @gtkleaveCB)
|
|
else
|
|
ConnectSenderSignal(gObject, 'focus-out-event', @gtkFocusOutNotifyCB);
|
|
end;
|
|
|
|
LM_LEAVE :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'leave', @gtkleaveCB);
|
|
end;
|
|
|
|
LM_WINDOWPOSCHANGED: //LM_SIZEALLOCATE, LM_RESIZE :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'size-allocate', @gtksize_allocateCB);
|
|
if gObject<>gFixed then
|
|
begin
|
|
ConnectSenderSignal(gFixed, 'size-allocate', @gtksize_allocate_client);
|
|
end;
|
|
end;
|
|
|
|
LM_CHECKRESIZE :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'check-resize', @gtkresizeCB);
|
|
end;
|
|
|
|
LM_SETEDITABLE :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'set-editable', @gtkseteditable);
|
|
end;
|
|
|
|
LM_MOVEWORD :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'move-word', @gtkmoveword);
|
|
end;
|
|
|
|
LM_MOVEPAGE :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'move-page', @gtkmovepage);
|
|
end;
|
|
|
|
LM_MOVETOROW :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'move-to-row', @gtkmovetorow);
|
|
end;
|
|
|
|
LM_MOVETOCOLUMN :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'move-to-column', @gtkmovetocolumn);
|
|
end;
|
|
|
|
LM_MOUSEENTER:
|
|
begin
|
|
if gCore<>nil then
|
|
ConnectSenderSignal(gCore, 'enter', @gtkEnterCB)
|
|
end;
|
|
|
|
LM_MOUSELEAVE:
|
|
begin
|
|
if gCore<>nil then
|
|
ConnectSenderSignal(gCore, 'leave', @gtkLeaveCB)
|
|
end;
|
|
|
|
LM_KILLCHAR :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'kill-char', @gtkkillchar);
|
|
end;
|
|
|
|
LM_KILLWORD :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'kill-word', @gtkkillword);
|
|
end;
|
|
|
|
LM_KILLLINE :
|
|
begin
|
|
ConnectSenderSignal(gObject, 'kill-line', @gtkkillline);
|
|
end;
|
|
|
|
LM_CUT:
|
|
begin
|
|
if (ALCLObject is TCustomMemo) then
|
|
ConnectSenderSignal(gCore, 'cut-clipboard', @gtkcuttoclip)
|
|
else
|
|
ConnectSenderSignal(gObject, 'cut-clipboard', @gtkcuttoclip);
|
|
end;
|
|
|
|
LM_COPY:
|
|
begin
|
|
if (ALCLObject is TCustomMemo) then
|
|
ConnectSenderSignal(gCore, 'copy-clipboard', @gtkcopytoclip)
|
|
else
|
|
ConnectSenderSignal(gObject, 'copy-clipboard', @gtkcopytoclip);
|
|
end;
|
|
|
|
LM_PASTE:
|
|
begin
|
|
if (ALCLObject is TCustomMemo) then
|
|
ConnectSenderSignal(gCore, 'paste-clipboard', @gtkpastefromclip)
|
|
else
|
|
ConnectSenderSignal(gObject, 'paste-clipboard', @gtkpastefromclip);
|
|
end;
|
|
|
|
LM_HSCROLL:
|
|
begin
|
|
Adjustment := GetAdjustment(gObject, False);
|
|
if Adjustment <> nil then
|
|
ConnectSenderSignal(Adjustment, 'value-changed', @GTKHScrollCB);
|
|
end;
|
|
|
|
LM_VSCROLL:
|
|
begin
|
|
Adjustment := GetAdjustment(gObject, True);
|
|
if Adjustment <> nil then
|
|
ConnectSenderSignal(Adjustment, 'value-changed', @GTKVScrollCB);
|
|
end;
|
|
|
|
LM_YEARCHANGED : //calendar
|
|
Begin
|
|
ConnectSenderSignal(gCore, 'prev-year', @gtkyearchanged);
|
|
ConnectSenderSignal(gCore, 'next-year', @gtkyearchanged);
|
|
end;
|
|
|
|
// Listview & Header control
|
|
LM_COMMAND:
|
|
begin
|
|
if ALCLObject is TCustomComboBox then begin
|
|
ConnectSenderSignalAfter(PgtkObject(PgtkCombo(gObject)^.popwin),
|
|
'show', @gtkComboBoxShowAfter);
|
|
ConnectSenderSignalAfter(PgtkObject(PgtkCombo(gObject)^.popwin),
|
|
'hide', @gtkComboBoxHideAfter);
|
|
end;
|
|
end;
|
|
|
|
LM_SelChange:
|
|
begin
|
|
if ALCLObject is TCustomListBox then
|
|
ConnectSenderSignalAfter(PgtkObject(gCore),
|
|
'selection_changed', @gtkListBoxSelectionChangedAfter);
|
|
end;
|
|
|
|
LM_DROPFILES:
|
|
ConnectSenderSignal(gCore, 'drag_data_received', @GtkDragDataReceived);
|
|
|
|
(*
|
|
LM_WINDOWPOSCHANGED:
|
|
begin
|
|
ConnectSenderSignal(gObject, 'size-allocate', @gtkSizeAllocateCB);
|
|
// ConnectSenderSignal(gObject, 'move_resize', @gtkmoveresize);
|
|
end;
|
|
*)
|
|
else
|
|
DebugLn(Format('Trace:ERROR: Signal %d not found!', [AMsg]));
|
|
end;
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.SetCallbackDirect(const AMsg: LongInt;
|
|
const AGTKObject: PGTKObject; const ALCLObject: TObject);
|
|
begin
|
|
SetCallbackEx(AMsg,AGTKObject,ALCLObject,true);
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.SetCallback(const AMsg: LongInt;
|
|
const AGTKObject: PGTKObject; const ALCLObject: TObject);
|
|
begin
|
|
SetCallbackEx(AMsg,AGTKObject,ALCLObject,false);
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.SetCommonCallbacks(const AGTKObject: PGTKObject;
|
|
const ALCLObject: TObject);
|
|
begin
|
|
SetCallback(LM_SHOWWINDOW, AGTKObject, ALCLObject);
|
|
SetCallback(LM_DESTROY, AGTKObject, ALCLObject);
|
|
SetCallback(LM_FOCUS, AGTKObject, ALCLObject);
|
|
SetCallback(LM_WINDOWPOSCHANGED, AGTKObject, ALCLObject);
|
|
SetCallback(LM_PAINT, AGTKObject, ALCLObject);
|
|
SetCallback(LM_KEYDOWN, AGTKObject, ALCLObject);
|
|
SetCallback(LM_KEYUP, AGTKObject, ALCLObject);
|
|
SetCallback(LM_CHAR, AGTKObject, ALCLObject);
|
|
SetCallback(LM_MOUSEMOVE, AGTKObject, ALCLObject);
|
|
SetCallback(LM_LBUTTONDOWN, AGTKObject, ALCLObject);
|
|
SetCallback(LM_LBUTTONUP, AGTKObject, ALCLObject);
|
|
SetCallback(LM_RBUTTONDOWN, AGTKObject, ALCLObject);
|
|
SetCallback(LM_RBUTTONUP, AGTKObject, ALCLObject);
|
|
SetCallback(LM_MBUTTONDOWN, AGTKObject, ALCLObject);
|
|
SetCallback(LM_MBUTTONUP, AGTKObject, ALCLObject);
|
|
SetCallback(LM_MOUSEWHEEL, AGTKObject, ALCLObject);
|
|
SetCallback(LM_DROPFILES, AGTKObject, ALCLObject);
|
|
SetCallback(LM_CONTEXTMENU, AGtkObject, ALCLObject);
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: TGtkWidgetSet.RemoveCallBacks
|
|
Params: Widget
|
|
Returns: nothing
|
|
|
|
Removes Call Back Signals from the Widget
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.RemoveCallbacks(Widget: PGtkWidget);
|
|
{$IFDEF Gtk1}
|
|
var
|
|
MainWidget, ClientWidget, ImplWidget: PGtkWidget;
|
|
WinWidgetInfo: PWinWidgetInfo;
|
|
{$ELSE}
|
|
var
|
|
Info: PWinWidgetInfo;
|
|
{$ENDIF}
|
|
begin
|
|
if Widget = nil then Exit;
|
|
{$IFDEF Gtk1}
|
|
MainWidget := Widget;
|
|
if GtkWidgetIsA(Widget, GTK_MENU_ITEM_GET_TYPE) then Exit;
|
|
|
|
ClientWidget := GetFixedWidget(MainWidget);
|
|
WinWidgetInfo := GetWidgetInfo(MainWidget, False);
|
|
if WinWidgetInfo <> nil then
|
|
ImplWidget := WinWidgetInfo^.CoreWidget
|
|
else
|
|
ImplWidget := nil;
|
|
if MainWidget = PGtkWidget(LastWFPResult) then
|
|
DestroyWindowFromPointCB(MainWidget, nil);
|
|
g_signal_handlers_destroy(PGtkObject(MainWidget));
|
|
if (ClientWidget <> nil) and (ClientWidget <> MainWidget) then
|
|
begin
|
|
if ClientWidget = PGtkWidget(LastWFPResult) then
|
|
DestroyWindowFromPointCB(ClientWidget, nil);
|
|
g_signal_handlers_destroy(PGtkObject(ClientWidget));
|
|
end;
|
|
if (ImplWidget <> nil) and
|
|
(ImplWidget <> ClientWidget) and
|
|
(ImplWidget <> MainWidget) then
|
|
begin
|
|
if ImplWidget = PGtkWidget(LastWFPResult) then
|
|
DestroyWindowFromPointCB(ImplWidget, nil);
|
|
g_signal_handlers_destroy(PGtkObject(ImplWidget));
|
|
end;
|
|
{$ELSE}
|
|
Info := GetWidgetInfo(Widget, False);
|
|
if Info <> nil then
|
|
g_signal_handlers_disconnect_matched(Widget, G_SIGNAL_MATCH_DATA, 0, 0, nil, nil, Info);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TGtkWidgetSet.DestroyLCLComponent
|
|
Params: Sender: TObject
|
|
|
|
Destroy the widget and all associated data
|
|
-------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.DestroyLCLComponent(Sender : TObject);
|
|
var
|
|
handle: hwnd; // handle of sender
|
|
Widget: PGtkWidget;
|
|
APage: TCustomPage;
|
|
NoteBookWidget: PGtkNotebook;
|
|
GtkWindow: PGtkWidget;
|
|
begin
|
|
Handle := HWnd(PtrUInt(ObjectToGtkObject(Sender)));
|
|
if Handle=0 then exit;
|
|
Widget:=PGtkWidget(Handle);
|
|
if WidgetIsDestroyingHandle(Widget) then exit;
|
|
SetWidgetIsDestroyingHandle(Widget);
|
|
|
|
//DebugLn('TGtkWidgetSet.DestroyLCLComponent A ',GetWidgetClassName(Widget));
|
|
|
|
// if one of its widgets has the focus then unfocus
|
|
GtkWindow:=gtk_widget_get_toplevel(Widget);
|
|
if GtkWidgetIsA(GtkWindow,GTK_TYPE_WINDOW)
|
|
and (GetNearestLCLObject(PGtkWindow(GtkWindow)^.Focus_Widget)=Sender)
|
|
then begin
|
|
gtk_window_set_focus(PGtkWindow(GtkWindow),nil);
|
|
end;
|
|
|
|
if Sender is TControl then begin
|
|
if Sender is TCustomPage then begin
|
|
// a notebook always need at least one page
|
|
// -> if this is the last page, then add a dummy page
|
|
APage:=TCustomPage(Sender);
|
|
if (APage.Parent<>nil) and APage.Parent.HandleAllocated
|
|
and (APage.Parent is TCustomTabControl) then begin
|
|
NoteBookWidget:=PGtkNotebook(TCustomTabControl(APage.Parent).Handle);
|
|
if GetGtkNoteBookPageCount(NoteBookWidget)=1 then begin
|
|
AddDummyNoteBookPage(NoteBookWidget);
|
|
UpdateNoteBookClientWidget(TCustomTabControl(APage.Parent));
|
|
end;
|
|
end;
|
|
end;
|
|
end
|
|
else if Sender is TCommonDialog then begin
|
|
DestroyCommonDialogAddOns(TCommonDialog(Sender));
|
|
end;
|
|
|
|
// destroy widget and properties
|
|
DestroyConnectedWidget(Widget,false);
|
|
|
|
// clean up unneeded containers
|
|
if Sender is TMenuItem then begin
|
|
DestroyEmptySubmenu(TMenuItem(Sender));
|
|
end;
|
|
|
|
// mouse click messages
|
|
if LastLeft.Component=Sender then
|
|
LastLeft:=EmptyLastMouseClick;
|
|
if LastMiddle.Component=Sender then
|
|
LastMiddle:=EmptyLastMouseClick;
|
|
if LastRight.Component=Sender then
|
|
LastRight:=EmptyLastMouseClick;
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.FinishCreateHandle(const AWinControl: TWinControl;
|
|
Widget: PGtkWidget; const AParams: TCreateParams);
|
|
var
|
|
WidgetInfo: PWidgetInfo;
|
|
Allocation: TGTKAllocation;
|
|
begin
|
|
WidgetInfo := GetWidgetInfo(Widget,true); // Widget info already created in CreateAPIWidget
|
|
WidgetInfo^.LCLObject := AWinControl;
|
|
WidgetInfo^.Style := AParams.Style;
|
|
WidgetInfo^.ExStyle := AParams.ExStyle;
|
|
WidgetInfo^.WndProc := PtrUInt(AParams.WindowClass.lpfnWndProc);
|
|
|
|
// set allocation
|
|
Allocation.X := AParams.X;
|
|
Allocation.Y := AParams.Y;
|
|
Allocation.Width := AParams.Width;
|
|
Allocation.Height := AParams.Height;
|
|
gtk_widget_size_allocate(Widget, @Allocation);
|
|
|
|
Set_RC_Name(AWinControl, Widget);
|
|
TGtkWSWinControl.SetCallbacks(PGtkObject(Widget), AWinControl);
|
|
end;
|
|
|
|
procedure TGtkWidgetSet.DestroyConnectedWidget(Widget: PGtkWidget;
|
|
CheckIfDestroying: boolean);
|
|
var
|
|
FixWidget: PGtkWidget;
|
|
{$IFNDef GTK2}
|
|
Accelerators: PGSlist;
|
|
AccelEntry : PGtkAccelEntry;
|
|
{$Endif}
|
|
QueueItem : TGtkMessageQueueItem;
|
|
NextItem : TGtkMessageQueueItem;
|
|
MsgPtr: PMsg;
|
|
begin
|
|
if CheckIfDestroying then begin
|
|
if WidgetIsDestroyingHandle(Widget) then exit;
|
|
SetWidgetIsDestroyingHandle(Widget);
|
|
end;
|
|
|
|
FixWidget:=GetFixedWidget(Widget);
|
|
|
|
// Remove control accelerators - has to be done due to GTK+ bug?
|
|
//DebugLn('TGtkWidgetSet.DestroyLCLComponent B Widget=',GetWidgetDebugReport(Widget));
|
|
{$IFDef GTK1}
|
|
Accelerators:= gtk_accel_group_entries_from_object(PGtkObject(Widget));
|
|
while Accelerators <> nil do begin
|
|
AccelEntry:= Accelerators^.data;
|
|
Accelerators:= Accelerators^.next;
|
|
with AccelEntry^ do
|
|
gtk_accel_group_remove(accel_group, accelerator_key, accelerator_mods,
|
|
PGtkObject(Widget));
|
|
end;
|
|
{$EndIf}
|
|
ClearAccelKey(Widget);
|
|
|
|
// untransient
|
|
if GtkWidgetIsA(Widget,GTK_TYPE_WINDOW) then begin
|
|
UntransientWindow(PGtkWindow(Widget));
|
|
end;
|
|
|
|
// callbacks
|
|
RemoveCallbacks(Widget);
|
|
|
|
{$ifdef Gtk1}
|
|
//Gtk2 uses a different combobox widget class
|
|
// children
|
|
if GtkWidgetIsA(Widget,GTK_COMBO_GET_TYPE) then begin
|
|
g_signal_handlers_destroy(PGtkObject(PGtkCombo(Widget)^.Entry));
|
|
g_signal_handlers_destroy(PGtkObject(PGtkCombo(Widget)^.List));
|
|
//is really necessary to clear the text here?
|
|
gtk_entry_set_text(PGtkEntry(PGtkCombo(Widget)^.Entry), '');
|
|
end;
|
|
{$endif}
|
|
|
|
// update mouse capturing
|
|
if (MouseCaptureWidget=Widget) or (MouseCaptureWidget=FixWidget) then
|
|
MouseCaptureWidget:=nil;
|
|
|
|
// update clipboard widget
|
|
if (ClipboardWidget=Widget) or (ClipboardWidget=FixWidget) then
|
|
begin
|
|
// clipboard widget destroyed
|
|
if (Application<>nil) and (Application.MainForm<>nil)
|
|
and (Application.MainForm.HandleAllocated)
|
|
and (PGtkWidget(Application.MainForm.Handle)<>Widget) then
|
|
// there is still the main form left -> use it for clipboard
|
|
SetClipboardWidget(PGtkWidget(Application.MainForm.Handle))
|
|
else
|
|
// program closed -> close clipboard
|
|
SetClipboardWidget(nil);
|
|
end;
|
|
|
|
// update caret
|
|
if GtkWidgetIsA(Widget,GTKAPIWidget_GetType) then
|
|
DestroyCaret(HDC(PtrUInt(Widget)));
|
|
|
|
// remove pending size messages
|
|
UnsetResizeRequest(Widget);
|
|
FWidgetsResized.Remove(Widget);
|
|
if FixWidget<>Widget then
|
|
FFixWidgetsResized.Remove(FixWidget);
|
|
|
|
// destroy the widget
|
|
//DebugLn(['TGtkWidgetSet.DestroyConnectedWidget ',GetWidgetDebugReport(Widget)]);
|
|
DestroyWidget(Widget);
|
|
|
|
// remove all remaining messages to this widget
|
|
fMessageQueue.Lock;
|
|
try
|
|
QueueItem:=FMessageQueue.FirstMessageItem;
|
|
while (QueueItem<>nil) do begin
|
|
MsgPtr := QueueItem.Msg;
|
|
NextItem := TGtkMessagequeueItem(QueueItem.Next);
|
|
if (PGtkWidget(MsgPtr^.hWnd)=Widget) then
|
|
fMessageQueue.RemoveMessage(QueueItem,FPMF_All,true);
|
|
QueueItem := NextItem;
|
|
end;
|
|
finally
|
|
fMessageQueue.UnLock;
|
|
end;
|
|
end;
|
|
|
|
function TGtkWidgetSet.GetCompStyle(Sender : TObject) : Longint;
|
|
begin
|
|
Result := csNone;
|
|
if (Sender is TControl) then
|
|
Result := TControl(Sender).FCompStyle
|
|
else
|
|
if (Sender is TMenuItem) then
|
|
Result := TMenuItem(Sender).FCompStyle
|
|
else
|
|
if (Sender is TMenu) or (Sender is TPopupMenu)
|
|
then
|
|
Result := TMenu(Sender).FCompStyle
|
|
else
|
|
if (Sender is TCommonDialog)
|
|
then
|
|
result := TCommonDialog(Sender).FCompStyle;
|
|
end;
|
|
|
|
function TGtkWidgetSet.GetCaption(Sender : TObject) : String;
|
|
begin
|
|
Result := Sender.ClassName;
|
|
if (Sender is TControl) then
|
|
Result := TControl(Sender).Caption
|
|
else
|
|
if (Sender is TMenuItem) then
|
|
Result := TMenuItem(Sender).Caption;
|
|
|
|
if Result = '' then
|
|
Result := rsBlank;
|
|
end;
|
|
|
|
function TGtkWidgetSet.CreateAPIWidget(
|
|
AWinControl: TWinControl): PGtkWidget;
|
|
// currently only used for csFixed
|
|
var
|
|
Adjustment: PGTKAdjustment;
|
|
WinWidgetInfo: PWinWidgetInfo;
|
|
begin
|
|
Result := GTKAPIWidget_New;
|
|
WinWidgetInfo := GetWidgetInfo(Result, True);
|
|
WinWidgetInfo^.CoreWidget := PGTKAPIWidget(Result)^.Client;
|
|
WinWidgetInfo^.LCLObject := AWinControl;
|
|
|
|
gtk_scrolled_window_set_policy(PGTKScrolledWindow(Result),
|
|
GTK_POLICY_NEVER, GTK_POLICY_NEVER);
|
|
|
|
Adjustment :=
|
|
gtk_scrolled_window_get_vadjustment(PGTKScrolledWindow(Result));
|
|
if Adjustment <> nil
|
|
then with Adjustment^ do
|
|
begin
|
|
gtk_object_set_data(PGTKObject(Adjustment), odnScrollBar,
|
|
PGTKScrolledWindow(Result)^.VScrollBar);
|
|
Step_Increment := 1;
|
|
end;
|
|
|
|
Adjustment :=
|
|
gtk_scrolled_window_get_hadjustment(PGTKScrolledWindow(Result));
|
|
if Adjustment <> nil
|
|
then with Adjustment^ do
|
|
begin
|
|
gtk_object_set_data(PGTKObject(Adjustment), odnScrollBar,
|
|
PGTKScrolledWindow(Result)^.HScrollBar);
|
|
Step_Increment := 1;
|
|
end;
|
|
|
|
if AWinControl is TCustomControl then
|
|
GTKAPIWidget_SetShadowType(PGTKAPIWidget(Result),
|
|
BorderStyleShadowMap[TCustomControl(AWinControl).BorderStyle]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TGtkWidgetSet.CreateStatusBarPanel(StatusBar: TObject; Index: integer
|
|
): PGtkWidget;
|
|
|
|
Creates a new statusbar panel widget.
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.OldCreateStatusBarPanel(StatusBar: TObject; Index: integer
|
|
): PGtkWidget;
|
|
begin
|
|
Result:=gtk_statusbar_new;
|
|
gtk_widget_show(Result);
|
|
// other properties are set in UpdateStatusBarPanels
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TGtkWidgetSet.CreateSimpleClientAreaWidget(Sender: TObject;
|
|
NotOnParentsClientArea: boolean): PGtkWidget;
|
|
|
|
Create a fixed widget in a horizontal box
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.CreateSimpleClientAreaWidget(Sender: TObject;
|
|
NotOnParentsClientArea: boolean): PGtkWidget;
|
|
var
|
|
TempWidget: PGtkWidget;
|
|
WinWidgetInfo: PWinWidgetInfo;
|
|
begin
|
|
{$if defined(gtk1) or defined(GtkFixedWithWindow)}
|
|
// Fixed + GdkWindow
|
|
Result := gtk_hbox_new(false, 0);
|
|
TempWidget := CreateFixedClientWidget;
|
|
{$else}
|
|
// Fixed w/o GdkWindow
|
|
Result := gtk_event_box_new;
|
|
{ MG: Normally the event box should be made invisible as suggested
|
|
here: http://library.gnome.org/devel/gtk/stable/GtkEventBox.html#gtk-event-box-set-visible-window
|
|
But is has a sideeffect:
|
|
Sometimes the mouse events for gtk widgets without window don't get any
|
|
mouse events any longer.
|
|
For example: Add a PageControl (Page3, Page4) into a PageControl (Page1,Page2).
|
|
Start program. Click on Page2, which hides the inner PageControl. Then
|
|
click to return to Page1. Now the inner PageControl does no longer
|
|
receive mouse events and so you can not switch between Page3 and Page4.}
|
|
// MG: disabled: gtk_event_box_set_visible_window(PGtkEventBox(Result), False);
|
|
TempWidget := CreateFixedClientWidget(False);
|
|
{$ifend}
|
|
|
|
gtk_container_add(GTK_CONTAINER(Result), TempWidget);
|
|
gtk_widget_show(TempWidget);
|
|
if NotOnParentsClientArea then
|
|
begin
|
|
WinWidgetInfo:=GetWidgetInfo(Result, true);
|
|
Include(WinWidgetInfo^.Flags, wwiNotOnParentsClientArea);
|
|
end;
|
|
SetFixedWidget(Result, TempWidget);
|
|
SetMainWidget(Result, TempWidget);
|
|
|
|
// MG: should fix the invisible event box, but does not:
|
|
// gtk_widget_add_events (PGtkWidget(Result), GDK_BUTTON_PRESS_MASK);
|
|
|
|
gtk_widget_show(Result);
|
|
end;
|
|
|
|
function TGTKWidgetSet.CreateStandardCursor(ACursor: SmallInt): hCursor;
|
|
var
|
|
CursorValue: Integer;
|
|
begin
|
|
Result := 0;
|
|
if ACursor < crLow then Exit;
|
|
if ACursor > crHigh then Exit;
|
|
|
|
case TCursor(ACursor) of
|
|
crDefault: CursorValue := GDK_LEFT_PTR;
|
|
crArrow: CursorValue := GDK_Arrow;
|
|
crCross: CursorValue := GDK_Cross;
|
|
crIBeam: CursorValue := GDK_XTerm;
|
|
crSizeNESW: CursorValue := GDK_BOTTOM_LEFT_CORNER;
|
|
crSizeNS: CursorValue := GDK_SB_V_DOUBLE_ARROW;
|
|
crSizeNWSE: CursorValue := GDK_TOP_LEFT_CORNER;
|
|
crSizeWE: CursorValue := GDK_SB_H_DOUBLE_ARROW;
|
|
crSizeNW: CursorValue := GDK_TOP_LEFT_CORNER;
|
|
crSizeN: CursorValue := GDK_TOP_SIDE;
|
|
crSizeNE: CursorValue := GDK_TOP_RIGHT_CORNER;
|
|
crSizeW: CursorValue := GDK_LEFT_SIDE;
|
|
crSizeE: CursorValue := GDK_RIGHT_SIDE;
|
|
crSizeSW: CursorValue := GDK_BOTTOM_LEFT_CORNER;
|
|
crSizeS: CursorValue := GDK_BOTTOM_SIDE;
|
|
crSizeSE: CursorValue := GDK_BOTTOM_RIGHT_CORNER;
|
|
crUpArrow: CursorValue := GDK_LEFT_PTR;
|
|
crHourGlass:CursorValue := GDK_WATCH;
|
|
crHSplit: CursorValue := GDK_SB_H_DOUBLE_ARROW;
|
|
crVSplit: CursorValue := GDK_SB_V_DOUBLE_ARROW;
|
|
crAppStart: CursorValue := GDK_LEFT_PTR;
|
|
crHelp: CursorValue := GDK_QUESTION_ARROW;
|
|
crHandPoint:CursorValue := GDK_Hand2;
|
|
crSizeAll: CursorValue := GDK_FLEUR;
|
|
else
|
|
CursorValue := -1;
|
|
end;
|
|
if CursorValue <> -1 then
|
|
Result := hCursor(PtrUInt(gdk_cursor_new(CursorValue)));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.DestroyEmptySubmenu(Sender: TObject);
|
|
|
|
Used by DestroyLCLComponent to destroy empty submenus, when destroying the
|
|
last menu item.
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.DestroyEmptySubmenu(Sender: TObject);
|
|
var
|
|
LCLMenuItem: TMenuItem;
|
|
ParentLCLMenuItem: TMenuItem;
|
|
ParentMenuWidget: PGtkWidget;
|
|
ParentSubMenuWidget: PGtkWidget;
|
|
SubMenuWidget: PGtkMenu;
|
|
begin
|
|
if not (Sender is TMenuItem) then
|
|
RaiseGDBException('TGtkWidgetSet.DestroyEmptySubmenu');
|
|
// destroying a TMenuItem
|
|
LCLMenuItem:=TMenuItem(Sender);
|
|
// check if in a sub menu
|
|
if (LCLMenuItem.Parent=nil) then exit;
|
|
if not (LCLMenuItem.Parent is TMenuItem) then exit;
|
|
ParentLCLMenuItem:=TMenuItem(LCLMenuItem.Parent);
|
|
if not ParentLCLMenuItem.HandleAllocated then exit;
|
|
ParentMenuWidget:=PGtkWidget(ParentLCLMenuItem.Handle);
|
|
if not GtkWidgetIsA(ParentMenuWidget,GTK_TYPE_MENU_ITEM) then exit;
|
|
ParentSubMenuWidget:=PGTKMenuItem(ParentMenuWidget)^.submenu;
|
|
if not GtkWidgetIsA(ParentSubMenuWidget,GTK_TYPE_MENU) then exit;
|
|
SubMenuWidget:=PGTKMenu(ParentSubMenuWidget);
|
|
if SubMenuWidget^.menu_shell.children=nil then begin
|
|
gtk_widget_destroy(PgtkWidget(SubMenuWidget));
|
|
gtk_object_set_data(PGtkObject(ParentMenuWidget),'ContainerMenu',nil);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TGtkWidgetSet ShowHide
|
|
*Note: Show or hide a widget
|
|
------------------------------------------------------------------------------}
|
|
{$IFDEF VerboseGtkToDos}{$note TODO: move to wsclass }{$ENDIF}
|
|
procedure TGtkWidgetSet.SetVisible(Sender: TObject; const AVisible: Boolean);
|
|
|
|
procedure RaiseWrongClass;
|
|
begin
|
|
RaiseGDBException('TGtkWidgetSet.ShowHide Sender.ClassName='+Sender.ClassName);
|
|
end;
|
|
|
|
var
|
|
SenderWidget: PGTKWidget;
|
|
LCLControl: TWinControl;
|
|
Decor, Func : Longint;
|
|
AWindow: PGdkWindow;
|
|
ACustomForm: TCustomForm;
|
|
{$IFDEF GTK2}
|
|
CurWindowState: TWindowState;
|
|
{$ENDIF}
|
|
begin
|
|
if not (Sender is TWinControl) then
|
|
RaiseWrongClass;
|
|
if (Sender is TCustomForm) then
|
|
ACustomForm := TCustomForm(Sender)
|
|
else
|
|
ACustomForm := nil;
|
|
|
|
LCLControl:=TWinControl(Sender);
|
|
if not LCLControl.HandleAllocated then exit;
|
|
SenderWidget:=PgtkWidget(LCLControl.Handle);
|
|
//if (Sender is TForm) and (Sender.ClassName='TForm1') then
|
|
// DebugLn('[TGtkWidgetSet.ShowHide] START ',TControl(Sender).Name,':',Sender.ClassName,
|
|
// ' Visible=',TControl(Sender).Visible,' GtkVisible=',gtk_widget_visible(SenderWidget),
|
|
// ' GtkRealized=',gtk_widget_realized(SenderWidget),
|
|
// ' GtkMapped=',gtk_widget_mapped(SenderWidget),
|
|
// ' Should=',AVisible );
|
|
if AVisible then
|
|
begin
|
|
if (ACustomForm<>nil) and (ACustomForm.Parent=nil) then begin
|
|
// update shared accelerators
|
|
ShareWindowAccelGroups(SenderWidget);
|
|
end;
|
|
|
|
// before making the widget visible, set the position and size
|
|
// this is not possible for windows - for windows position will be setted
|
|
// after widget become visible
|
|
if FWidgetsWithResizeRequest.Contains(SenderWidget) then
|
|
begin
|
|
if (ACustomForm<>nil) and (ACustomForm.Parent=nil) then
|
|
begin
|
|
// top level control (a form without parent)
|
|
{$IFDEF VerboseFormPositioning}
|
|
DebugLn('VFP [TGtkWidgetSet.ShowHide] A set bounds ',
|
|
LCLControl.Name,':',LCLControl.ClassName,
|
|
' Window=',dbgs(GetControlWindow(SenderWidget)<>nil),
|
|
' ',dbgs(LCLControl.Left),',',dbgs(LCLControl.Top),
|
|
',',dbgs(LCLControl.Width),',',dbgs(LCLControl.Height));
|
|
{$ENDIF}
|
|
SetWindowSizeAndPosition(PgtkWindow(SenderWidget),LCLControl);
|
|
end
|
|
else
|
|
if (LCLControl.Parent<>nil) then
|
|
begin
|
|
// resize widget
|
|
{$IFDEF VerboseSizeMsg}
|
|
DebugLn(['TGtkWidgetSet.ShowHide ',DbgSName(LCLControl)]);
|
|
{$ENDIF}
|
|
SetWidgetSizeAndPosition(LCLControl);
|
|
end;
|
|
{$ifndef windows}
|
|
UnsetResizeRequest(SenderWidget);
|
|
{$endif}
|
|
end;
|
|
|
|
if (ACustomForm<>nil) and (ACustomForm.Parent=nil) then
|
|
begin
|
|
If (ACustomForm.BorderStyle <> bsSizeable) or
|
|
((ACustomForm.FormStyle in fsAllStayOnTop)
|
|
and (not (csDesigning in ACustomForm.ComponentState)))
|
|
then begin
|
|
Decor := GetWindowDecorations(ACustomForm);
|
|
Func := GetWindowFunction(ACustomForm);
|
|
gtk_widget_realize(SenderWidget);
|
|
AWindow:=GetControlWindow(SenderWidget);
|
|
gdk_window_set_decorations(AWindow, decor);
|
|
gdk_window_set_functions(AWindow, func);
|
|
end;
|
|
ShareWindowAccelGroups(SenderWidget);
|
|
|
|
// capturing is always gtkwindow dependent. On showing a new window
|
|
// the gtk will put a new widget on the grab stack.
|
|
// -> release our capture
|
|
ReleaseMouseCapture;
|
|
end;
|
|
|
|
if gtk_widget_visible(SenderWidget) then
|
|
exit;
|
|
|
|
gtk_widget_show(SenderWidget);
|
|
|
|
{$IFDEF GTK2}
|
|
if (ACustomForm <> nil) and
|
|
(ACustomForm.Parent = nil) and
|
|
(ACustomForm.ParentWindow = 0) then
|
|
begin
|
|
CurWindowState:=ACustomForm.WindowState;
|
|
if csDesigning in ACustomForm.ComponentState then
|
|
CurWindowState:=wsNormal;
|
|
case CurWindowState of
|
|
wsNormal:
|
|
begin
|
|
gtk_window_deiconify(PGtkWindow(SenderWidget));
|
|
gtk_window_unmaximize(PGtkWindow(SenderWidget));
|
|
end;
|
|
wsMaximized: gtk_window_maximize(PGtkWindow(SenderWidget));
|
|
wsMinimized: gtk_window_iconify(PGtkWindow(SenderWidget));
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
end
|
|
else begin
|
|
|
|
if (ACustomForm<>nil) then
|
|
UnshareWindowAccelGroups(SenderWidget);
|
|
|
|
if not gtk_widget_visible(SenderWidget) then
|
|
exit;
|
|
|
|
{$IFDEF GTK2}
|
|
// save previous position
|
|
if ACustomForm <> nil then
|
|
begin
|
|
if (ACustomForm is TForm) and
|
|
not (ACustomForm.FormStyle in [fsMDIChild, fsSplash])
|
|
and (ACustomForm.BorderStyle <> bsNone) then
|
|
SetResizeRequest(SenderWidget);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
gtk_widget_hide(SenderWidget);
|
|
|
|
if GtkWidgetIsA(SenderWidget,GTK_TYPE_WINDOW) then begin
|
|
{$IFDEF VerboseTransient}
|
|
DebugLn('TGtkWidgetSet.ShowHide HIDE ',Sender.ClassName);
|
|
{$ENDIF}
|
|
UntransientWindow(PGtkWindow(SenderWidget));
|
|
end;
|
|
end;
|
|
|
|
if GtkWidgetIsA(SenderWidget,GTK_TYPE_WINDOW) then begin
|
|
// make sure when hiding a window, that at least the main window
|
|
// is selectable via the window manager
|
|
if (Application<>nil) and (Application.MainForm<>nil)
|
|
and (Application.MainForm.HandleAllocated) then begin
|
|
SetFormShowInTaskbar(Application.MainForm,stAlways);
|
|
end;
|
|
end;
|
|
|
|
//if Sender is TCustomForm then
|
|
// DebugLn('[TGtkWidgetSet.ShowHide] END ',Sender.ClassName,' Window=',FormWidget^.Window<>nil);
|
|
end;
|
|
|
|
function TGTKWidgetSet.DragImageList_BeginDrag(APixmap: PGdkPixmap; AMask: PGdkBitmap; AHotSpot: TPoint): Boolean;
|
|
var
|
|
w, h: gint;
|
|
begin
|
|
if FDragImageList = nil then
|
|
begin
|
|
FDragImageList := gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gdk_drawable_get_size(APixmap, @w, @h);
|
|
gtk_window_set_default_size(PGtkWindow(FDragImageList), w, h);
|
|
gtk_widget_realize(FDragImageList);
|
|
gdk_window_set_decorations(FDragImageList^.window, 0);
|
|
gdk_window_set_functions(FDragImageList^.window, GDK_FUNC_RESIZE or GDK_FUNC_CLOSE);
|
|
FDragImageListIcon := gtk_pixmap_new(APixmap, AMask);
|
|
gtk_container_add(PGtkContainer(FDragImageList), FDragImageListIcon);
|
|
gtk_widget_show(FDragImageListIcon);
|
|
// make window transparent outside mask
|
|
gdk_window_shape_combine_mask(FDragImageList^.window, AMask, 0, 0);
|
|
FDragHotStop := AHotSpot;
|
|
end;
|
|
Result := FDragImageList <> nil;
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.DragImageList_EndDrag;
|
|
begin
|
|
if FDragImageList <> nil then
|
|
begin
|
|
if FDragImageListIcon <> nil then
|
|
gtk_widget_destroy(FDragImageListIcon);
|
|
gtk_widget_destroy(FDragImageList);
|
|
FDragImageList := nil;
|
|
end;
|
|
end;
|
|
|
|
function TGTKWidgetSet.DragImageList_DragMove(X, Y: Integer): Boolean;
|
|
begin
|
|
Result := FDragImageList <> nil;
|
|
if Result then
|
|
begin
|
|
if gdk_window_is_visible(FDragImageList^.Window) then
|
|
gdk_window_raise(FDragImageList^.Window);
|
|
gdk_window_move(FDragImageList^.Window, X - FDragHotStop.X, Y - FDragHotStop.Y);
|
|
end;
|
|
end;
|
|
|
|
function TGTKWidgetSet.DragImageList_SetVisible(NewVisible: Boolean): Boolean;
|
|
begin
|
|
Result := FDragImageList <> nil;
|
|
if Result then
|
|
if NewVisible then
|
|
gtk_widget_show(FDragImageList)
|
|
else
|
|
gtk_widget_hide(FDragImageList);
|
|
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 TGtkWidgetSet.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;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure AddDummyNoteBookPage(NoteBookWidget: PGtkNoteBook);
|
|
|
|
Adds the dummy page.
|
|
A gtk notebook must have at least one page, but TCustomTabControl also allows
|
|
no pages at all. Therefore at least a dummy page is added. This dummy page is
|
|
removed as soon as other pages are added.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.AddDummyNoteBookPage(NoteBookWidget: PGtkNoteBook);
|
|
var
|
|
DummyWidget: PGtkWidget;
|
|
ALabel: PGtkWidget;
|
|
MenuLabel: PGtkWidget;
|
|
{$IFDEF Gtk}
|
|
AWidget: PGtkWidget;
|
|
{$ENDIF}
|
|
begin
|
|
if NoteBookWidget=nil then exit;
|
|
DummyWidget:=GetGtkNoteBookDummyPage(NoteBookWidget);
|
|
if (DummyWidget=nil) then begin
|
|
// the notebook has no pages
|
|
// -> add a dummy page
|
|
DummyWidget := gtk_hbox_new(false, 0);
|
|
{$IFDEF Gtk}
|
|
AWidget := CreateFixedClientWidget{$IFNDEF GtkFixedWithWindow}(false){$ENDIF};
|
|
gtk_widget_show(AWidget);
|
|
//gtk_box_pack_start_defaults(GTK_BOX(DummyWidget),AWidget);
|
|
gtk_container_add(GTK_CONTAINER(DummyWidget), AWidget);
|
|
{$ENDIF}
|
|
gtk_widget_show(DummyWidget);
|
|
ALabel:=gtk_label_new('');
|
|
gtk_widget_show(ALabel);
|
|
MenuLabel:=gtk_label_new('');
|
|
gtk_widget_show(MenuLabel);
|
|
gtk_notebook_append_page_menu(NoteBookWidget,DummyWidget,ALabel,MenuLabel);
|
|
SetGtkNoteBookDummyPage(NoteBookWidget,DummyWidget);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.SetPixel
|
|
Params: Sender : the lcl object which called this func via SendMessage
|
|
Data : pointer to a TLMSetGetPixel record
|
|
Returns: nothing
|
|
|
|
Set the color of the specified pixel on the window?screen?object?
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.DCSetPixel(CanvasHandle: HDC; X, Y: integer; AColor: TGraphicsColor);
|
|
var
|
|
DC : TGtkDeviceContext absolute CanvasHandle;
|
|
DCOrigin: TPoint;
|
|
GDKColor: TGDKColor;
|
|
begin
|
|
if (DC = nil) or (DC.Drawable = nil) then exit;
|
|
|
|
DCOrigin := DC.Offset;
|
|
inc(X,DCOrigin.X);
|
|
inc(Y,DCOrigin.Y);
|
|
|
|
DC.SelectedColors := dcscCustom;
|
|
GDKColor := AllocGDKColor(ColorToRGB(AColor));
|
|
gdk_gc_set_foreground(DC.GC, @GDKColor);
|
|
{$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
|
|
gdk_draw_point(DC.Drawable, DC.GC, X, Y);
|
|
{$IFDEF DebugGDK}EndGDKErrorTrap;{$ENDIF}
|
|
end;
|
|
|
|
procedure TGtkWidgetSet.DCRedraw(CanvasHandle: HDC);
|
|
var
|
|
fWindow :pGdkWindow;
|
|
widget : PgtkWIdget;
|
|
PixMap : pgdkPixMap;
|
|
Child: PGtkWidget;
|
|
begin
|
|
//DebugLn('Trace:In AutoRedraw in GTKObject');
|
|
|
|
Child := PgtkWidget(CanvasHandle);
|
|
Widget := GetFixedWidget(Child);
|
|
pixmap := gtk_Object_get_data(pgtkobject(Child),'Pixmap');
|
|
if PixMap = nil then Exit;
|
|
fWindow := GetControlWindow(widget);
|
|
|
|
if fWindow<>nil then begin
|
|
{$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
|
|
gdk_draw_pixmap(fwindow,
|
|
gtk_widget_get_style(widget)^.fg_gc[GTK_WIDGET_STATE (widget)],
|
|
pixmap,
|
|
0,0,
|
|
0,0,
|
|
pgtkwidget(widget)^.allocation.width,
|
|
pgtkwidget(widget)^.allocation.height);
|
|
{$IFDEF DebugGDK}EndGDKErrorTrap;{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkWidgetSet.GetPixel
|
|
Params: Sender : the lcl object which called this func via SenMessage
|
|
Data : pointer to a TLMSetGetPixel record
|
|
Returns: nothing
|
|
|
|
Get the color of the specified pixel on the window?screen?object?
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.DCGetPixel(CanvasHandle: HDC; X, Y: integer): TGraphicsColor;
|
|
var
|
|
DC : TGtkDeviceContext absolute CanvasHandle;
|
|
Image : pGDKImage;
|
|
GDKColor: TGDKColor;
|
|
Colormap : PGDKColormap;
|
|
DCOrigin: TPoint;
|
|
MaxX, MaxY: integer;
|
|
Pixel: LongWord;
|
|
begin
|
|
Result := clNone;
|
|
if (DC = nil) or (DC.Drawable = nil) then Exit;
|
|
|
|
DCOrigin := DC.Offset;
|
|
inc(X,DCOrigin.X);
|
|
inc(Y,DCOrigin.Y);
|
|
|
|
gdk_drawable_get_size(DC.Drawable, @MaxX, @MaxY);
|
|
if (X<0) or (Y<0) or (X>=MaxX) or (Y>=MaxY) then exit;
|
|
|
|
Image := gdk_drawable_get_image(DC.Drawable,X,Y,1,1);
|
|
if Image = nil then exit;
|
|
|
|
{$ifdef Gtk1}
|
|
// previously gdk_image_get_colormap(image) was used, implementation
|
|
// was casting GdkImage to GdkWindow which is not valid and cause AVs
|
|
if gdk_window_get_type(PGdkWindow(DC.Drawable))= GDK_WINDOW_PIXMAP then
|
|
colormap := nil // pixmaps are created with null colormap, get system one instead
|
|
else
|
|
colormap := gdk_window_get_colormap(PGdkWindow(DC.Drawable));
|
|
{$else}
|
|
colormap := gdk_image_get_colormap(image);
|
|
if colormap = nil then
|
|
colormap := gdk_drawable_get_colormap(DC.Drawable);
|
|
{$endif}
|
|
|
|
|
|
if colormap = nil then
|
|
colormap := gdk_colormap_get_system;
|
|
|
|
Pixel:=gdk_image_get_pixel(Image,0,0);
|
|
FillChar(GDKColor, SizeOf(GDKColor),0);
|
|
// does not work with TBitmap.Canvas
|
|
gdk_colormap_query_color(colormap, Pixel, @GDKColor);
|
|
|
|
gdk_image_unref(Image);
|
|
|
|
Result := TGDKColorToTColor(GDKColor);
|
|
end;
|
|
|
|
{ TODO: move this ``LM_GETVALUE'' spinedit code someplace useful
|
|
|
|
csSpinEdit :
|
|
Begin
|
|
Single(Data^):=gtk_spin_button_get_value_As_Float(PgtkSpinButton(Handle));
|
|
end;
|
|
}
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: IsValidDC
|
|
Params: DC: a (LCL) devicecontext
|
|
Returns: True if valid
|
|
|
|
Checks if the given DC is valid.
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.IsValidDC(const DC: HDC): Boolean;
|
|
begin
|
|
Result := FDeviceContexts.Contains(Pointer(DC));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: IsValidGDIObject
|
|
Params: GDIObject: a (LCL) gdiObject
|
|
Returns: True if valid
|
|
|
|
Checks if the given GDIObject is valid (e.g. known to the gtk interface).
|
|
This is a quick consistency check to avoid working with dangling pointers.
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.IsValidGDIObject(const AGDIObj: HGDIOBJ): Boolean;
|
|
var
|
|
GdiObject: PGdiObject absolute AGDIObj;
|
|
begin
|
|
Result := (AGDIObj <> 0) and FGDIObjects.Contains(GDIObject);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: IsValidGDIObjectType
|
|
Params: GDIObject: a (LCL) gdiObject
|
|
GDIType: the requested type
|
|
Returns: True if valid
|
|
|
|
Checks if the given GDIObject is valid and the GDItype is the requested type
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.IsValidGDIObjectType(
|
|
const GDIObject: HGDIOBJ; const GDIType: TGDIType): Boolean;
|
|
begin
|
|
Result := IsValidGDIObject(GDIObject)
|
|
and (PGdiObject(GDIObject)^.GDIType = GDIType);
|
|
end;
|
|
|
|
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: NewDC
|
|
Params: none
|
|
Returns: a gtkwinapi DeviceContext
|
|
|
|
Creates a raw DC and adds it to FDeviceContexts.
|
|
|
|
Used internally by: CreateCompatibleDC, CreateDCForWidget and SaveDC
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.NewDC: TGtkDeviceContext;
|
|
begin
|
|
//DebugLn(Format('Trace:> [TGtkWidgetSet.NewDC]', []));
|
|
|
|
if FDCManager = nil
|
|
then begin
|
|
FDCManager := TDeviceContextMemManager.Create(GetDeviceContextClass);
|
|
FDCManager.MinimumFreeCount := 1000;
|
|
end;
|
|
Result := FDCManager.NewDeviceContext;
|
|
{$IFDEF DebugLCLComponents}
|
|
DebugDeviceContexts.MarkCreated(Result,'TGtkWidgetSet.NewDC');
|
|
{$ENDIF}
|
|
|
|
FDeviceContexts.Add(Result);
|
|
|
|
{$ifdef TraceGdiCalls}
|
|
FillStackAddrs(get_caller_frame(get_frame), @Result.StackAddrs);
|
|
{$endif}
|
|
//DebugLn(['[TGtkWidgetSet.NewDC] ',DbgS(Result),' ',FDeviceContexts.Count]);
|
|
//DebugLn(Format('Trace:< [TGtkWidgetSet.NewDC] FDeviceContexts[%d] --> 0x%p', [n, Result]));
|
|
end;
|
|
|
|
function TGTKWidgetSet.FindDCWithGDIObject(GDIObject: PGdiObject
|
|
): TGtkDeviceContext;
|
|
var
|
|
HashItem: PDynHashArrayItem;
|
|
DC: TGtkDeviceContext;
|
|
g: TGDIType;
|
|
Cnt: Integer;
|
|
begin
|
|
Result:=nil;
|
|
if GdiObject=nil then exit;
|
|
HashItem:=FDeviceContexts.FirstHashItem;
|
|
Cnt:=0;
|
|
while HashItem<>nil do begin
|
|
DC:=TGtkDeviceContext(HashItem^.Item);
|
|
for g:=Low(TGDIType) to High(TGDIType) do
|
|
if DC.GDIObjects[g]=GdiObject then exit(DC);
|
|
inc(Cnt);
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
if Cnt<>FDeviceContexts.Count then
|
|
RaiseGDBException('');
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.DisposeDC(DC: PDeviceContext);
|
|
|
|
Disposes a DC
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.DisposeDC(aDC: TGtkDeviceContext);
|
|
begin
|
|
if not FDeviceContexts.Contains(aDC) then Exit;
|
|
|
|
FDeviceContexts.Remove(aDC);
|
|
|
|
{$IFDEF DebugLCLComponents}
|
|
DebugDeviceContexts.MarkDestroyed(ADC);
|
|
{$ENDIF}
|
|
FDCManager.DisposeDeviceContext(ADC);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TGtkWidgetSet.CreateDCForWidget(TheWidget: PGtkWidget;
|
|
TheWindow: PGdkWindow; WithChildWindows: boolean): HDC;
|
|
|
|
Creates an initial DC
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.CreateDCForWidget(AWidget: PGtkWidget; AWindow: PGdkWindow;
|
|
AWithChildWindows: Boolean; ADoubleBuffer: PGdkDrawable): HDC;
|
|
var
|
|
DC: TGtkDeviceContext absolute Result;
|
|
begin
|
|
DC := NewDC;
|
|
DC.SetWidget(AWidget, AWindow, AWithChildWindows, ADoubleBuffer);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TGtkWidgetSet.GetDoubleBufferedDC(Handle: HWND): HDC;
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.GetDoubleBufferedDC(Handle: HWND): HDC;
|
|
var
|
|
Widget: PGtkWidget;
|
|
WidgetInfo: PWinWidgetInfo;
|
|
AWindow: PGdkWindow;
|
|
Width, Height: integer;
|
|
BufferWidth, BufferHeight: integer;
|
|
DoubleBuffer: PGdkPixmap;
|
|
BufferCreated: Boolean;
|
|
DevContext: TGtkDeviceContext absolute Result;
|
|
CaretWasVisible: Boolean;
|
|
MainWidget: PGtkWidget;
|
|
GC: PGdkGC;
|
|
//LCLObject: TObject;
|
|
//x, y: integer;
|
|
begin
|
|
Result:=0;
|
|
Widget:=PGtkWidget(Handle);
|
|
{$IFDEF VerboseDoubleBuffer}
|
|
DebugLn('TGtkWidgetSet.GetDoubleBufferedDC ',GetWidgetClassName(Widget));
|
|
{$ENDIF}
|
|
WidgetInfo:=GetWidgetInfo(Widget,true);
|
|
AWindow:=Widget^.Window;
|
|
Width:=Widget^.allocation.width;
|
|
Height:=Widget^.allocation.height;
|
|
// create or resize DoubleBuffer
|
|
DoubleBuffer:=WidgetInfo^.DoubleBuffer;
|
|
if DoubleBuffer<>nil then begin
|
|
gdk_window_get_size(DoubleBuffer,@BufferWidth,@BufferHeight);
|
|
{$IFDEF VerboseDoubleBuffer}
|
|
DebugLn('TGtkWidgetSet.GetDoubleBufferedDC Checking ',
|
|
' Width=',Width,' Height=',Height,
|
|
' BufferWidth=',BufferWidth,' BufferHeight=',BufferHeight
|
|
);
|
|
{$ENDIF}
|
|
// lazy update of buffer
|
|
if (BufferWidth<Width) or (BufferHeight<Height)
|
|
or (BufferWidth>(Width*2+20)) or (BufferHeight>(Height*2+20))
|
|
then begin
|
|
{$IFDEF VerboseDoubleBuffer}
|
|
DebugLn('TGtkWidgetSet.GetDoubleBufferedDC Destroying old double buffer ');
|
|
{$ENDIF}
|
|
gdk_pixmap_unref(DoubleBuffer);
|
|
DoubleBuffer:=nil;
|
|
WidgetInfo^.DoubleBuffer:=nil;
|
|
end;
|
|
end;
|
|
BufferCreated:=false;
|
|
if DoubleBuffer=nil then begin
|
|
// create DoubleBuffer
|
|
{$IFDEF VerboseDoubleBuffer}
|
|
DebugLn('TGtkWidgetSet.GetDoubleBufferedDC Creating double buffer ',
|
|
' Width=',Width,' Height=',Height);
|
|
{$ENDIF}
|
|
DoubleBuffer:=gdk_pixmap_new(AWindow,Width,Height,-1);
|
|
WidgetInfo^.DoubleBuffer := DoubleBuffer;
|
|
BufferCreated:=true;
|
|
end;
|
|
|
|
// create DC for double buffer
|
|
Result := CreateDCForWidget(Widget, Widget^.Window, False, DoubleBuffer);
|
|
|
|
|
|
if BufferCreated
|
|
then begin
|
|
// create GC
|
|
GC:=DevContext.GC;
|
|
// copy old context to buffer
|
|
gdk_gc_set_clip_region(GC, nil);
|
|
gdk_gc_set_clip_rectangle(GC, nil);
|
|
|
|
// hide caret
|
|
HideCaretOfWidgetGroup(Widget,MainWidget,CaretWasVisible);
|
|
// copy
|
|
gdk_window_copy_area(DoubleBuffer, GC,0,0,
|
|
Widget^.Window,0,0,Width,Height);
|
|
|
|
{LCLObject:=GetParentLCLObject(Widget);
|
|
DebugLn('TGtkWidgetSet.GetDoubleBufferedDC ',DbgS(Widget),8),'=',GetWidgetClassName(Widget),' ',DbgS(Cardinal(LCLObject));
|
|
if (LCLObject is TPanel)
|
|
and (csDesigning in TPanel(LCLObject).ComponentState) then begin
|
|
gdk_window_get_origin(Widget^.Window,@x,@y);
|
|
DebugLn('TGtkWidgetSet.BeginPaint ',TPanel(LCLObject).Name,':',TPanel(LCLObject).ClassName,
|
|
' Widget=',GetWidgetClassName(Widget),
|
|
' Origin=',x,',',y,
|
|
' ',Widget^.allocation.x,',',Widget^.allocation.y);
|
|
end;}
|
|
|
|
// restore caret
|
|
if CaretWasVisible then
|
|
GTKAPIWidget_ShowCaret(PGTKAPIWidget(MainWidget));
|
|
end;
|
|
{$IFDEF VerboseDoubleBuffer}
|
|
DebugLn('TGtkWidgetSet.GetDoubleBufferedDC DC=',DbgS(Result));
|
|
{$ENDIF}
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: NewGDIObject
|
|
Params: none
|
|
Returns: a gtkwinapi DeviceContext
|
|
|
|
Creates an initial GDIObject of GDIType.
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.NewGDIObject(const GDIType: TGDIType): PGdiObject;
|
|
begin
|
|
//DebugLn(Format('Trace:> [TGtkWidgetSet.NewGDIObject]', []));
|
|
Result:=GtkDef.InternalNewPGDIObject;
|
|
{$ifdef TraceGdiCalls}
|
|
FillStackAddrs(get_caller_frame(get_frame), @Result^.StackAddrs);
|
|
{$endif}
|
|
Result^.GDIType := GDIType;
|
|
Result^.Shared := False;
|
|
inc(Result^.RefCount);
|
|
FGDIObjects.Add(Result);
|
|
//DebugLn('[TGtkWidgetSet.NewGDIObject] ',DbgS(Result),' ',FGDIObjects.Count);
|
|
//DebugLn(Format('Trace:< [TGtkWidgetSet.NewGDIObject] FGDIObjects --> 0x%p', [Result]));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: NewGDIObject
|
|
Params: GdiObject: PGdiObject
|
|
Returns: none
|
|
|
|
Dispose a GdiObject
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.DisposeGDIObject(GDIObject: PGdiObject);
|
|
begin
|
|
if FGDIObjects.Contains(GDIObject) then
|
|
begin
|
|
FGDIObjects.Remove(GDIObject);
|
|
GtkDef.InternalDisposePGDIObject(GDIObject);
|
|
end
|
|
else
|
|
RaiseGDBException('');
|
|
end;
|
|
|
|
function TGTKWidgetSet.ReleaseGDIObject(GdiObject: PGdiObject): boolean;
|
|
|
|
procedure RaiseGDIObjectIsStillUsed;
|
|
var
|
|
CurGDIObject: PGDIObject;
|
|
DC: TGtkDeviceContext;
|
|
begin
|
|
{$ifdef TraceGdiCalls}
|
|
DebugLn();
|
|
DebugLn('TGtkWidgetSet.ReleaseGDIObject: TraceCall for still used object: ');
|
|
DumpBackTrace(PgdiObject(GdiObject)^.StackAddrs);
|
|
DebugLn();
|
|
DebugLn('Exception will follow:');
|
|
DebugLn();
|
|
{$endif}
|
|
// do not raise an exception, because this is a common bug in many programs
|
|
// just give a warning
|
|
CurGDIObject:=PGdiObject(GdiObject);
|
|
debugln('TGtkWidgetSet.ReleaseGDIObject GdiObject='+dbgs(CurGDIObject)
|
|
+' '+dbgs(CurGDIObject^.GDIType)
|
|
+' is still used. DCCount='+dbgs(CurGDIObject^.DCCount));
|
|
DC:=FindDCWithGDIObject(CurGDIObject);
|
|
if DC<>nil then begin
|
|
DebugLn(['DC: ',dbgs(Pointer(DC)),' ',
|
|
GetWidgetDebugReport(DC.Widget)]);
|
|
end else begin
|
|
DebugLn(['No DC found with this GDIObject => either the DCCount is wrong or the DC is not in the DC list']);
|
|
end;
|
|
//DumpStack;
|
|
//RaiseGDBException('');
|
|
end;
|
|
|
|
procedure RaiseInvalidGDIOwner;
|
|
var
|
|
o: PGDIObject;
|
|
begin
|
|
{$ifdef TraceGdiCalls}
|
|
DebugLn();
|
|
DebugLn('TGtkWidgetSet.ReleaseGDIObject: TraceCall for invalid object: ');
|
|
DumpBackTrace(PgdiObject(GdiObject)^.StackAddrs);
|
|
DebugLn();
|
|
DebugLn('Exception will follow:');
|
|
DebugLn();
|
|
{$endif}
|
|
o:=PGdiObject(GdiObject);
|
|
RaiseGDBException('TGtkWidgetSet.ReleaseGDIObject invalid owner of'
|
|
+' GdiObject='+dbgs(o)
|
|
+' Owner='+dbgs(o^.Owner)
|
|
+' Owner.OwnedGDIObjects='+dbgs(o^.Owner.OwnedGDIObjects[o^.GDIType]));
|
|
end;
|
|
|
|
begin
|
|
if GDIObject = nil then
|
|
begin
|
|
Result := True;
|
|
exit;
|
|
end;
|
|
{$IFDEF DebugLCLComponents}
|
|
if DebugGdiObjects.IsDestroyed(GDIObject) then
|
|
begin
|
|
DebugLn(['TGtkWidgetSet.ReleaseGDIObject object already deleted ',GDIObject]);
|
|
debugln(DebugGdiObjects.GetInfo(GDIObject,true));
|
|
Halt;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
with PGdiObject(GDIObject)^ do
|
|
begin
|
|
dec(RefCount);
|
|
if (RefCount > 0) or Shared then
|
|
begin
|
|
Result := True;
|
|
exit;
|
|
end;
|
|
if DCCount > 0 then
|
|
begin
|
|
RaiseGDIObjectIsStillUsed;
|
|
exit(False);
|
|
end;
|
|
|
|
if Owner <> nil then
|
|
begin
|
|
if Owner.OwnedGDIObjects[GDIType] <> PGdiObject(GDIObject) then
|
|
RaiseInvalidGDIOwner;
|
|
Owner.OwnedGDIObjects[GDIType] := nil;
|
|
end;
|
|
|
|
case GDIType of
|
|
gdiFont:
|
|
begin
|
|
if GDIFontObject <> nil then
|
|
begin
|
|
//DebugLn(['TGtkWidgetSet.DeleteObject GDIObject=',dbgs(Pointer(PtrInt(GDIObject))),' GDIFontObject=',dbgs(GDIFontObject)]);
|
|
FontCache.Unreference(GDIFontObject);
|
|
end;
|
|
end;
|
|
gdiBrush:
|
|
begin
|
|
{$IFDEF DebugGDKTraps}
|
|
BeginGDKErrorTrap;
|
|
{$ENDIF}
|
|
{$IFDEF DebugGDIBrush}
|
|
debugln('TGtkWidgetSet.DeleteObject gdiBrush: ',DbgS(GdiObject));
|
|
//if Cardinal(GdiObject)=$404826F4 then RaiseGDBException('');
|
|
{$ENDIF}
|
|
if (GDIBrushPixmap <> nil) then
|
|
gdk_pixmap_unref(GDIBrushPixmap);
|
|
{$IFDEF DebugGDKTraps}
|
|
EndGDKErrorTrap;
|
|
{$ENDIF}
|
|
|
|
FreeGDIColor(@GDIBrushColor);
|
|
end;
|
|
gdiBitmap:
|
|
begin
|
|
{$IFDEF DebugGDKTraps}
|
|
BeginGDKErrorTrap;
|
|
{$ENDIF}
|
|
case GDIBitmapType of
|
|
gbBitmap:
|
|
begin
|
|
if GDIBitmapObject <> nil then
|
|
gdk_bitmap_unref(GDIBitmapObject);
|
|
end;
|
|
gbPixmap:
|
|
begin
|
|
if GDIPixmapObject.Image <> nil then
|
|
gdk_pixmap_unref(GDIPixmapObject.Image);
|
|
if GDIPixmapObject.Mask <> nil then
|
|
gdk_bitmap_unref(GDIPixmapObject.Mask);
|
|
end;
|
|
gbPixbuf:
|
|
begin
|
|
if GDIPixbufObject <> nil then
|
|
gdk_pixbuf_unref(GDIPixbufObject);
|
|
end;
|
|
end;
|
|
|
|
if (Visual <> nil) and (not SystemVisual) then
|
|
gdk_visual_unref(Visual);
|
|
if Colormap <> nil then
|
|
gdk_colormap_unref(Colormap);
|
|
{$IFDEF DebugGDKTraps}
|
|
EndGDKErrorTrap;
|
|
{$ENDIF}
|
|
end;
|
|
gdiPen:
|
|
begin
|
|
FreeGDIColor(@GDIPenColor);
|
|
FreeMem(GDIPenDashes);
|
|
end;
|
|
gdiRegion:
|
|
begin
|
|
if (GDIRegionObject <> nil) then
|
|
gdk_region_destroy(GDIRegionObject);
|
|
end;
|
|
gdiPalette:
|
|
begin
|
|
{$IFDEF DebugGDKTraps}
|
|
BeginGDKErrorTrap;
|
|
{$ENDIF}
|
|
If PaletteVisual <> nil then
|
|
gdk_visual_unref(PaletteVisual);
|
|
If PaletteColormap <> nil then
|
|
gdk_colormap_unref(PaletteColormap);
|
|
{$IFDEF DebugGDKTraps}
|
|
EndGDKErrorTrap;
|
|
{$ENDIF}
|
|
|
|
FreeAndNil(RGBTable);
|
|
FreeAndNil(IndexTable);
|
|
end;
|
|
else begin
|
|
Result:= false;
|
|
DebugLn('[TGtkWidgetSet.DeleteObject] TODO : Unimplemented GDI type');
|
|
//DebugLn('Trace:TODO : Unimplemented GDI object in delete object');
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Dispose of the GDI object }
|
|
//DebugLn('[TGtkWidgetSet.DeleteObject] ',Result,' ',DbgS(GDIObject,8),' ',FGDIObjects.Count);
|
|
DisposeGDIObject(PGDIObject(GDIObject));
|
|
end;
|
|
|
|
procedure TGTKWidgetSet.ReferenceGDIObject(GdiObject: PGdiObject);
|
|
begin
|
|
inc(GdiObject^.RefCount);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: CreateDefaultBrush
|
|
Params: none
|
|
Returns: a Brush GDIObject
|
|
|
|
Creates an default brush, used for initial values
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.CreateDefaultBrush: PGdiObject;
|
|
begin
|
|
//debugln(' TGtkWidgetSet.CreateDefaultBrush ->');
|
|
Result := NewGDIObject(gdiBrush);
|
|
{$IFDEF DebugGDIBrush}
|
|
debugln('TGtkWidgetSet.CreateDefaultBrush Created: ',DbgS(Result));
|
|
{$ENDIF}
|
|
Result^.GDIBrushFill := GDK_SOLID;
|
|
Result^.GDIBrushColor.ColorRef := 0;
|
|
Result^.GDIBrushColor.Colormap := gdk_colormap_get_system;
|
|
gdk_color_white(Result^.GDIBrushColor.Colormap, @Result^.GDIBrushColor.Color);
|
|
BuildColorRefFromGDKColor(Result^.GDIBrushColor);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: CreateDefaultFont
|
|
Params: none
|
|
Returns: a Font GDIObject
|
|
|
|
Creates an default font, used for initial values
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.CreateDefaultFont: PGdiObject;
|
|
var
|
|
CachedFont: TGtkFontCacheDescriptor;
|
|
begin
|
|
Result := NewGDIObject(gdiFont);
|
|
Result^.UntransfFontHeight := 0;
|
|
Result^.GDIFontObject:=GetDefaultGtkFont(false);
|
|
CachedFont:=FontCache.FindADescriptor(Result^.GDIFontObject);
|
|
if CachedFont<>nil then
|
|
FontCache.Reference(Result^.GDIFontObject)
|
|
else
|
|
FontCache.Add(Result^.GDIFontObject,DefaultLogFont,'');
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: CreateDefaultPen
|
|
Params: none
|
|
Returns: a Pen GDIObject
|
|
|
|
Creates an default pen, used for initial values
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.CreateDefaultPen: PGdiObject;
|
|
begin
|
|
//write(' TGtkWidgetSet.CreateDefaultPen ->');
|
|
Result := NewGDIObject(gdiPen);
|
|
Result^.UnTransfPenWidth := 0;
|
|
Result^.GDIPenStyle := PS_SOLID;
|
|
Result^.GDIPenColor.ColorRef := 0;
|
|
Result^.GDIPenColor.Colormap := gdk_colormap_get_system;
|
|
gdk_color_black(Result^.GDIPenColor.Colormap, @Result^.GDIPenColor.Color);
|
|
BuildColorRefFromGDKColor(Result^.GDIPenColor);
|
|
end;
|
|
|
|
function TGTKWidgetSet.CreateDefaultGDIBitmap: PGdiObject;
|
|
begin
|
|
Result := NewGDIObject(gdiBitmap);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.UpdateDCTextMetric(DC: TGtkDeviceContext);
|
|
|
|
Sets the gtk resource file and parses it.
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.UpdateDCTextMetric(DC: TGtkDeviceContext);
|
|
const
|
|
TestString: array[boolean] of string = (
|
|
// single byte char font
|
|
'{ABCDEFGHIJKLMNOPQRSTUVWXYZXYZabcdefghijklmnopqrstuvwxyz|_}',
|
|
// double byte char font
|
|
#0'{'#0'A'#0'B'#0'C'#0'D'#0'E'#0'F'#0'G'#0'H'#0'I'#0'J'#0'K'#0'L'#0'M'#0'N'
|
|
+#0'O'#0'P'#0'Q'#0'R'#0'S'#0'T'#0'U'#0'V'#0'W'#0'X'#0'Y'#0'Z'#0'X'#0'Y'#0'Z'
|
|
+#0'a'#0'b'#0'c'#0'd'#0'e'#0'f'#0'g'#0'h'#0'i'#0'j'#0'k'#0'l'#0'm'#0'n'#0'o'
|
|
+#0'p'#0'q'#0'r'#0's'#0't'#0'u'#0'v'#0'w'#0'x'#0'y'#0'z'#0'|'#0'_'#0'}'
|
|
);
|
|
var
|
|
UseFont : TGtkIntfFont;
|
|
CachedFont: TGtkFontCacheItem;
|
|
IsDefault: Boolean;
|
|
{$IFDEF Gtk1}
|
|
AvgTxtLen: Integer;
|
|
Width: LongInt;
|
|
{$ELSE}
|
|
AWidget: PGtkWidget;
|
|
APangoContext: PPangoContext;
|
|
APangoLanguage: PPangoLanguage;
|
|
Desc: TGtkFontCacheDescriptor;
|
|
APangoFontDescription: PPangoFontDescription;
|
|
APangoMetrics: PPangoFontMetrics;
|
|
aRect: TPangoRectangle;
|
|
{$ENDIF}
|
|
begin
|
|
with TGtkDeviceContext(DC) do begin
|
|
if dcfTextMetricsValid in Flags then begin
|
|
// cache valid
|
|
exit;
|
|
end;
|
|
UseFont:=GetGtkFont(TGtkDeviceContext(DC));
|
|
FillChar(DCTextMetric, SizeOf(DCTextMetric), 0);
|
|
CachedFont:=FontCache.FindGTKFont(UseFont);
|
|
IsDefault:=UseFont = GetDefaultGtkFont(false);
|
|
if (CachedFont=nil) and (not IsDefault) then begin
|
|
DebugLn(['TGtkWidgetSet.UpdateDCTextMetric no CachedFont UseFont=',dbgs(UseFont)]);
|
|
DumpStack;
|
|
end;
|
|
//DebugLn(['TGtkWidgetSet.UpdateDCTextMetric IsDefault=',UseFont = GetDefaultGtkFont(false)]);
|
|
|
|
if (CachedFont<>nil) and (CachedFont.MetricsValid) then begin
|
|
DCTextMetric.lBearing:=CachedFont.lBearing;
|
|
DCTextMetric.rBearing:=CachedFont.rBearing;
|
|
DCTextMetric.IsDoubleByteChar:=CachedFont.IsDoubleByteChar;
|
|
DCTextMetric.IsMonoSpace:=CachedFont.IsMonoSpace;
|
|
DCTextMetric.TextMetric:=CachedFont.TextMetric;
|
|
end
|
|
else with DCTextMetric do begin
|
|
IsDoubleByteChar:=FontIsDoubleByteCharsFont(UseFont);
|
|
IsMonoSpace:=FontIsMonoSpaceFont(UseFont);
|
|
{$IFDEF Gtk1}
|
|
AvgTxtLen:=length(TestString[false]);
|
|
if IsDoubleByteChar then begin
|
|
gdk_text_extents(UseFont, PChar(TestString[IsDoubleByteChar]),
|
|
AvgTxtLen, @lBearing, @rBearing, @Width,
|
|
@TextMetric.tmAscent, @TextMetric.tmDescent);
|
|
//debugln('TGtkWidgetSet.UpdateDCTextMetric A IsDoubleByteChar=',dbgs(IsDoubleByteChar),' Width=',dbgs(Width),' AvgTxtLen=',dbgs(AvgTxtLen));
|
|
TextMetric.tmHeight := TextMetric.tmAscent+TextMetric.tmDescent;
|
|
// gdk_text_height(UseFont,PChar(TestString[IsDoubleByteChar]),
|
|
// AvgTxtLen*2)
|
|
// {$IfNDef Win32} + TextMetric.tmdescent div 2{$EndIf};
|
|
end else begin
|
|
gdk_text_extents(UseFont, PChar(TestString[IsDoubleByteChar]),
|
|
AvgTxtLen, @lBearing, @rBearing, @Width,
|
|
@TextMetric.tmAscent, @TextMetric.tmDescent);
|
|
TextMetric.tmHeight := TextMetric.tmAscent+TextMetric.tmDescent;
|
|
// gdk_text_height(UseFont,PChar(TestString[IsDoubleByteChar]),
|
|
// AvgTxtLen)
|
|
// {$IfNDef Win32} + TextMetric.tmdescent div 2{$EndIf};
|
|
end;
|
|
//if Width<AvgTxtLen then UseWidthHeuristic;
|
|
//TextMetric.tmAscent := TextMetric.tmHeight - TextMetric.tmDescent;
|
|
if IsDoubleByteChar then
|
|
TextMetric.tmAveCharWidth:=Width div (AvgTxtLen div 2)
|
|
else
|
|
TextMetric.tmAveCharWidth:=Width div AvgTxtLen;
|
|
if TextMetric.tmAveCharWidth<1 then TextMetric.tmAveCharWidth:=1;
|
|
TextMetric.tmMaxCharWidth :=
|
|
Max(gdk_char_width(UseFont, 'W'),
|
|
gdk_char_width(UseFont, 'M')); // temp hack
|
|
if TextMetric.tmMaxCharWidth<TextMetric.tmAveCharWidth then
|
|
TextMetric.tmMaxCharWidth:=TextMetric.tmAveCharWidth;
|
|
{$ELSE Gtk2}
|
|
// get pango context (= association to a widget)
|
|
AWidget:=Widget;
|
|
if AWidget=nil then
|
|
AWidget:=GetStyleWidget(lgsLabel);
|
|
APangoContext := gtk_widget_get_pango_context(AWidget);
|
|
if APangoContext=nil then
|
|
DebugLn(['TGtkWidgetSet.UpdateDCTextMetric WARNING: no pango context']);
|
|
// get pango language (e.g. de_DE)
|
|
APangoLanguage := pango_context_get_language(APangoContext);
|
|
if APangoLanguage=nil then
|
|
DebugLn(['TGtkWidgetSet.UpdateDCTextMetric WARNING: no pango language']);
|
|
// get pango font description (e.g. 'sans 12')
|
|
APangoFontDescription := nil;
|
|
if (not IsDefault) and (CachedFont<>nil) then begin
|
|
Desc:=FontCache.FindADescriptor(UseFont);
|
|
if Desc<>nil then
|
|
APangoFontDescription := Desc.PangoFontDescription;
|
|
//DebugLn(['TGtkWidgetSet.UpdateDCTextMetric CachedFont Desc.PangoFontDescription=',GetPangoDescriptionReport(APangoFontDescription),' Desc.LongFontName=',Desc.LongFontName]);
|
|
end;
|
|
if APangoFontDescription=nil then
|
|
APangoFontDescription:=pango_context_get_font_description(APangoContext);
|
|
if APangoFontDescription=nil then
|
|
APangoFontDescription:=GetDefaultFontDesc(false);
|
|
if APangoFontDescription=nil then
|
|
DebugLn(['TGtkWidgetSet.UpdateDCTextMetric WARNING: no pango font description']);
|
|
//DebugLn(['TGtkWidgetSet.UpdateDCTextMetric APangoFontDescription=',GetPangoDescriptionReport(APangoFontDescription)]);
|
|
// get pango metrics (e.g. ascent, descent)
|
|
APangoMetrics := pango_context_get_metrics(APangoContext,
|
|
APangoFontDescription, APangoLanguage);
|
|
if APangoMetrics=nil then
|
|
DebugLn(['TGtkWidgetSet.UpdateDCTextMetric WARNING: no pango metrics']);
|
|
|
|
TextMetric.tmAveCharWidth := Max(1,
|
|
pango_font_metrics_get_approximate_char_width(APangoMetrics)
|
|
div PANGO_SCALE);
|
|
TextMetric.tmAscent := pango_font_metrics_get_ascent(APangoMetrics) div PANGO_SCALE;
|
|
TextMetric.tmDescent := pango_font_metrics_get_descent(APangoMetrics) div PANGO_SCALE;
|
|
TextMetric.tmHeight := TextMetric.tmAscent+TextMetric.tmDescent;
|
|
|
|
pango_layout_set_text(UseFont, PChar(TestString[IsDoubleByteChar]),
|
|
length(PChar(TestString[IsDoubleByteChar])));
|
|
pango_layout_get_extents(UseFont, nil, @aRect);
|
|
|
|
lBearing := PANGO_LBEARING(aRect) div PANGO_SCALE;
|
|
rBearing := PANGO_RBEARING(aRect) div PANGO_SCALE;
|
|
|
|
pango_layout_set_text(UseFont, 'M', 1);
|
|
pango_layout_get_pixel_size(UseFont, @aRect.width, @aRect.height);
|
|
TextMetric.tmMaxCharWidth := Max(1,aRect.width);
|
|
pango_layout_set_text(UseFont, 'W', 1);
|
|
pango_layout_get_pixel_size(UseFont, @aRect.width, @aRect.height);
|
|
TextMetric.tmMaxCharWidth := Max(TextMetric.tmMaxCharWidth,aRect.width);
|
|
|
|
pango_font_metrics_unref(APangoMetrics);
|
|
{$ENDIF}
|
|
(*debugln('TGtkWidgetSet.UpdateDCTextMetric A IsDoubleByteChar=',dbgs(IsDoubleByteChar),
|
|
' lbearing=',dbgs(lBearing),' rbearing=',dbgs(rBearing),
|
|
{$IFDEF Gtk1}
|
|
' width='+dbgs(width),
|
|
' AvgTxtLen='+dbgs(AvgTxtLen),
|
|
{$ENDIF}
|
|
' tmAscent='+dbgs(TextMetric.tmAscent),
|
|
' tmDescent='+dbgs(TextMetric.tmdescent),
|
|
' tmHeight='+dbgs(TextMetric.tmHeight),
|
|
' tmMaxCharWidth='+dbgs(TextMetric.tmMaxCharWidth),
|
|
' tmAveCharWidth='+dbgs(TextMetric.tmAveCharWidth));*)
|
|
if (CachedFont<>nil) then begin
|
|
CachedFont.lBearing:=lBearing;
|
|
CachedFont.rBearing:=rBearing;
|
|
CachedFont.IsDoubleByteChar:=IsDoubleByteChar;
|
|
CachedFont.IsMonoSpace:=IsMonoSpace;
|
|
CachedFont.TextMetric:=TextMetric;
|
|
CachedFont.MetricsValid:=true;
|
|
end;
|
|
end;
|
|
Flags := Flags + [dcfTextMetricsValid];
|
|
end;
|
|
end;
|
|
|
|
{$Ifdef GTK2}
|
|
{------------------------------------------------------------------------------
|
|
function TGtkWidgetSet.GetDefaultFontDesc(IncreaseReferenceCount: boolean
|
|
): PPangoFontDescription;
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.GetDefaultFontDesc(IncreaseReferenceCount: boolean
|
|
): PPangoFontDescription;
|
|
begin
|
|
if FDefaultFontDesc = nil then begin
|
|
FDefaultFontDesc:=LoadDefaultFontDesc;
|
|
if FDefaultFontDesc = nil then
|
|
raise EOutOfResources.Create(rsUnableToLoadDefaultFont);
|
|
end;
|
|
Result:=FDefaultFontDesc;
|
|
if IncreaseReferenceCount then
|
|
Result := pango_font_description_copy(Result);
|
|
end;
|
|
{$Endif}
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TGtkWidgetSet.GetDefaultGtkFont(IncreaseReferenceCount: boolean
|
|
): TGtkIntfFont;
|
|
------------------------------------------------------------------------------}
|
|
function TGtkWidgetSet.GetDefaultGtkFont(IncreaseReferenceCount: boolean
|
|
): TGtkIntfFont;
|
|
begin
|
|
if FDefaultFont = nil then begin
|
|
FDefaultFont:=LoadDefaultFont;
|
|
if FDefaultFont = nil then
|
|
raise EOutOfResources.Create(rsUnableToLoadDefaultFont);
|
|
ReferenceGtkIntfFont(FDefaultFont); // mark as used globally
|
|
end;
|
|
Result:=FDefaultFont;
|
|
if IncreaseReferenceCount then
|
|
ReferenceGtkIntfFont(Result); // mark again
|
|
end;
|
|
|
|
function TGTKWidgetSet.GetGtkFont(DC: TGtkDeviceContext): TGtkIntfFont;
|
|
begin
|
|
{$IFDEF Gtk}
|
|
if (DC.CurrentFont = nil) or (DC.CurrentFont^.GDIFontObject = nil)
|
|
then begin
|
|
Result := GetDefaultGtkFont(false);
|
|
end
|
|
else begin
|
|
Result := DC.CurrentFont^.GDIFontObject;
|
|
end;
|
|
{$ELSE}
|
|
// create font if needed
|
|
Result:=DC.GetFont^.GDIFontObject;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TGtkWidgetSet.CreateRegionCopy(SrcRGN: hRGN): hRGN;
|
|
var
|
|
GDIObject: PGDIObject;
|
|
begin
|
|
GDIObject := NewGDIObject(gdiRegion);
|
|
GDIObject^.GDIRegionObject:=gdk_region_copy(PGdiObject(SrcRGN)^.GDIRegionObject);
|
|
Result := hRgn(PtrUInt(GDIObject));
|
|
end;
|
|
|
|
function TGtkWidgetSet.DCClipRegionValid(DC: HDC): boolean;
|
|
var
|
|
CurClipRegion: hRGN;
|
|
begin
|
|
Result:=false;
|
|
if not IsValidDC(DC) then exit;
|
|
CurClipRegion:=HRGN(PtrUInt(TGtkDeviceContext(DC).ClipRegion));
|
|
if (CurClipRegion<>0) and (not IsValidGDIObject(CurClipRegion)) then exit;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TGtkWidgetSet.CreateEmptyRegion: hRGN;
|
|
var
|
|
GObject: PGdiObject;
|
|
begin
|
|
GObject := NewGDIObject(gdiRegion);
|
|
GObject^.GDIRegionObject := gdk_region_new;
|
|
Result := HRGN(PtrUInt(GObject));
|
|
//DebugLn('TGtkWidgetSet.CreateEmptyRgn A RGN=',DbgS(Result));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: SetRCFilename
|
|
Params: const AValue: string
|
|
Returns: none
|
|
|
|
Sets the gtk resource file and parses it.
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.SetRCFilename(const AValue: string);
|
|
begin
|
|
if (FRCFilename=AValue) then exit;
|
|
FRCFilename:=AValue;
|
|
FRCFileParsed:=false;
|
|
ParseRCFile;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.CheckRCFilename;
|
|
|
|
Sets the gtk resource file and parses it.
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.CheckRCFilename;
|
|
begin
|
|
if FRCFileParsed and (FRCFilename<>'') and FileExistsUTF8(FRCFilename)
|
|
and (FileAgeUTF8(FRCFilename)<>FRCFileAge) then
|
|
FRCFileParsed:=false;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: ParseRCFile
|
|
Params: const AValue: string
|
|
Returns: none
|
|
|
|
Sets the gtk resource file and parses it.
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.ParseRCFile;
|
|
begin
|
|
if (not FRCFileParsed)
|
|
and (FRCFilename<>'') and FileExistsUTF8(FRCFilename) then
|
|
begin
|
|
gtk_rc_parse(PChar(FRCFilename));
|
|
FRCFileParsed:=true;
|
|
FRCFileAge:=FileAgeUTF8(FRCFilename);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Function: SetClipboardWidget
|
|
Params: TargetWidget: PGtkWidget - This widget will be connected to all
|
|
clipboard signals which are all handled by the TGtkWidgetSet
|
|
itself.
|
|
Returns: none
|
|
|
|
All supported targets are added to the new widget. This way, no one,
|
|
especially not the lcl, will notice the change. ;)
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.SetClipboardWidget(TargetWidget: PGtkWidget);
|
|
type
|
|
TGtkTargetSelectionList = record
|
|
Selection: Cardinal;
|
|
List: PGtkTargetList;
|
|
end;
|
|
PGtkTargetSelectionList = ^TGtkTargetSelectionList;
|
|
const
|
|
gtk_selection_handler_key: PChar = 'gtk-selection-handlers';
|
|
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
function gtk_selection_target_list_get(Widget: PGtkWidget;
|
|
ClipboardType: TClipboardType): PGtkTargetList;
|
|
var
|
|
SelectionLists, CurSelList: PGList;
|
|
TargetSelList: PGtkTargetSelectionList;
|
|
begin
|
|
SelectionLists := gtk_object_get_data (PGtkObject(Widget),
|
|
gtk_selection_handler_key);
|
|
CurSelList := SelectionLists;
|
|
while (CurSelList<>nil) do begin
|
|
TargetSelList := CurSelList^.Data;
|
|
if (TargetSelList^.Selection = ClipboardTypeAtoms[ClipboardType]) then
|
|
begin
|
|
Result:=TargetSelList^.List;
|
|
exit;
|
|
end;
|
|
CurSelList := CurSelList^.Next;
|
|
end;
|
|
Result:=nil;
|
|
end;
|
|
|
|
procedure WriteTargetLists(Widget: PGtkWidget);
|
|
var c: TClipboardType;
|
|
TargetList: PGtkTargetList;
|
|
TmpList: PGList;
|
|
Pair: PGtkTargetPair;
|
|
begin
|
|
DebugLn(' WriteTargetLists WWW START');
|
|
for c:=Low(TClipboardType) to High(TClipboardType) do begin
|
|
TargetList:=gtk_selection_target_list_get(Widget,c);
|
|
DebugLn(' WriteTargetLists WWW ',ClipboardTypeName[c],' ',dbgs(TargetList<>nil));
|
|
if TargetList<>nil then begin
|
|
TmpList:=TargetList^.List;
|
|
while TmpList<>nil do begin
|
|
Pair:=PGtkTargetPair(TmpList^.Data);
|
|
DebugLn(' WriteTargetLists BBB ',dbgs(Pair^.Target),' ',GdkAtomToStr(Pair^.Target));
|
|
TmpList:=TmpList^.Next;
|
|
end;
|
|
end;
|
|
end;
|
|
DebugLn(' WriteTargetLists WWW END');
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure ClearTargetLists(Widget: PGtkWidget);
|
|
// MG: Reading in gtk internals is dirty, but there seems to be no other way
|
|
// to clear the old target lists
|
|
var
|
|
SelectionLists: PGList;
|
|
{$ifdef gtk1}
|
|
CurSelList: PGList;
|
|
TargetSelList: PGtkTargetSelectionList;
|
|
{$else}
|
|
CurClipboard: TClipboardType;
|
|
{$endif}
|
|
begin
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn(' ClearTargetLists WWW START');
|
|
{$ENDIF}
|
|
{$ifdef gtk1}
|
|
SelectionLists := gtk_object_get_data (PGtkObject(Widget),
|
|
gtk_selection_handler_key);
|
|
CurSelList := SelectionLists;
|
|
while (CurSelList<>nil) do
|
|
begin
|
|
TargetSelList := CurSelList^.Data;
|
|
gtk_target_list_unref(TargetSelList^.List);
|
|
g_free(TargetSelList);
|
|
CurSelList := CurSelList^.Next;
|
|
end;
|
|
g_list_free(SelectionLists);
|
|
{$else}
|
|
// clear 3 selections
|
|
for CurClipboard := Low(TClipboardType) to High(CurClipboard) do
|
|
gtk_selection_clear_targets(Widget, ClipboardTypeAtoms[CurClipboard]);
|
|
|
|
SelectionLists := gtk_object_get_data(PGtkObject(Widget),
|
|
gtk_selection_handler_key);
|
|
if SelectionLists <> nil then
|
|
g_list_free(SelectionLists);
|
|
{$endif}
|
|
gtk_object_set_data (PGtkObject(Widget), gtk_selection_handler_key, GtkNil);
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn(' ClearTargetLists WWW END');
|
|
{$ENDIF}
|
|
end;
|
|
|
|
var c: TClipboardType;
|
|
begin
|
|
if ClipboardWidget=TargetWidget then exit;
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
DebugLn('[TGtkWidgetSet.SetClipboardWidget] ',dbgs(ClipboardWidget<>nil),' -> ',dbgs(TargetWidget<>nil),' ',GetWidgetDebugReport(TargetWidget));
|
|
{$ENDIF}
|
|
if ClipboardWidget<>nil then begin
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
WriteTargetLists(ClipboardWidget);
|
|
{$ENDIF}
|
|
ClearTargetLists(ClipboardWidget);
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
WriteTargetLists(ClipboardWidget);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
ClipboardWidget:=TargetWidget;
|
|
if ClipboardWidget<>nil then begin
|
|
// connect widget to all clipboard signals
|
|
g_signal_connect(PGtkObject(ClipboardWidget),'selection_received',
|
|
TGTKSignalFunc(@ClipboardSelectionReceivedHandler),GtkNil);
|
|
g_signal_connect(PGtkObject(ClipboardWidget),'selection_get',
|
|
TGTKSignalFunc(@ClipboardSelectionRequestHandler),GtkNil);
|
|
g_signal_connect(PGtkObject(ClipboardWidget),'selection_clear_event',
|
|
TGTKSignalFunc(@ClipboardSelectionLostOwnershipHandler),GtkNil);
|
|
// add all supported targets for all clipboard types
|
|
for c:=Low(TClipboardType) to High(TClipboardType) do begin
|
|
if (ClipboardTargetEntries[c]<>nil) then begin
|
|
//DebugLn('TGtkWidgetSet.SetClipboardWidget ',GdkAtomToStr(ClipboardTypeAtoms[c]),' Entries=',dbgs(ClipboardTargetEntryCnt[c]));
|
|
gtk_selection_add_targets(ClipboardWidget,ClipboardTypeAtoms[c],
|
|
ClipboardTargetEntries[c],ClipboardTargetEntryCnt[c]);
|
|
end;
|
|
end;
|
|
{$IFDEF DEBUG_CLIPBOARD}
|
|
WriteTargetLists(ClipboardWidget);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkWidgetSet.WordWrap(AText: PChar; MaxWidthInPixel: integer;
|
|
var Lines: PPChar; var LineCount: integer); virtual;
|
|
|
|
Breaks AText into several lines and creates a list of PChar. The last entry
|
|
will be nil.
|
|
Lines break at new line chars and at spaces if a line is longer than
|
|
MaxWidthInPixel or in a word.
|
|
Lines will be one memory block so that you can free the list and all lines
|
|
with FreeMem(Lines).
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkWidgetSet.WordWrap(DC: HDC; AText: PChar;
|
|
MaxWidthInPixel: integer; var Lines: PPChar; var LineCount: integer);
|
|
var
|
|
UseFont: TGtkIntfFont;
|
|
|
|
function GetLineWidthInPixel(LineStart, LineLen: integer): integer;
|
|
var
|
|
width: LongInt;
|
|
begin
|
|
GetTextExtentIgnoringAmpersands(UseFont, @AText[LineStart], LineLen,
|
|
nil, nil, @width, nil, nil);
|
|
Result:=Width;
|
|
end;
|
|
|
|
function FindLineEnd(LineStart: integer): integer;
|
|
var
|
|
CharLen,
|
|
LineStop,
|
|
LineWidth, WordWidth, WordEnd, CharWidth: integer;
|
|
begin
|
|
// first search line break or text break
|
|
Result:=LineStart;
|
|
while not (AText[Result] in [#0,#10,#13]) do inc(Result);
|
|
if Result<=LineStart+1 then exit;
|
|
lineStop:=Result;
|
|
|
|
// get current line width in pixel
|
|
LineWidth:=GetLineWidthInPixel(LineStart,Result-LineStart);
|
|
if LineWidth>MaxWidthInPixel then begin
|
|
// line too long
|
|
// -> add words till line size reached
|
|
LineWidth:=0;
|
|
WordEnd:=LineStart;
|
|
WordWidth:=0;
|
|
repeat
|
|
Result:=WordEnd;
|
|
inc(LineWidth,WordWidth);
|
|
// find word start
|
|
while AText[WordEnd] in [' ',#9] do inc(WordEnd);
|
|
// find word end
|
|
while not (AText[WordEnd] in [#0,' ',#9,#10,#13]) do inc(WordEnd);
|
|
// calculate word width
|
|
WordWidth:=GetLineWidthInPixel(Result,WordEnd-Result);
|
|
until LineWidth+WordWidth>MaxWidthInPixel;
|
|
if LineWidth=0 then begin
|
|
// the first word is longer than the maximum width
|
|
// -> add chars till line size reached
|
|
Result:=LineStart;
|
|
LineWidth:=0;
|
|
repeat
|
|
charLen:=UTF8CharacterLength(@AText[result]);
|
|
CharWidth:=GetLineWidthInPixel(Result,charLen);
|
|
inc(LineWidth,CharWidth);
|
|
if LineWidth>MaxWidthInPixel then break;
|
|
if result>=lineStop then break;
|
|
inc(Result,charLen);
|
|
until false;
|
|
// at least one char
|
|
if Result=LineStart then begin
|
|
charLen:=UTF8CharacterLength(@AText[result]);
|
|
inc(Result,charLen);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function IsEmptyText: boolean;
|
|
begin
|
|
if (AText=nil) or (AText[0]=#0) then begin
|
|
// no text
|
|
GetMem(Lines,SizeOf(PChar));
|
|
Lines[0]:=nil;
|
|
LineCount:=0;
|
|
Result:=true;
|
|
end else
|
|
Result:=false;
|
|
end;
|
|
|
|
procedure InitFont;
|
|
begin
|
|
UseFont:=GetGtkFont(TGtkDeviceContext(DC));
|
|
end;
|
|
|
|
var
|
|
LinesList: TFPList;
|
|
LineStart, LineEnd, LineLen: integer;
|
|
ArraySize, TotalSize: integer;
|
|
i: integer;
|
|
CurLineEntry: PPChar;
|
|
CurLineStart: PChar;
|
|
begin
|
|
if IsEmptyText then exit;
|
|
InitFont;
|
|
LinesList:=TFPList.Create;
|
|
LineStart:=0;
|
|
|
|
// find all line starts and line ends
|
|
repeat
|
|
LinesList.Add(Pointer(PtrInt(LineStart)));
|
|
// find line end
|
|
LineEnd:=FindLineEnd(LineStart);
|
|
LinesList.Add(Pointer(PtrInt(LineEnd)));
|
|
// find next line start
|
|
LineStart:=LineEnd;
|
|
if AText[LineStart] in [#10,#13] then begin
|
|
// skip new line chars
|
|
inc(LineStart);
|
|
if (AText[LineStart] in [#10,#13])
|
|
and (AText[LineStart]<>AText[LineStart-1]) then
|
|
inc(LineStart);
|
|
end else if AText[LineStart] in [' ',#9] then begin
|
|
// skip space
|
|
while AText[LineStart] in [' ',#9] do
|
|
inc(LineStart);
|
|
end;
|
|
until AText[LineStart]=#0;
|
|
|
|
// create mem block for 'Lines': array of PChar + all lines
|
|
LineCount:=LinesList.Count shr 1;
|
|
ArraySize:=(LineCount+1)*SizeOf(PChar);
|
|
TotalSize:=ArraySize;
|
|
i:=0;
|
|
while i<LinesList.Count do begin
|
|
// add LineEnd - LineStart + 1 for the #0
|
|
LineLen:=PtrUInt(LinesList[i+1])-PtrUInt(LinesList[i])+1;
|
|
inc(TotalSize,LineLen);
|
|
inc(i,2);
|
|
end;
|
|
GetMem(Lines,TotalSize);
|
|
FillChar(Lines^,TotalSize,0);
|
|
|
|
// create Lines
|
|
CurLineEntry:=Lines;
|
|
CurLineStart:=PChar(CurLineEntry)+ArraySize;
|
|
i:=0;
|
|
while i<LinesList.Count do begin
|
|
// set the pointer to the start of the current line
|
|
CurLineEntry[i shr 1]:=CurLineStart;
|
|
// copy the line
|
|
LineStart:=integer(PtrUInt(LinesList[i]));
|
|
LineEnd:=integer(PtrUInt(LinesList[i+1]));
|
|
LineLen:=LineEnd-LineStart;
|
|
if LineLen>0 then
|
|
Move(AText[LineStart],CurLineStart^,LineLen);
|
|
inc(CurLineStart,LineLen);
|
|
// add #0 as line end
|
|
CurLineStart^:=#0;
|
|
inc(CurLineStart);
|
|
// next line
|
|
inc(i,2);
|
|
end;
|
|
if PtrUInt(CurLineStart)-PtrUInt(Lines)<>TotalSize then
|
|
RaiseGDBException('TGtkWidgetSet.WordWrap Consistency Error:'
|
|
+' Lines+TotalSize<>CurLineStart');
|
|
CurLineEntry[i shr 1]:=nil;
|
|
|
|
LinesList.Free;
|
|
end;
|
|
|
|
function TGtkWidgetSet.ForceLineBreaks(DC: hDC; Src: PChar;
|
|
MaxWidthInPixels: Longint;
|
|
ConvertAmpersandsToUnderScores: Boolean) : PChar;
|
|
var
|
|
Lines : PPChar;
|
|
I, NumLines : Longint;
|
|
TmpStr : PGString;
|
|
Line : PgChar;
|
|
begin
|
|
TmpStr := nil;
|
|
WordWrap(DC, Src, MaxWidthInPixels, Lines, NumLines);
|
|
For I := 0 to NumLines - 1 do begin
|
|
If TmpStr <> nil then
|
|
g_string_append_c(TmpStr, #10);
|
|
|
|
If ConvertAmpersandsToUnderScores then begin
|
|
Line := Ampersands2Underscore(Lines[I]);
|
|
If Line <> nil then begin
|
|
If TmpStr <> nil then begin
|
|
g_string_append(TmpStr, Line);
|
|
end
|
|
else
|
|
TmpStr := g_string_new(Line);
|
|
StrDispose(Line);
|
|
end;
|
|
end
|
|
else begin
|
|
If Lines[I] <> nil then
|
|
If TmpStr <> nil then
|
|
g_string_append(TmpStr, Lines[I])
|
|
else
|
|
TmpStr := g_string_new(Lines[I]);
|
|
end;
|
|
end;
|
|
ReallocMem(Lines, 0);
|
|
If TmpStr <> nil then
|
|
Result := StrNew(TmpStr^.str)
|
|
else
|
|
Result:=nil;
|
|
end;
|
|
|
|
{$IFDEF ASSERT_IS_ON}
|
|
{$UNDEF ASSERT_IS_ON}
|
|
{$C-}
|
|
{$ENDIF}
|