Files
lazarus-ccr/components/exctrls/source/exshape.pas

363 lines
9.3 KiB
ObjectPascal
Raw Normal View History

unit ExShape;
{$mode ObjFPC}{$H+}
interface
uses
LCLVersion, Types, Graphics, Classes, SysUtils, ExtCtrls;
type
{$IF LCL_FullVersion < 2020000}
TVerticalAlignment = (taAlignTop, taAlignBottom, taVerticalCenter);
{$ENDIF}
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
property Font;
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.