mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-16 13:49:30 +02:00
535 lines
14 KiB
ObjectPascal
535 lines
14 KiB
ObjectPascal
{
|
|
/***************************************************************************
|
|
lclproc.pas
|
|
-----------
|
|
Component Library Code
|
|
|
|
|
|
***************************************************************************/
|
|
|
|
*****************************************************************************
|
|
* *
|
|
* This file is part of the Lazarus Component Library (LCL) *
|
|
* *
|
|
* See the file COPYING.LCL, 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. *
|
|
* *
|
|
*****************************************************************************
|
|
|
|
Useful lower level helper functions and classes.
|
|
}
|
|
unit LCLProc;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LCLStrConsts, LCLType;
|
|
|
|
type
|
|
TMethodList = class
|
|
private
|
|
FItems: ^TMethod;
|
|
FCount: integer;
|
|
function GetItems(Index: integer): TMethod;
|
|
procedure SetItems(Index: integer; const AValue: TMethod);
|
|
public
|
|
destructor Destroy; override;
|
|
function Count: integer;
|
|
function NextDownIndex(var Index: integer): boolean;
|
|
function IndexOf(const AMethod: TMethod): integer;
|
|
procedure Delete(Index: integer);
|
|
procedure Remove(const AMethod: TMethod);
|
|
procedure Add(const AMethod: TMethod);
|
|
procedure Add(const AMethod: TMethod; AsLast: boolean);
|
|
procedure Insert(Index: integer; const AMethod: TMethod);
|
|
procedure Move(OldIndex, NewIndex: integer);
|
|
procedure RemoveAllMethodsOfObject(const AnObject: TObject);
|
|
public
|
|
property Items[Index: integer]: TMethod read GetItems write SetItems; default;
|
|
end;
|
|
|
|
Function DeleteAmpersands(var Str : String) : Longint;
|
|
|
|
function ShortCutToShortCutText(ShortCut: TShortCut): string;
|
|
function ShortCutTextToShortCut(const ShortCutText: string): TShortCut;
|
|
|
|
// Hooks used to prevent unit circles
|
|
type
|
|
TSendApplicationMessageFunction =
|
|
function(Msg: Cardinal; WParam, LParam: Longint):Longint;
|
|
TOwnerFormDesignerModifiedProc =
|
|
procedure(AComponent: TComponent);
|
|
TSendMessageToInterfaceFunction =
|
|
function(LM_Message: Integer; Sender: TObject; data: pointer): integer
|
|
of object;
|
|
|
|
var
|
|
SendApplicationMessageFunction: TSendApplicationMessageFunction;
|
|
OwnerFormDesignerModifiedProc: TOwnerFormDesignerModifiedProc;
|
|
// SendMsgToInterface is set in interfacebase.pp
|
|
SendMsgToInterface: TSendMessageToInterfaceFunction;
|
|
|
|
function SendApplicationMessage(Msg: Cardinal; WParam, LParam: Longint):Longint;
|
|
procedure OwnerFormDesignerModified(AComponent: TComponent);
|
|
function OffsetRect(var ARect: TRect; dx,dy: Integer): Boolean;
|
|
procedure FreeThenNil(var AnObject: TObject);
|
|
procedure RaiseGDBException(const Msg: string);
|
|
|
|
procedure MakeMinMax(var i1, i2: integer);
|
|
procedure CalculateLeftTopWidthHeight(X1,Y1,X2,Y2: integer;
|
|
var Left,Top,Width,Height: integer);
|
|
|
|
function BreakString(const s: string; MaxLineLength, Indent: integer): string;
|
|
|
|
implementation
|
|
|
|
|
|
Function DeleteAmpersands(var Str : String) : Longint;
|
|
// Replace all &x with x
|
|
// and return the position of the first ampersand letter in the resulting Str.
|
|
// double ampersands && are converted to a single & and are ignored.
|
|
var
|
|
SrcPos, DestPos, SrcLen: Integer;
|
|
begin
|
|
Result:=-1;
|
|
SrcLen:=length(Str);
|
|
SrcPos:=1;
|
|
DestPos:=1;
|
|
while SrcPos<=SrcLen do begin
|
|
if (Str[SrcPos]='&') and (SrcPos<SrcLen) then begin
|
|
// & found
|
|
inc(SrcPos); // skip &
|
|
if (Str[SrcPos]<>'&') and (Result<1) then
|
|
Result:=DestPos;
|
|
end;
|
|
if DestPos<SrcPos then
|
|
Str[DestPos]:=Str[SrcPos];
|
|
inc(SrcPos);
|
|
inc(DestPos);
|
|
end;
|
|
if DestPos<SrcPos then
|
|
SetLength(Str,DestPos-1);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Keys and shortcuts
|
|
|
|
type
|
|
TMenuKeyCap = (mkcBkSp, mkcTab, mkcEsc, mkcEnter, mkcSpace, mkcPgUp,
|
|
mkcPgDn, mkcEnd, mkcHome, mkcLeft, mkcUp, mkcRight, mkcDown, mkcIns,
|
|
mkcDel, mkcShift, mkcCtrl, mkcAlt);
|
|
|
|
const
|
|
SmkcBkSp = 'BkSp';
|
|
SmkcTab = 'Tab';
|
|
SmkcEsc = 'Esc';
|
|
SmkcEnter = 'Enter';
|
|
SmkcSpace = 'Space';
|
|
SmkcPgUp = 'PgUp';
|
|
SmkcPgDn = 'PgDn';
|
|
SmkcEnd = 'End';
|
|
SmkcHome = 'Home';
|
|
SmkcLeft = 'Left';
|
|
SmkcUp = 'Up';
|
|
SmkcRight = 'Right';
|
|
SmkcDown = 'Down';
|
|
SmkcIns = 'Ins';
|
|
SmkcDel = 'Del';
|
|
SmkcShift = 'Shift+';
|
|
SmkcCtrl = 'Ctrl+';
|
|
SmkcAlt = 'Alt+';
|
|
|
|
MenuKeyCaps: array[TMenuKeyCap] of string = (
|
|
SmkcBkSp, SmkcTab, SmkcEsc, SmkcEnter, SmkcSpace, SmkcPgUp,
|
|
SmkcPgDn, SmkcEnd, SmkcHome, SmkcLeft, SmkcUp, SmkcRight,
|
|
SmkcDown, SmkcIns, SmkcDel, SmkcShift, SmkcCtrl, SmkcAlt);
|
|
|
|
function GetSpecialShortCutName(ShortCut: TShortCut): string;
|
|
{var
|
|
ScanCode: Integer;
|
|
KeyName: array[0..255] of Char;}
|
|
begin
|
|
Result := '';
|
|
// ToDo:
|
|
{
|
|
ScanCode := MapVirtualKey(WordRec(ShortCut).Lo, 0) shl 16;
|
|
if ScanCode <> 0 then
|
|
begin
|
|
GetKeyNameText(ScanCode, KeyName, SizeOf(KeyName));
|
|
Result := KeyName;
|
|
end;
|
|
}
|
|
end;
|
|
|
|
function ShortCutToShortCutText(ShortCut: TShortCut): string;
|
|
var
|
|
Name: string;
|
|
begin
|
|
case WordRec(ShortCut).Lo of
|
|
$08, $09:
|
|
Name := MenuKeyCaps[TMenuKeyCap(Ord(mkcBkSp) + WordRec(ShortCut).Lo - $08)];
|
|
$0D: Name := MenuKeyCaps[mkcEnter];
|
|
$1B: Name := MenuKeyCaps[mkcEsc];
|
|
$20..$28:
|
|
Name := MenuKeyCaps[TMenuKeyCap(Ord(mkcSpace) + WordRec(ShortCut).Lo - $20)];
|
|
$2D..$2E:
|
|
Name := MenuKeyCaps[TMenuKeyCap(Ord(mkcIns) + WordRec(ShortCut).Lo - $2D)];
|
|
$30..$39: Name := Chr(WordRec(ShortCut).Lo - $30 + Ord('0'));
|
|
$41..$5A: Name := Chr(WordRec(ShortCut).Lo - $41 + Ord('A'));
|
|
$60..$69: Name := Chr(WordRec(ShortCut).Lo - $60 + Ord('0'));
|
|
$70..$87: Name := 'F' + IntToStr(WordRec(ShortCut).Lo - $6F);
|
|
else
|
|
Name := GetSpecialShortCutName(ShortCut);
|
|
end;
|
|
if Name <> '' then
|
|
begin
|
|
Result := '';
|
|
if ShortCut and scShift <> 0 then Result := Result + MenuKeyCaps[mkcShift];
|
|
if ShortCut and scCtrl <> 0 then Result := Result + MenuKeyCaps[mkcCtrl];
|
|
if ShortCut and scAlt <> 0 then Result := Result + MenuKeyCaps[mkcAlt];
|
|
Result := Result + Name;
|
|
end
|
|
else Result := '';
|
|
end;
|
|
|
|
function ShortCutTextToShortCut(const ShortCutText: string): TShortCut;
|
|
|
|
function CompareFront(var StartPos: integer; const Front: string): Boolean;
|
|
begin
|
|
if (Front<>'') and (StartPos+length(Front)-1<=length(ShortCutText))
|
|
and (AnsiStrLIComp(@ShortCutText[StartPos], PChar(Front), Length(Front))= 0)
|
|
then begin
|
|
Result:=true;
|
|
inc(StartPos,length(Front));
|
|
end else
|
|
Result:=false;
|
|
end;
|
|
|
|
var
|
|
Key: TShortCut;
|
|
Shift: TShortCut;
|
|
StartPos: integer;
|
|
Name: string;
|
|
begin
|
|
Result := 0;
|
|
Shift := 0;
|
|
StartPos:=1;
|
|
while True do
|
|
begin
|
|
if CompareFront(StartPos, MenuKeyCaps[mkcShift]) then
|
|
Shift := Shift or scShift
|
|
else if CompareFront(StartPos, '^') then
|
|
Shift := Shift or scCtrl
|
|
else if CompareFront(StartPos, MenuKeyCaps[mkcCtrl]) then
|
|
Shift := Shift or scCtrl
|
|
else if CompareFront(StartPos, MenuKeyCaps[mkcAlt]) then
|
|
Shift := Shift or scAlt
|
|
else
|
|
Break;
|
|
end;
|
|
if ShortCutText = '' then Exit;
|
|
for Key := $08 to $255 do begin { Copy range from table in ShortCutToText }
|
|
Name:=ShortCutToShortCutText(Key);
|
|
if (Name<>'') and (length(Name)=length(ShortCutText)-StartPos+1)
|
|
and (AnsiStrLIComp(@ShortCutText[StartPos], PChar(Name), length(Name)) = 0)
|
|
then begin
|
|
Result := Key or Shift;
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function SendApplicationMessage(Msg: Cardinal; WParam, LParam: Longint
|
|
): Longint;
|
|
begin
|
|
if SendApplicationMessageFunction<>nil then
|
|
Result:=SendApplicationMessageFunction(Msg,WParam,LParam)
|
|
else
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure OwnerFormDesignerModified(AComponent: TComponent);
|
|
begin
|
|
if OwnerFormDesignerModifiedProc<>nil then
|
|
OwnerFormDesignerModifiedProc(AComponent);
|
|
end;
|
|
|
|
function OffSetRect(var ARect: TRect; dx,dy: Integer): Boolean;
|
|
Begin
|
|
with ARect do
|
|
begin
|
|
Left := Left + dx;
|
|
Right := Right + dx;
|
|
Top := Top + dy;
|
|
Bottom := Bottom + dy;
|
|
end;
|
|
if (ARect.Left >= 0) and (ARect.Top >= 0) then
|
|
Result := True
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
procedure FreeThenNil(var AnObject: TObject);
|
|
begin
|
|
if AnObject<>nil then begin
|
|
AnObject.Free;
|
|
AnObject:=nil;
|
|
end;
|
|
end;
|
|
|
|
{ TMethodList }
|
|
|
|
function TMethodList.GetItems(Index: integer): TMethod;
|
|
begin
|
|
Result:=FItems[Index];
|
|
end;
|
|
|
|
procedure TMethodList.SetItems(Index: integer; const AValue: TMethod);
|
|
begin
|
|
FItems[Index]:=AValue;
|
|
end;
|
|
|
|
destructor TMethodList.Destroy;
|
|
begin
|
|
ReAllocMem(FItems,0);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TMethodList.Count: integer;
|
|
begin
|
|
if Self<>nil then
|
|
Result:=FCount
|
|
else
|
|
Result:=0;
|
|
end;
|
|
|
|
function TMethodList.NextDownIndex(var Index: integer): boolean;
|
|
begin
|
|
if Self<>nil then begin
|
|
dec(Index);
|
|
if (Index>=FCount) then
|
|
Index:=FCount-1;
|
|
end else
|
|
Index:=-1;
|
|
Result:=(Index>=0);
|
|
end;
|
|
|
|
function TMethodList.IndexOf(const AMethod: TMethod): integer;
|
|
begin
|
|
if Self<>nil then begin
|
|
Result:=FCount-1;
|
|
while Result>=0 do begin
|
|
if (FItems[Result].Code=AMethod.Code)
|
|
and (FItems[Result].Data=AMethod.Data) then exit;
|
|
dec(Result);
|
|
end;
|
|
end else
|
|
Result:=-1;
|
|
end;
|
|
|
|
procedure TMethodList.Delete(Index: integer);
|
|
begin
|
|
dec(FCount);
|
|
if FCount>Index then
|
|
System.Move(FItems[Index+1],FItems[Index],(FCount-Index)*SizeOf(TMethod));
|
|
ReAllocMem(FItems,FCount*SizeOf(TMethod));
|
|
end;
|
|
|
|
procedure TMethodList.Remove(const AMethod: TMethod);
|
|
var
|
|
i: integer;
|
|
begin
|
|
if Self<>nil then begin
|
|
i:=IndexOf(AMethod);
|
|
if i>=0 then Delete(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TMethodList.Add(const AMethod: TMethod);
|
|
begin
|
|
inc(FCount);
|
|
ReAllocMem(FItems,FCount*SizeOf(TMethod));
|
|
FItems[FCount-1]:=AMethod;
|
|
end;
|
|
|
|
procedure TMethodList.Add(const AMethod: TMethod; AsLast: boolean);
|
|
begin
|
|
if AsLast then
|
|
Add(AMethod)
|
|
else
|
|
Insert(0,AMethod);
|
|
end;
|
|
|
|
procedure TMethodList.Insert(Index: integer; const AMethod: TMethod);
|
|
begin
|
|
if Index<FCount then
|
|
System.Move(FItems[Index],FItems[Index+1],(FCount-Index)*SizeOf(TMethod));
|
|
FItems[Index]:=AMethod;
|
|
end;
|
|
|
|
procedure TMethodList.Move(OldIndex, NewIndex: integer);
|
|
var
|
|
MovingMethod: TMethod;
|
|
begin
|
|
if OldIndex=NewIndex then exit;
|
|
MovingMethod:=FItems[OldIndex];
|
|
if OldIndex>NewIndex then
|
|
System.Move(FItems[NewIndex],FItems[NewIndex+1],
|
|
SizeOf(TMethod)*(OldIndex-NewIndex))
|
|
else
|
|
System.Move(FItems[NewIndex+1],FItems[NewIndex],
|
|
SizeOf(TMethod)*(NewIndex-OldIndex));
|
|
FItems[NewIndex]:=MovingMethod;
|
|
end;
|
|
|
|
procedure TMethodList.RemoveAllMethodsOfObject(const AnObject: TObject);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if Self=nil then exit;
|
|
i:=FCount-1;
|
|
while i>=0 do begin
|
|
if TObject(FItems[i].Data)=AnObject then Delete(i);
|
|
dec(i);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure RaiseGDBException(const Msg: string);
|
|
|
|
Raises an exception.
|
|
gdb does not catch fpc Exception objects, therefore this procedure raises
|
|
a standard AV which is catched by gdb.
|
|
------------------------------------------------------------------------------}
|
|
procedure RaiseGDBException(const Msg: string);
|
|
begin
|
|
writeln(rsERRORInLCL, Msg);
|
|
// creates an exception, that gdb catches:
|
|
writeln(rsCreatingGdbCatchableError);
|
|
if (length(Msg) div (length(Msg) div 10000))=0 then ;
|
|
end;
|
|
|
|
procedure MakeMinMax(var i1, i2: integer);
|
|
var
|
|
h: Integer;
|
|
begin
|
|
if i1>i2 then begin
|
|
h:=i1;
|
|
i1:=i2;
|
|
i2:=h;
|
|
end;
|
|
end;
|
|
|
|
procedure CalculateLeftTopWidthHeight(X1, Y1, X2, Y2: integer;
|
|
var Left, Top, Width, Height: integer);
|
|
begin
|
|
if X1<=X2 then begin
|
|
Left:=X1;
|
|
Width:=X2 - X1;
|
|
end else begin
|
|
Left:=X2;
|
|
Width:=X1 - X2;
|
|
end;
|
|
if Y1<=Y2 then begin
|
|
Top:=Y1;
|
|
Height:=Y2 - Y1;
|
|
end else begin
|
|
Top:=Y2;
|
|
Height:=Y1 - Y2;
|
|
end;
|
|
end;
|
|
|
|
function BreakString(const s: string; MaxLineLength, Indent: integer): string;
|
|
const
|
|
NewLine = {$IfDef win32}#13+{$EndIf}#10;
|
|
var
|
|
SrcLen: Integer;
|
|
APos: Integer;
|
|
Src: String;
|
|
SplitPos: Integer;
|
|
CurMaxLineLength: Integer;
|
|
begin
|
|
Result:='';
|
|
Src:=s;
|
|
CurMaxLineLength:=MaxLineLength;
|
|
if Indent>MaxLineLength-2 then Indent:=MaxLineLength-2;
|
|
if Indent<0 then MaxLineLength:=0;
|
|
repeat
|
|
SrcLen:=length(Src);
|
|
if SrcLen<=CurMaxLineLength then begin
|
|
Result:=Result+Src;
|
|
break;
|
|
end;
|
|
// split line
|
|
SplitPos:=0;
|
|
// search new line chars
|
|
APos:=1;
|
|
while (APos<=CurMaxLineLength) do begin
|
|
if Src[APos] in [#13,#10] then begin
|
|
SplitPos:=APos;
|
|
break;
|
|
end;
|
|
inc(APos);
|
|
end;
|
|
// search a space boundary
|
|
if SplitPos=0 then begin
|
|
APos:=CurMaxLineLength;
|
|
while APos>1 do begin
|
|
if (Src[APos-1] in [' ',#9])
|
|
and (not (Src[APos] in [' ',#9])) then begin
|
|
SplitPos:=APos;
|
|
break;
|
|
end;
|
|
dec(APos);
|
|
end;
|
|
end;
|
|
// search a word boundary
|
|
if SplitPos=0 then begin
|
|
APos:=CurMaxLineLength;
|
|
while APos>1 do begin
|
|
if (Src[APos] in ['A'..'Z','a'..'z'])
|
|
and (not (Src[APos-1] in ['A'..'Z','a'..'z'])) then begin
|
|
SplitPos:=APos;
|
|
break;
|
|
end;
|
|
dec(APos);
|
|
end;
|
|
end;
|
|
if SplitPos=0 then begin
|
|
// no word boundary found -> split chars
|
|
SplitPos:=CurMaxLineLength;
|
|
end;
|
|
// append part and newline
|
|
if (SplitPos<=SrcLen) and (Src[SplitPos] in [#10,#13]) then begin
|
|
// there is already a new line char at position
|
|
inc(SplitPos);
|
|
if (SplitPos<=SrcLen) and (Src[SplitPos] in [#10,#13])
|
|
and (Src[SplitPos]<>Src[SplitPos-1]) then
|
|
inc(SplitPos);
|
|
Result:=Result+copy(Src,1,SplitPos-1);
|
|
end else begin
|
|
Result:=Result+copy(Src,1,SplitPos-1)+NewLine;
|
|
end;
|
|
// append indent
|
|
if Indent>0 then
|
|
Result:=Result+StringOfChar(' ',Indent);
|
|
// calculate new LineLength
|
|
CurMaxLineLength:=MaxLineLength-Indent;
|
|
// cut string
|
|
Src:=copy(Src,SplitPos,length(Src)-SplitPos+1);
|
|
until false;
|
|
end;
|
|
|
|
initialization
|
|
SendApplicationMessageFunction:=nil;
|
|
OwnerFormDesignerModifiedProc:=nil;
|
|
|
|
end.
|
|
|