{*************************************************************************** GtkMsgQueue - Messagequeue for Gtk interface -------------------------------------------- Initial Revision : Thu Aug 16, 2003 ***************************************************************************/ ***************************************************************************** This file is part of the Lazarus Component Library (LCL) See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** } unit Gtk2MsgQueue; {$mode objfpc}{$H+} interface uses // RTL Classes, // LCL LazLinkedList, LCLType, LMessages, Gtk2Globals, Gtk2Proc, // LazUtils DynHashArray {$IFNDEF USE_GTK_MAIN_OLD_ITERATION} , glib2 {$ENDIF} ; type TFinalPaintMessageFlag=(FPMF_None,FPMF_Internal,FPMF_All); TGtkMessageQueueItem=class(TLinkListitem) private fMsg : PMsg; public property Msg: PMsg read fMsg write fMsg; function IsPaintMessage: Boolean; procedure DestroyMessage(ParFinalInternalOnly: TFinalPaintMessageFlag; DisposeMessage: boolean); constructor Create; end; { TGtkMessageQueue } TGtkMessageQueue=class(TLinkList) private FPaintMessages: TDynHashArray; // Hash for paint messages FCritSec: TRTLCriticalSection; {$IFNDEF USE_GTK_MAIN_OLD_ITERATION} FMainContext: PGMainContext; {$ELSE} fLock: integer; {$ENDIF} protected function CreateItem : TLinkListItem;override; function CalculateHash(ParWnd : Hwnd):integer; function HashPaintMessage(p: pointer): integer; public constructor Create; destructor destroy;override; procedure Lock; procedure UnLock; function FirstMessageItem: TGtkMessageQueueItem; function LastMessageItem: TGtkMessageQueueItem; function FirstMessage: PMsg; function LastMessage: PMsg; procedure AddMessage(ParMsg: PMsg); procedure RemoveMessage(ParItem: TGtkMessageQueueItem; ParFinalOnlyInternal: TFinalPaintMessageFlag; DisposeMessage: boolean); function FindPaintMessage(HandleWnd: HWnd): TGtkMessageQueueItem; function HasPaintMessages:boolean; function HasNonPaintMessages:boolean; function NumberOfPaintMessages:integer; function PopFirstMessage: PMsg; {$IFNDEF USE_GTK_MAIN_OLD_ITERATION} property MainContext: PGMainContext read FMainContext; {$ENDIF} end; implementation {---(TGtkMessageQueueItem)----------------------} function TGtkMessageQueueItem.IsPaintMessage: Boolean; begin if fMsg <> nil then Result := (Msg^.Message = LM_PAINT) or (Msg^.Message = LM_GTKPAINT) else Result := False; end; constructor TGtkMessageQueueItem.Create; begin inherited Create; fMsg := nil; end; procedure TGtkMessageQueueItem.DestroyMessage( ParFinalInternalOnly: TFinalPaintMessageFlag; DisposeMessage: boolean); begin if (ParFinalInternalOnly in [FPMF_All, FPMF_Internal]) and (fMsg^.message = LM_GTKPAINT) then FinalizePaintTagMsg(fMsg); if DisposeMessage then Dispose(fMsg); fMsg := nil; end; {---(TGtkMessageQueue )---------------------------} constructor TGtkMessageQueue.Create; begin inherited Create; FPaintMessages := TDynHashArray.Create(-1); FPaintMessages.OwnerHashFunction := @HashPaintMessage; InitCriticalSection(FCritSec); {$IFNDEF USE_GTK_MAIN_OLD_ITERATION} FMainContext := g_main_context_new; g_main_context_ref(FMainContext); {$ENDIF} end; destructor TGtkMessageQueue.destroy; var QueueItem : TGtkMessageQueueItem; NextQueueItem : TGtkMessageQueueItem; begin // cleanup outstanding PostMessages // for example, this situation may appear on slow systems Lock; try QueueItem:=FirstMessageItem; while (QueueItem<>nil) do begin NextQueueItem := TGtkMessageQueueItem(QueueItem.Next); RemoveMessage(QueueItem,FPMF_All,true); QueueItem := NextQueueItem; end; finally Unlock; end; inherited Destroy; fPaintMessages.destroy; {$IFNDEF USE_GTK_MAIN_OLD_ITERATION} g_main_context_unref(FMainContext); FMainContext := nil; {$ENDIF} DoneCriticalsection(FCritSec); end; procedure TGtkMessageQueue.Lock; begin {$IFDEF USE_GTK_MAIN_OLD_ITERATION} if InterlockedIncrement(fLock)=1 then EnterCriticalsection(FCritSec); {$ELSE} if GetCurrentThreadId = MainThreadID then repeat until g_main_context_acquire(FMainContext) // This can return False. else EnterCriticalsection(FCritSec); {$ENDIF} end; procedure TGtkMessageQueue.UnLock; begin {$IFDEF USE_GTK_MAIN_OLD_ITERATION} if InterlockedDecrement(fLock)=0 then LeaveCriticalsection(FCritSec); {$ELSE} if GetCurrentThreadId = MainThreadID then g_main_context_release(FMainContext) else LeaveCriticalsection(FCritSec) {$ENDIF} end; {------------------------------------------------------------------------------ Function: FindPaintMessage Params: a window handle Returns: nil or a Paint Message to the widget Searches in FPaintMessages for a LM_PAINT message with HandleWnd. ------------------------------------------------------------------------------} function TGtkMessageQueue.FindPaintMessage(HandleWnd: HWnd): TGtkMessageQueueItem; var h: integer; HashItem: PDynHashArrayItem; begin h:= CalculateHash(HandleWnd); HashItem:=FPaintMessages.GetHashItem(h); if HashItem<>nil then begin Result:=TGtkMessageQueueItem(HashItem^.Item); if Result.Msg^.hWnd=HandleWnd then exit; HashItem:=HashItem^.Next; while (HashItem<>nil) and (HashItem^.IsOverflow) do begin Result:=TGtkMessageQueueItem(HashItem^.Item); if Result.Msg^.hWnd=HandleWnd then exit; HashItem:=HashItem^.Next; end; end; Result:=nil; end; function TGtkMessageQueue.HashPaintMessage(p: pointer): integer; begin result := CalculateHash(TGtkMessageQueueItem(p).Msg^.Hwnd); end; function TGtkMessageQueue.CalculateHash(ParWnd : Hwnd):integer; var h:integer; begin h :=ParWnd; if h<0 then h:=-h; Result:=((h mod 5364329)+(h mod 17)) mod FPaintMessages.Capacity; end; function TGtkMessageQueue.CreateItem : TLinkListItem; begin result := TGtkMessageQueueItem.Create; result.ResetItem; end; procedure TGtkMessageQueue.AddMessage(ParMsg : PMsg); var vLItem : TGtkMessageQueueItem; begin vlItem := TGtkMessageQueueItem(GetNewItem); vlItem.fMsg := ParMsg; AddAsLast(vlItem); if vlItem.IsPaintMessage then fPaintMessages.Add(vlitem); end; function TGtkMessageQueue.FirstMessageItem : TGtkMessageQueueItem; begin Lock; try Result :=TGtkMessageQueueItem(First); finally UnLock; end; end; function TGtkMessageQueue.FirstMessage : PMsg; begin Result := nil; Lock; try if FirstMessageItem <> nil then Result := FirstMessageItem.fMsg; finally UnLock; end; end; function TGtkMessageQueue.LastMessageItem : TGtkMessageQueueItem; begin Lock; try Result:=TGtkMessageQueueItem(Last); finally UnLock; end; end; function TGtkMessageQueue.LastMessage : PMsg; begin Lock; try Result := nil; if LastMessageItem <> nil then result := LastMessageItem.fMsg; finally UnLock; end; end; { Remove from queue and destroy message ParItem : Queue Item for removel ParFinalOnlyInterl : finalyze message only for LM_GtkPaint } procedure TGtkMessageQueue.RemoveMessage(ParItem: TGtkMessageQueueItem; ParFinalOnlyInternal: TFinalPaintMessageFlag; DisposeMessage: boolean); begin Lock; try if (ParItem.IsPaintMessage) then fPaintMessages.Remove(ParItem); ParItem.DestroyMessage(ParFinalOnlyInternal, DisposeMessage); Delete(ParItem); finally UnLock; end; end; function TGtkMessageQueue.HasPaintMessages:boolean; begin Lock; try Result := fPaintMessages.Count > 0; finally UnLock; end; end; function TGtkMessageQueue.NumberOfPaintMessages:integer; begin Lock; try Result := fPaintMessages.Count; finally UnLock; end; end; function TGtkMessageQueue.HasNonPaintMessages:boolean; begin Lock; try Result := fPaintMessages.Count <> count; finally UnLock; end; end; function TGtkMessageQueue.PopFirstMessage: PMsg; var vlItem : TGtkMessageQueueItem; begin Lock; try vlItem := FirstMessageItem; if vlItem <> nil then begin Result := vlItem.Msg; RemoveMessage(vlItem,FPMF_none,false); end else Result := nil; finally UnLock; end; end; end.