2022-09-17 17:26:39 +00:00
|
|
|
unit ExShape;
|
|
|
|
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
2022-09-21 10:04:13 +00:00
|
|
|
LCLVersion, Types, Graphics, Classes, SysUtils, ExtCtrls;
|
2022-09-17 17:26:39 +00:00
|
|
|
|
|
|
|
type
|
2022-09-21 10:04:13 +00:00
|
|
|
{$IF LCL_FullVersion < 2020000}
|
|
|
|
TVerticalAlignment = (taAlignTop, taAlignBottom, taVerticalCenter);
|
|
|
|
{$ENDIF}
|
|
|
|
|
2022-09-17 17:26:39 +00:00
|
|
|
TShapeText = class(TPersistent)
|
|
|
|
private
|
|
|
|
const
|
|
|
|
cDefMargin = 8;
|
|
|
|
private
|
|
|
|
FShape: TShape;
|
|
|
|
FHorAlignment: TAlignment;
|
|
|
|
FHorMargin: Integer;
|
|
|
|
FLines: TStrings;
|
|
|
|
FVertAlignment: TVerticalAlignment;
|
|
|
|
FVertMargin: Integer;
|
|
|
|
function IsHorMarginStored: Boolean;
|
|
|
|
function IsVertMarginStored: Boolean;
|
|
|
|
procedure SetHorAlignment(const AValue: TAlignment);
|
|
|
|
procedure SetHorMargin(const AValue: Integer);
|
|
|
|
procedure SetLines(const AValue: TStrings);
|
|
|
|
procedure SetVertAlignment(const AValue: TVerticalAlignment);
|
|
|
|
procedure SetVertMargin(const AValue: Integer);
|
|
|
|
protected
|
|
|
|
procedure Changed(Sender: TObject); virtual;
|
|
|
|
public
|
|
|
|
constructor Create(AShape: TShape);
|
|
|
|
destructor Destroy; override;
|
|
|
|
procedure Assign(ASource: TPersistent); override;
|
|
|
|
published
|
|
|
|
property HorAlignment: TAlignment read FHorAlignment write SetHorAlignment default taCenter;
|
|
|
|
property HorMargin: Integer read FHorMargin write SetHorMargin stored IsHorMarginStored;
|
|
|
|
property Lines: TStrings read FLines write SetLines;
|
|
|
|
property VertAlignment: TVerticalAlignment read FVertAlignment write SetVertAlignment default taVerticalCenter;
|
|
|
|
property VertMargin: Integer read FVertMargin write SetVertMargin stored IsVertMarginStored;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ TShapeEx}
|
|
|
|
|
|
|
|
TShapeEx = class(TShape)
|
|
|
|
private
|
|
|
|
FHeader: TShapeText;
|
|
|
|
FHeaderFont: TFont;
|
|
|
|
FText: TShapeText;
|
|
|
|
procedure SetHeader(const AValue: TShapeText);
|
|
|
|
procedure SetHeaderFont(const AValue: TFont);
|
|
|
|
procedure SetText(const AValue: TShapeText); reintroduce;
|
|
|
|
protected
|
|
|
|
function DefaultPlaceText(AText: TShapeText): TPoint;
|
|
|
|
procedure HeaderFontChanged(Sender: TObject);
|
|
|
|
function MeasureText(AText: TShapeText): TSize;
|
|
|
|
function TextFlags(AText: TShapeText): Integer;
|
|
|
|
public
|
|
|
|
constructor Create(AOwner: TComponent); override;
|
|
|
|
destructor Destroy; override;
|
|
|
|
procedure Paint; override;
|
|
|
|
published
|
2022-09-17 17:39:06 +00:00
|
|
|
property Font;
|
2022-09-17 17:26:39 +00:00
|
|
|
property Header: TShapeText read FHeader write SetHeader;
|
|
|
|
property HeaderFont: TFont read FHeaderFont write SetHeaderFont;
|
|
|
|
property Text: TShapeText read FText write SetText;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
uses
|
|
|
|
LCLIntf, LCLType;
|
|
|
|
|
|
|
|
{ TShapeText }
|
|
|
|
|
|
|
|
constructor TShapeText.Create(AShape: TShape);
|
|
|
|
begin
|
|
|
|
inherited Create;
|
|
|
|
FShape := AShape;
|
|
|
|
FLines := TStringList.Create;
|
|
|
|
FLines.SkipLastLineBreak := true;
|
|
|
|
TStringList(FLines).OnChange := @Changed;
|
|
|
|
FHorMargin := cDefMargin;
|
|
|
|
FVertMargin := cDefMargin;
|
|
|
|
FHorAlignment := taCenter;
|
|
|
|
FVertAlignment := taVerticalCenter;
|
|
|
|
end;
|
|
|
|
|
|
|
|
destructor TShapeText.Destroy;
|
|
|
|
begin
|
|
|
|
FLines.Free;
|
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeText.Assign(ASource: TPersistent);
|
|
|
|
begin
|
|
|
|
if ASource is TShapeText then
|
|
|
|
begin
|
|
|
|
FLines.Assign(TShapeText(ASource).Lines);
|
|
|
|
FHorAlignment := TShapeText(ASource).HorAlignment;
|
|
|
|
FHorMargin := TShapeText(ASource).HorMargin;
|
|
|
|
FVertAlignment := TShapeText(ASource).VertAlignment;
|
|
|
|
FVertMargin := TShapeText(ASource).VertMargin;
|
|
|
|
end else
|
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeText.Changed(Sender: TObject);
|
|
|
|
begin
|
|
|
|
if FShape <> nil then
|
|
|
|
FShape.Invalidate;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TShapeText.IsHorMarginStored: Boolean;
|
|
|
|
begin
|
|
|
|
Result := FHorMargin <> cDefMargin;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TShapeText.IsVertMarginStored: Boolean;
|
|
|
|
begin
|
|
|
|
Result := FVertMargin <> cDefMargin;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeText.SetHorAlignment(const AValue: TAlignment);
|
|
|
|
begin
|
|
|
|
if FHorAlignment <> AValue then
|
|
|
|
begin
|
|
|
|
FHorAlignment := AValue;
|
|
|
|
Changed(Self);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeText.SetHorMargin(const AValue: Integer);
|
|
|
|
begin
|
|
|
|
if FHorMargin <> AValue then
|
|
|
|
begin
|
|
|
|
FHorMargin := AValue;
|
|
|
|
Changed(Self);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeText.SetLines(const AValue: TStrings);
|
|
|
|
begin
|
|
|
|
if FLines.Text <> AValue.Text then
|
|
|
|
begin
|
|
|
|
FLines.Assign(AValue);
|
|
|
|
FLines.SkipLastLineBreak := true;
|
|
|
|
Changed(Self);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeText.SetVertAlignment(const AValue: TVerticalAlignment);
|
|
|
|
begin
|
|
|
|
if FVertAlignment <> AValue then
|
|
|
|
begin
|
|
|
|
FVertAlignment := AValue;
|
|
|
|
Changed(Self);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeText.SetVertMargin(const AValue: Integer);
|
|
|
|
begin
|
|
|
|
if FVertMargin <> AValue then
|
|
|
|
begin
|
|
|
|
FVertMargin := AValue;
|
|
|
|
Changed(Self);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{ TShapeEx }
|
|
|
|
|
|
|
|
constructor TShapeEx.Create(AOwner: TComponent);
|
|
|
|
begin
|
|
|
|
inherited Create(AOwner);
|
|
|
|
|
|
|
|
FHeader := TShapeText.Create(Self);
|
|
|
|
FHeader.VertAlignment := taAlignTop;
|
|
|
|
|
|
|
|
FHeaderFont := TFont.Create;
|
|
|
|
FHeaderFont.Style := [fsBold];
|
|
|
|
FHeaderFont.OnChange := @HeaderFontChanged;
|
|
|
|
|
|
|
|
FText := TShapeText.Create(self);
|
|
|
|
end;
|
|
|
|
|
|
|
|
destructor TShapeEx.Destroy;
|
|
|
|
begin
|
|
|
|
FText.Free;
|
|
|
|
FHeader.Free;
|
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TShapeEx.DefaultPlaceText(AText: TShapeText): TPoint;
|
|
|
|
var
|
|
|
|
lSize: TSize;
|
|
|
|
begin
|
|
|
|
if AText.Lines.Text = '' then
|
|
|
|
begin
|
|
|
|
Result := Point(MaxInt, MaxInt);
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
lSize := MeasureText(AText);
|
|
|
|
case AText.HorAlignment of
|
|
|
|
taLeftJustify: Result.X := AText.HorMargin;
|
|
|
|
taCenter: Result.X := (ClientWidth - lSize.CX) div 2;
|
|
|
|
taRightJustify: Result.X := ClientWidth - lSize.CX - AText.HorMargin;
|
|
|
|
end;
|
|
|
|
case AText.VertAlignment of
|
|
|
|
taAlignTop: Result.Y := AText.VertMargin;
|
|
|
|
taVerticalCenter: Result.Y := (ClientHeight - lSize.CY) div 2;
|
|
|
|
taAlignBottom: Result.Y := ClientHeight - lSize.CY - AText.VertMargin;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeEx.HeaderFontChanged(Sender: TObject);
|
|
|
|
begin
|
|
|
|
Invalidate;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TShapeEx.MeasureText(AText: TShapeText): TSize;
|
|
|
|
var
|
|
|
|
R: TRect;
|
|
|
|
flags: Integer;
|
|
|
|
s: String;
|
|
|
|
begin
|
|
|
|
s := AText.Lines.Text;
|
|
|
|
if s = '' then
|
|
|
|
begin
|
|
|
|
Result := Size(0, 0);
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
flags := DT_NOPREFIX or DT_CALCRECT or DT_WORDBREAK;
|
|
|
|
if AText = FHeader then
|
|
|
|
Canvas.Font.Assign(FHeaderFont)
|
|
|
|
else
|
|
|
|
Canvas.Font.Assign(Font);
|
|
|
|
|
|
|
|
R := Rect(0, 0, ClientWidth, 0);
|
|
|
|
R.Right := R.Right - 2 * AText.HorMargin;
|
|
|
|
DrawText(Canvas.Handle, PChar(s), Length(s), R, flags);
|
|
|
|
Result := Size(R.Width, R.Height);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeEx.Paint;
|
|
|
|
type
|
|
|
|
TTextItem = (tiHeader, tiText);
|
|
|
|
var
|
|
|
|
P: array[TTextItem] of TPoint;
|
|
|
|
s: array[TTextItem] of String;
|
|
|
|
lSize: array[TTextItem] of TSize;
|
|
|
|
flags: Integer;
|
|
|
|
R: TRect;
|
|
|
|
h, m: Integer;
|
|
|
|
begin
|
|
|
|
// Draw the shape itself
|
|
|
|
inherited Paint;
|
|
|
|
|
|
|
|
s[tiHeader] := FHeader.Lines.Text;
|
|
|
|
s[tiText] := FText.Lines.Text;
|
|
|
|
|
|
|
|
// Get text size
|
|
|
|
lSize[tiHeader] := MeasureText(FHeader);
|
|
|
|
lSize[tiText] := MeasureText(FText);
|
|
|
|
|
|
|
|
// Get default text positions
|
|
|
|
P[tiHeader] := DefaultPlaceText(FHeader);
|
|
|
|
P[tiText] := DefaultPlaceText(FText);
|
|
|
|
|
|
|
|
// Fix special cases of positioning
|
|
|
|
if (s[tiHeader] <> '') and (s[tiText] <> '') then
|
|
|
|
begin
|
|
|
|
// Texts colliding at top
|
|
|
|
if (FHeader.VertAlignment = taAlignTop) and (FText.VertAlignment = taAlignTop) then
|
|
|
|
P[tiText].Y := P[tiHeader].Y + lSize[tiHeader].CY + P[tiText].Y
|
|
|
|
else
|
|
|
|
// Texts colliding at center
|
|
|
|
if (FHeader.VertAlignment = taVerticalCenter) and (FText.VertAlignment = taVerticalCenter) then
|
|
|
|
begin
|
|
|
|
// Average margin value
|
|
|
|
m := (FHeader.VertMargin + FText.VertMargin) div 2;
|
|
|
|
// Combined height of header and text, with average margin
|
|
|
|
h := lSize[tiHeader].CY + lSize[tiText].CY + m;
|
|
|
|
P[tiHeader].Y := (ClientHeight - h) div 2;
|
|
|
|
P[tiText].Y := P[tiHeader].Y + lSize[tiHeader].CY + m;
|
|
|
|
end else
|
|
|
|
// Texts colliding at bottom
|
|
|
|
if (FHeader.VertAlignment = taAlignBottom) and (FText.VertAlignment = taAlignBottom) then
|
|
|
|
P[tiText].Y := P[tiHeader].Y - lSize[tiText].CY - FText.VertMargin
|
|
|
|
else
|
|
|
|
// Adjust centered Text when header is at top or bottom
|
|
|
|
if (FText.VertAlignment = taVerticalCenter) then
|
|
|
|
begin
|
|
|
|
// Vertically available space without header
|
|
|
|
h := ClientHeight - FHeader.VertMargin - lSize[tiHeader].CY;
|
|
|
|
if (FHeader.VertAlignment = taAlignTop) then
|
|
|
|
P[tiText].Y := P[tiHeader].Y + lSize[tiHeader].CY + (h - lSize[tiText].CY) div 2
|
|
|
|
else
|
|
|
|
if (FHeader.VertAlignment = taAlignBottom) then // Keep header at bottom to become a footer
|
|
|
|
P[tiText].Y := (h - lSize[tiText].CY) div 2;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
Canvas.Brush.Style := bsClear;
|
|
|
|
flags := DT_NOPREFIX or DT_WORDBREAK;
|
|
|
|
|
|
|
|
// Draw the header
|
|
|
|
Canvas.Font.Assign(FHeaderFont);
|
|
|
|
R.TopLeft := Point(0, 0);
|
|
|
|
R.BottomRight := TPoint(lSize[tiHeader]);
|
|
|
|
OffsetRect(R, P[tiHeader].X, P[tiHeader].Y);
|
|
|
|
DrawText(Canvas.Handle, PChar(s[tiHeader]), Length(s[tiHeader]), R, flags or TextFlags(FHeader));
|
|
|
|
|
|
|
|
// Draw the text
|
|
|
|
Canvas.Font.Assign(Font);
|
|
|
|
R.TopLeft := Point(0, 0);
|
|
|
|
R.BottomRight := TPoint(lSize[tiText]);
|
|
|
|
OffsetRect(R, P[tiText].X, P[tiText].Y);
|
|
|
|
DrawText(Canvas.Handle, PChar(s[tiText]), Length(s[tiText]), R, flags or TextFlags(FText));
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeEx.SetHeader(const AValue: TShapeText);
|
|
|
|
begin
|
|
|
|
FHeader.Assign(AValue);
|
|
|
|
Invalidate;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeEx.SetHeaderFont(const AValue: TFont);
|
|
|
|
begin
|
|
|
|
FHeaderFont.Assign(AValue);
|
|
|
|
Invalidate;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TShapeEx.SetText(const AValue: TShapeText);
|
|
|
|
begin
|
|
|
|
FText.Assign(AValue);
|
|
|
|
Invalidate;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TShapeEx.TextFlags(AText: TShapeText): Integer;
|
|
|
|
begin
|
|
|
|
case AText.HorAlignment of
|
|
|
|
taLeftJustify: Result := DT_LEFT;
|
|
|
|
taCenter: Result := DT_CENTER;
|
|
|
|
taRightJustify: Result := DT_RIGHT;
|
|
|
|
end;
|
|
|
|
case AText.VertAlignment of
|
|
|
|
taAlignTop: Result := Result or DT_TOP;
|
|
|
|
taVerticalCenter: Result := Result or DT_VCENTER;
|
|
|
|
taAlignBottom: Result := Result or DT_BOTTOM;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
end.
|
|
|
|
|