2131 lines
61 KiB
ObjectPascal
2131 lines
61 KiB
ObjectPascal
{-----------------------------------------------------------------------------
|
|
The contents of this file are subject to the Mozilla Public License
|
|
Version 1.1 (the "License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/MPL-1.1.html
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
|
|
the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is: JvDiagramShape.PAS, released on 2002-03-22.
|
|
|
|
Original Developer: Jim Cooper <jcooper att tabdee dott ltd dott uk>
|
|
Contributor(s): Michael Beck <mbeck1 att compuserve dott com>
|
|
|
|
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
|
|
located at http://jvcl.sourceforge.net
|
|
|
|
Known Issues:
|
|
-----------------------------------------------------------------------------}
|
|
// $Id: JvDiagramShape.pas 10612 2006-05-19 19:04:09Z jfudickar $
|
|
|
|
unit JvDiagramShape;
|
|
|
|
{$I jvcl.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFDEF UNITVERSIONING}
|
|
JclUnitVersioning,
|
|
{$ENDIF UNITVERSIONING}
|
|
Windows,
|
|
{$IFDEF VisualCLX}
|
|
QTypes,
|
|
{$ENDIF VisualCLX}
|
|
Classes, Graphics, Controls, ExtCtrls, ImgList,
|
|
JvComponent;
|
|
|
|
type
|
|
TJvTextShape = class;
|
|
|
|
// All controls descend from this, to help with streaming and unique naming
|
|
TJvCustomDiagramShape = class(TJvGraphicControl)
|
|
private
|
|
FCanProcessMouseMsg: Boolean;
|
|
FCaption: TJvTextShape;
|
|
FSelected: Boolean;
|
|
FWasCovered: Boolean;
|
|
FMultiSelect: Boolean;
|
|
FRightClickSelect: Boolean;
|
|
FAlignment: TAlignment;
|
|
protected
|
|
procedure SetCaption(Value: TJvTextShape); virtual;
|
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer); override;
|
|
procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer); override;
|
|
function GetCustomShapeAtPos(X, Y: Integer): TJvCustomDiagramShape;
|
|
property CanProcessMouseMsg: Boolean read FCanProcessMouseMsg
|
|
write FCanProcessMouseMsg;
|
|
{$IFDEF VCL}
|
|
procedure SetParent(AParent: TWinControl); override;
|
|
{$ENDIF VCL}
|
|
{$IFDEF VisualCLX}
|
|
procedure SetParent(const AParent: TWidgetControl); override;
|
|
{$ENDIF VisualCLX}
|
|
procedure SetSelected(Value: Boolean); virtual;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
property MultiSelect: Boolean read FMultiSelect write FMultiSelect;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
|
|
procedure AlignCaption(Alignment: TAlignment);
|
|
// Class methods to save and load all TJvCustomDiagramShape components
|
|
// that are children of a given control. They are class methods so that an
|
|
// instance of TJvCustomDiagramShape is not required
|
|
class procedure SaveToFile(const FileName: string; ParentControl: TWinControl);
|
|
class procedure LoadFromFile(const FileName: string; ParentControl: TWinControl);
|
|
class procedure DeleteAllShapes(ParentControl: TWinControl);
|
|
class procedure DeleteSelectedShapes(ParentControl: TWinControl);
|
|
class procedure UnselectAllShapes(ParentControl: TWinControl);
|
|
class procedure SetMultiSelected(ParentControl: TWinControl; Value: Boolean);
|
|
property Selected: Boolean read FSelected write SetSelected;
|
|
property Caption: TJvTextShape read FCaption write SetCaption;
|
|
property RightClickSelect: Boolean read FRightClickSelect write FRightClickSelect default True;
|
|
property OnDblClick;
|
|
end;
|
|
|
|
TJvMoveableShape = class(TJvCustomDiagramShape)
|
|
private
|
|
FOrigin: TPoint;
|
|
FMoving: Boolean;
|
|
protected
|
|
procedure StartMove(X, Y: Integer);
|
|
procedure Move(DeltaX, DeltaY: Integer);
|
|
procedure EndMove;
|
|
function ValidMove(DeltaX, DeltaY: Integer): Boolean;
|
|
procedure MoveShapes(DeltaX, DeltaY: Integer);
|
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer); override;
|
|
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
|
|
procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer); override;
|
|
property Moving: Boolean read FMoving write FMoving;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
published
|
|
property Caption;
|
|
property RightClickSelect;
|
|
// Make these properties available
|
|
property OnClick;
|
|
property OnDblClick;
|
|
end;
|
|
|
|
TJvSizingMode = (smTopLeft, smTop, smTopRight, smLeft, smRight,
|
|
smBottomLeft, smBottom, smBottomRight, smNone);
|
|
|
|
TJvSizeableShape = class(TJvMoveableShape)
|
|
private
|
|
FSizingMode: TJvSizingMode;
|
|
FSizeOrigin: TPoint;
|
|
FSizeRectHeight: Integer;
|
|
FSizeRectWidth: Integer;
|
|
FMinHeight: Integer;
|
|
FMinWidth: Integer;
|
|
protected
|
|
procedure SetSelected(Value: Boolean); override;
|
|
procedure Paint; override;
|
|
procedure DrawSizingRects;
|
|
function GetSizeRect(SizeRectType: TJvSizingMode): TRect;
|
|
procedure CheckForSizeRects(X, Y: Integer);
|
|
procedure ResizeControl(X, Y: Integer);
|
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer); override;
|
|
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
|
|
procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer); override;
|
|
property SizingMode: TJvSizingMode read FSizingMode write FSizingMode;
|
|
property SizeRectHeight: Integer read FSizeRectHeight write FSizeRectHeight;
|
|
property SizeRectWidth: Integer read FSizeRectWidth write FSizeRectWidth;
|
|
property MinHeight: Integer read FMinHeight write FMinHeight;
|
|
property MinWidth: Integer read FMinWidth write FMinWidth;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
|
|
end;
|
|
|
|
TJvTextShape = class(TJvSizeableShape)
|
|
private
|
|
FText: TCaption;
|
|
FAutoSize: Boolean;
|
|
FFont: TFont;
|
|
{$IFDEF VCL}
|
|
procedure SetText(const Value: TCaption);
|
|
{$ENDIF VCL}
|
|
procedure SetFont(Value: TFont);
|
|
procedure FontChange(Sender: TObject);
|
|
protected
|
|
procedure SetAutoSize(Value: Boolean); {$IFDEF VCL} override; {$ENDIF}
|
|
procedure RefreshText;
|
|
{$IFDEF VCL}
|
|
procedure SetParent(AParent: TWinControl); override;
|
|
{$ENDIF VCL}
|
|
{$IFDEF VisualCLX}
|
|
procedure SetParent(const AParent: TWidgetControl); override;
|
|
procedure SetText(const Value: TCaption); override;
|
|
function GetText: TCaption; override;
|
|
{$ENDIF VisualCLX}
|
|
procedure Paint; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
|
|
published
|
|
property Text: TCaption read FText write SetText;
|
|
property AutoSize: Boolean read FAutoSize write SetAutoSize default True;
|
|
property Font: TFont read FFont write SetFont;
|
|
end;
|
|
|
|
TJvBitmapShape = class(TJvMoveableShape)
|
|
private
|
|
FImages: TImageList;
|
|
FImageIndex: Integer;
|
|
procedure SetImages(Value: TImageList);
|
|
procedure SetImageIndex(Value: Integer);
|
|
protected
|
|
procedure SetSelected(Value: Boolean); override;
|
|
procedure Paint; override;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
published
|
|
property Images: TImageList read FImages write SetImages;
|
|
property ImageIndex: Integer read FImageIndex write SetImageIndex;
|
|
// Make these properties available
|
|
property PopupMenu;
|
|
property OnMouseDown;
|
|
property OnMouseUp;
|
|
property OnMouseMove;
|
|
property OnClick;
|
|
property OnDblClick;
|
|
end;
|
|
|
|
TJvStandardShape = class(TJvSizeableShape)
|
|
private
|
|
FShapeType: TShapeType;
|
|
FLineColor: TColor;
|
|
procedure SetShapeType(Value: TShapeType);
|
|
protected
|
|
procedure Paint; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
published
|
|
property ShapeType: TShapeType read FShapeType write SetShapeType;
|
|
// (rom) renamed from LineColour
|
|
property LineColor: TColor read FLineColor write FLineColor default clBlack;
|
|
end;
|
|
|
|
TJvConnectionSide = (csLeft, csRight, csTop, csBottom);
|
|
|
|
TJvConnection = class(TPersistent)
|
|
private
|
|
FShape: TJvCustomDiagramShape;
|
|
FSide: TJvConnectionSide; // Side to connect to
|
|
FOffset: Integer; // Distance from top or left of side
|
|
public
|
|
constructor Create;
|
|
procedure Assign(Source: TPersistent); override;
|
|
// Gets connection point in parent's coordinates
|
|
function ConnPoint(TerminatorRect: TRect): TPoint;
|
|
// Gets terminator connection point in parent's coordinates
|
|
function TermPoint(TerminatorRect: TRect): TPoint;
|
|
// Functions to get boundaries of the terminators
|
|
function LeftMost(TerminatorRect: TRect): TPoint;
|
|
function RightMost(TerminatorRect: TRect): TPoint;
|
|
function TopMost(TerminatorRect: TRect): TPoint;
|
|
function BottomMost(TerminatorRect: TRect): TPoint;
|
|
published
|
|
property Shape: TJvCustomDiagramShape read FShape write FShape;
|
|
property Side: TJvConnectionSide read FSide write FSide;
|
|
property Offset: Integer read FOffset write FOffset;
|
|
end;
|
|
|
|
TJvConnector = class(TJvCustomDiagramShape)
|
|
private
|
|
FLineWidth: Integer;
|
|
FLineColor: TColor;
|
|
// The shapes connected by this control
|
|
FStartConn: TJvConnection;
|
|
FEndConn: TJvConnection;
|
|
// Area of the terminator symbol to be drawn (in horizontal position)
|
|
FStartTermRect: TRect;
|
|
FEndTermRect: TRect;
|
|
// Used to track required movement of the caption
|
|
FMidPoint: TPoint;
|
|
procedure SetLineWidth(Value: Integer);
|
|
function GetConn(Index: Integer): TJvConnection;
|
|
procedure SetConn(Index: Integer; Value: TJvConnection);
|
|
function GetTermRect(Index: Integer): TRect;
|
|
procedure SetTermRect(Index: Integer; Value: TRect);
|
|
procedure CheckSize(var AWidth, AHeight: Integer);
|
|
function GetMidPoint: TPoint;
|
|
protected
|
|
procedure SetCaption(Value: TJvTextShape); override;
|
|
procedure Paint; override;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
// For drawing arrows etc. Called from Paint.
|
|
procedure DrawStartTerminator; virtual;
|
|
procedure DrawEndTerminator; virtual;
|
|
procedure MoveCaption;
|
|
// Converts point from parent's coordinates to own coordinates
|
|
function Convert(APoint: TPoint): TPoint;
|
|
function IsConnected(ConnectedShape: TJvCustomDiagramShape): Boolean;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
// Restrict the minimum size
|
|
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
|
|
// Called when moving one of the connected shapes
|
|
procedure SetBoundingRect;
|
|
procedure SetConnections(TheStartConn, TheEndConn: TJvConnection);
|
|
property StartTermRect: TRect index 1 read GetTermRect write SetTermRect;
|
|
property EndTermRect: TRect index 2 read GetTermRect write SetTermRect;
|
|
published
|
|
// Publish these properties so that component streaming can be used to
|
|
// store them in a file
|
|
property LineWidth: Integer read FLineWidth write SetLineWidth default 1;
|
|
property LineColor: TColor read FLineColor write FLineColor default clBlack;
|
|
property StartConn: TJvConnection index 1 read GetConn write SetConn;
|
|
property EndConn: TJvConnection index 2 read GetConn write SetConn;
|
|
property MidPoint: TPoint read GetMidPoint;
|
|
property Caption;
|
|
property RightClickSelect;
|
|
// Make these properties available
|
|
property OnClick;
|
|
property OnDblClick;
|
|
end;
|
|
|
|
TJvSingleHeadArrow = class(TJvConnector)
|
|
protected
|
|
procedure DrawArrowHead(ConnPt, TermPt: TPoint);
|
|
procedure DrawEndTerminator; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
end;
|
|
|
|
TJvSingleHeadOpenDashArrow = class(TJvConnector)
|
|
protected
|
|
procedure Paint; override;
|
|
procedure DrawArrowHead(ConnPt, TermPt: TPoint);
|
|
procedure DrawEndTerminator; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
end;
|
|
|
|
TJvBluntSingleHeadOpenDashArrow = class(TJvSingleHeadOpenDashArrow)
|
|
protected
|
|
procedure DrawStartTerminator; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
end;
|
|
|
|
TJvBluntSingleHeadArrow = class(TJvSingleHeadArrow)
|
|
protected
|
|
procedure DrawStartTerminator; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
end;
|
|
|
|
TJvSubCaseArrow = class(TJvConnector)
|
|
protected
|
|
procedure DrawArrowHead(ConnPt, TermPt: TPoint);
|
|
procedure DrawEndTerminator; override;
|
|
procedure DrawStartTerminator; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
end;
|
|
|
|
TJvDoubleHeadArrow = class(TJvSingleHeadArrow)
|
|
protected
|
|
procedure DrawStartTerminator; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
end;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
const
|
|
UnitVersioning: TUnitVersionInfo = (
|
|
RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvDiagramShape.pas $';
|
|
Revision: '$Revision: 10612 $';
|
|
Date: '$Date: 2006-05-19 21:04:09 +0200 (ven., 19 mai 2006) $';
|
|
LogPath: 'JVCL\run'
|
|
);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysUtils,
|
|
JvTypes, JvConsts;
|
|
|
|
type
|
|
// This type is solely for the acccess to the protected MouseDown method
|
|
TCrackTControl = class(TControl);
|
|
|
|
var
|
|
// Used in unique naming scheme. It is global in this unit to enable a
|
|
// 'memory' of the component names used during the lifetime of this unit.
|
|
GlobalShapeCount: Integer = 1;
|
|
|
|
procedure NoLessThan(var Value: Integer; Limit: Integer);
|
|
begin
|
|
if Value < Limit then
|
|
Value := Limit;
|
|
end;
|
|
|
|
function RectHeight(ARect: TRect): Integer;
|
|
begin
|
|
Result := ARect.Bottom - ARect.Top;
|
|
end;
|
|
|
|
function RectWidth(ARect: TRect): Integer;
|
|
begin
|
|
Result := ARect.Right - ARect.Left;
|
|
end;
|
|
|
|
function InRect(X, Y: Integer; ARect: TRect): Boolean;
|
|
begin
|
|
Result := (X >= ARect.Left) and (X <= ARect.Right) and
|
|
(Y >= ARect.Top) and (Y <= ARect.Bottom);
|
|
end;
|
|
|
|
function Min(A: array of Integer): Integer;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
// (rom) the "Purely" comment is wrong
|
|
// (rom) the function explicitly handles empty arrays
|
|
Result := 0; // Purely to stop compiler warnings
|
|
for I := Low(A) to High(A) do
|
|
if I = Low(A) then
|
|
Result := A[I]
|
|
else
|
|
if A[I] < Result then
|
|
Result := A[I];
|
|
end;
|
|
|
|
function Max(A: array of Integer): Integer;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := 0; // Purely to stop compiler warnings
|
|
for I := Low(A) to High(A) do
|
|
if I = Low(A) then
|
|
Result := A[I]
|
|
else
|
|
if A[I] > Result then
|
|
Result := A[I];
|
|
end;
|
|
|
|
//=== { TJvCustomDiagramShape } ==============================================
|
|
|
|
constructor TJvCustomDiagramShape.Create(AOwner: TComponent);
|
|
var
|
|
AlreadyUsed: Boolean;
|
|
I: Integer;
|
|
TempName: string;
|
|
begin
|
|
inherited Create(AOwner);
|
|
FCanProcessMouseMsg := True;
|
|
FCaption := nil;
|
|
FSelected := False;
|
|
FWasCovered := False;
|
|
|
|
// (rom) this was removed, but should be handled
|
|
//if AOwner = nil then
|
|
//Exit;
|
|
// Give the component a name and ensure that it is unique
|
|
repeat
|
|
// Use a local variable to hold the name, so that don't get exceptions
|
|
// raised on duplicate names
|
|
TempName := 'Shape' + IntToStr(GlobalShapeCount);
|
|
Inc(GlobalShapeCount);
|
|
AlreadyUsed := False;
|
|
|
|
// Loop through all the components on the form to ensure that this name
|
|
// is not already in use
|
|
for I := 0 to Owner.ComponentCount - 1 do
|
|
if Owner.Components[I].Name = TempName then
|
|
begin
|
|
// Try the next component name as this one is used already
|
|
AlreadyUsed := True;
|
|
Break;
|
|
end;
|
|
until not AlreadyUsed;
|
|
Name := TempName;
|
|
end;
|
|
|
|
destructor TJvCustomDiagramShape.Destroy;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
FreeAndNil(FCaption);
|
|
// First check that this control has been placed on a form
|
|
if Assigned(Parent) then
|
|
begin
|
|
// Search parent control for TJvConnector components that connect
|
|
// to this component
|
|
I := 0;
|
|
while I < Parent.ControlCount do
|
|
if (Parent.Controls[I] is TJvConnector) and
|
|
(TJvConnector(Parent.Controls[I]).IsConnected(Self)) then
|
|
Parent.Controls[I].Free
|
|
else
|
|
Inc(I);
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvCustomDiagramShape.SetCaption(Value: TJvTextShape);
|
|
begin
|
|
if (Value = nil) and Assigned(FCaption) then
|
|
begin
|
|
FCaption.Free;
|
|
FCaption := nil;
|
|
end
|
|
else
|
|
if Value <> FCaption then
|
|
begin
|
|
FCaption := Value;
|
|
FCaption.Parent := Self.Parent;
|
|
// Ensure the caption gets aligned correctly. Ths only needs to happen if
|
|
// the caption has not already been set in place (it will already be in the
|
|
// right place if we are loading this from a file).
|
|
if (FCaption.Left = 0) and (FCaption.Top = 0) then
|
|
AlignCaption(taCenter);
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF VisualCLX}
|
|
procedure TJvCustomDiagramShape.SetParent(const AParent: TWidgetControl);
|
|
{$ENDIF VisualCLX}
|
|
{$IFDEF VCL}
|
|
procedure TJvCustomDiagramShape.SetParent(AParent: TWinControl);
|
|
{$ENDIF VCL}
|
|
begin
|
|
inherited SetParent(AParent);
|
|
if Assigned(FCaption) then
|
|
FCaption.Parent := AParent;
|
|
end;
|
|
|
|
procedure TJvCustomDiagramShape.SetSelected(Value: Boolean);
|
|
begin
|
|
FSelected := Value;
|
|
if Assigned(FCaption) then
|
|
FCaption.SetSelected(Value);
|
|
end;
|
|
|
|
procedure TJvCustomDiagramShape.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
|
|
if not Assigned(Parent) then
|
|
Exit;
|
|
// Search parent control for TJvConnector components
|
|
for I := 0 to Parent.ControlCount - 1 do
|
|
if Parent.Controls[I] is TJvConnector then
|
|
if TJvConnector(Parent.Controls[I]).IsConnected(Self) then
|
|
// Resize the connector, but don't draw it yet
|
|
TJvConnector(Parent.Controls[I]).SetBoundingRect;
|
|
AlignCaption(FAlignment);
|
|
end;
|
|
|
|
procedure TJvCustomDiagramShape.Notification(AComponent: TComponent; Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if Operation = opRemove then
|
|
if AComponent = FCaption then
|
|
FCaption := nil;
|
|
end;
|
|
|
|
procedure TJvCustomDiagramShape.MouseDown(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
var
|
|
TempPt: TPoint;
|
|
CoveredShape: TJvCustomDiagramShape;
|
|
begin
|
|
if CanProcessMouseMsg then
|
|
begin
|
|
BringToFront;
|
|
MouseCapture := True;
|
|
inherited MouseDown(Button, Shift, X, Y);
|
|
Exit;
|
|
end;
|
|
|
|
// Pass message on to any covered control capable of handling it
|
|
CoveredShape := GetCustomShapeAtPos(X, Y);
|
|
TempPt := Point(X, Y);
|
|
MouseCapture := False;
|
|
|
|
if CoveredShape <> nil then
|
|
begin
|
|
SendToBack;
|
|
// Convert coordinates to covered shape's coordinates
|
|
TempPt := CoveredShape.ScreenToClient(ClientToScreen(TempPt));
|
|
// Send the mouse down message to the covered shape
|
|
CoveredShape.MouseDown(Button, Shift, TempPt.X, TempPt.Y);
|
|
// Flag the control as having been covered because we lose a mouse click
|
|
CoveredShape.FWasCovered := True;
|
|
end
|
|
else
|
|
if Assigned(Parent) then
|
|
begin
|
|
// Send mouse down message to Parent. The typecast is purely to gain access
|
|
// to the Parent.MouseDown method. Need to convert coordinates to parent's
|
|
// coordinates
|
|
TempPt := Parent.ScreenToClient(ClientToScreen(TempPt));
|
|
TCrackTControl(Parent).MouseDown(Button, Shift, TempPt.X, TempPt.Y);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvCustomDiagramShape.MouseUp(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
begin
|
|
inherited MouseUp(Button, Shift, X, Y);
|
|
if FWasCovered then
|
|
begin
|
|
// We will lose a mouse click, so replace it
|
|
Click;
|
|
FWasCovered := False;
|
|
end;
|
|
end;
|
|
|
|
function TJvCustomDiagramShape.GetCustomShapeAtPos(X, Y: Integer): TJvCustomDiagramShape;
|
|
var
|
|
I: Integer;
|
|
Pt: TPoint;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(Parent) then
|
|
Exit;
|
|
|
|
Pt := Parent.ScreenToClient(ClientToScreen(Point(X, Y)));
|
|
|
|
for I := 0 to Parent.ControlCount - 1 do
|
|
if (Parent.Controls[I] <> Self) and
|
|
(Parent.Controls[I] is TJvCustomDiagramShape) and
|
|
TJvCustomDiagramShape(Parent.Controls[I]).CanProcessMouseMsg and
|
|
InRect(Pt.X, Pt.Y, Parent.Controls[I].BoundsRect) then
|
|
begin
|
|
Result := TJvCustomDiagramShape(Parent.Controls[I]);
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvCustomDiagramShape.AlignCaption(Alignment: TAlignment);
|
|
var
|
|
ALeft, ATop, AWidth, AHeight: Integer;
|
|
begin
|
|
FAlignment := Alignment;
|
|
if not Assigned(FCaption) then
|
|
Exit;
|
|
|
|
ALeft := Left;
|
|
ATop := Top + Height + 5;
|
|
AWidth := FCaption.Width;
|
|
AHeight := FCaption.Height;
|
|
|
|
case Alignment of
|
|
taLeftJustify:
|
|
ALeft := Left;
|
|
taRightJustify:
|
|
ALeft := Left + Width - 1;
|
|
taCenter:
|
|
ALeft := Left + ((Width - FCaption.Width) div 2);
|
|
end;
|
|
FCaption.SetBounds(ALeft, ATop, AWidth, AHeight);
|
|
end;
|
|
|
|
class procedure TJvCustomDiagramShape.SaveToFile(const FileName: string;
|
|
ParentControl: TWinControl);
|
|
var
|
|
FS: TFileStream;
|
|
Writer: TWriter;
|
|
RealName: string;
|
|
begin
|
|
FS := TFileStream.Create(FileName, fmCreate or fmShareDenyWrite);
|
|
Writer := TWriter.Create(FS, 1024);
|
|
try
|
|
Writer.Root := ParentControl.Owner;
|
|
RealName := ParentControl.Name;
|
|
ParentControl.Name := '';
|
|
Writer.WriteComponent(ParentControl);
|
|
ParentControl.Name := RealName;
|
|
finally
|
|
Writer.Free;
|
|
FS.Free;
|
|
end;
|
|
end;
|
|
|
|
class procedure TJvCustomDiagramShape.LoadFromFile(const FileName: string;
|
|
ParentControl: TWinControl);
|
|
var
|
|
FS: TFileStream;
|
|
Reader: TReader;
|
|
RealName: string;
|
|
begin
|
|
DeleteAllShapes(ParentControl);
|
|
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
|
|
Reader := TReader.Create(FS, 1024);
|
|
try
|
|
// Save the parent's name, in case we are reading into a different
|
|
// control than we saved the diagram from
|
|
RealName := ParentControl.Name;
|
|
Reader.Root := ParentControl.Owner;
|
|
Reader.BeginReferences;
|
|
Reader.ReadComponent(ParentControl);
|
|
Reader.FixupReferences;
|
|
// Restore the parent's name
|
|
ParentControl.Name := RealName;
|
|
finally
|
|
Reader.EndReferences;
|
|
Reader.Free;
|
|
FS.Free;
|
|
end;
|
|
end;
|
|
|
|
class procedure TJvCustomDiagramShape.DeleteAllShapes(ParentControl: TWinControl);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
// Delete controls from ParentControl
|
|
I := 0;
|
|
// (rom) added Assigned for security
|
|
if Assigned(ParentControl) then
|
|
while I < ParentControl.ControlCount do
|
|
if ParentControl.Controls[I] is TJvCustomDiagramShape then
|
|
ParentControl.Controls[I].Free
|
|
// Note that there is no need to increment the counter, because the
|
|
// next component (if any) will now be at the same position in Controls[]
|
|
else
|
|
Inc(I);
|
|
end;
|
|
|
|
class procedure TJvCustomDiagramShape.DeleteSelectedShapes(ParentControl: TWinControl);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
// Delete controls from ParentControl if they are flagged as selected
|
|
I := 0;
|
|
// (rom) added Assigned for security
|
|
if Assigned(ParentControl) then
|
|
while I < ParentControl.ControlCount do
|
|
if (ParentControl.Controls[I] is TJvCustomDiagramShape) and
|
|
(TJvCustomDiagramShape(ParentControl.Controls[I]).Selected) then
|
|
ParentControl.Controls[I].Free
|
|
// Note that there is no need to increment the counter, because the
|
|
// next component (if any) will now be at the same position in Controls[]
|
|
else
|
|
Inc(I);
|
|
end;
|
|
|
|
class procedure TJvCustomDiagramShape.UnselectAllShapes(ParentControl: TWinControl);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
// (rom) added Assigned for security
|
|
if Assigned(ParentControl) then
|
|
for I := 0 to ParentControl.ControlCount - 1 do
|
|
if ParentControl.Controls[I] is TJvCustomDiagramShape then
|
|
TJvCustomDiagramShape(ParentControl.Controls[I]).Selected := False;
|
|
end;
|
|
|
|
class procedure TJvCustomDiagramShape.SetMultiSelected(ParentControl: TWinControl;
|
|
Value: Boolean);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if Assigned(ParentControl) then
|
|
for I := 0 to ParentControl.ControlCount - 1 do
|
|
if ParentControl.Controls[I] is TJvCustomDiagramShape then
|
|
TJvCustomDiagramShape(ParentControl.Controls[I]).MultiSelect := Value;
|
|
end;
|
|
|
|
//=== { TJvMoveableShape } ===================================================
|
|
|
|
constructor TJvMoveableShape.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
Selected := False;
|
|
Moving := False;
|
|
FOrigin := Point(0, 0);
|
|
end;
|
|
|
|
procedure TJvMoveableShape.StartMove(X, Y: Integer);
|
|
begin
|
|
Selected := True;
|
|
Moving := True;
|
|
FOrigin := Point(X, Y);
|
|
end;
|
|
|
|
procedure TJvMoveableShape.Move(DeltaX, DeltaY: Integer);
|
|
begin
|
|
SetBounds(Left + DeltaX, Top + DeltaY, Width, Height);
|
|
end;
|
|
|
|
procedure TJvMoveableShape.EndMove;
|
|
begin
|
|
Moving := False;
|
|
FOrigin := Point(0, 0);
|
|
end;
|
|
|
|
function TJvMoveableShape.ValidMove(DeltaX, DeltaY: Integer): Boolean;
|
|
begin
|
|
Result := True;
|
|
if not Assigned(Parent) then
|
|
Exit;
|
|
|
|
if Selected then
|
|
Result := (Left + DeltaX >= 0) and (Top + DeltaY >= 0) and
|
|
(Left + DeltaX + Width - 1 < Parent.ClientRect.Right - Parent.ClientRect.Left) and
|
|
(Top + DeltaY + Height - 1 < Parent.ClientRect.Bottom - Parent.ClientRect.Top);
|
|
end;
|
|
|
|
procedure TJvMoveableShape.MoveShapes(DeltaX, DeltaY: Integer);
|
|
var
|
|
I, Pass: Integer;
|
|
TempControl: TControl;
|
|
begin
|
|
if not Assigned(Parent) then
|
|
Exit;
|
|
|
|
// Do 2 passes through controls. The first one is to check that all
|
|
// movements are valid
|
|
for Pass := 1 to 2 do
|
|
begin
|
|
for I := 0 to Parent.ControlCount - 1 do
|
|
begin
|
|
TempControl := Parent.Controls[I];
|
|
if TempControl is TJvMoveableShape then
|
|
begin
|
|
if (Pass = 1) and
|
|
(not TJvMoveableShape(TempControl).ValidMove(DeltaX, DeltaY)) then
|
|
Exit
|
|
else
|
|
if (Pass = 2) and TJvMoveableShape(TempControl).Selected then
|
|
TJvMoveableShape(TempControl).Move(DeltaX, DeltaY);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvMoveableShape.MouseDown(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
begin
|
|
inherited MouseDown(Button, Shift, X, Y);
|
|
// Only respond to left mouse button events
|
|
if Button <> mbLeft then
|
|
Exit;
|
|
// If not holding down the shift key then not doing multiple selection
|
|
if not (ssShift in Shift) then
|
|
UnselectAllShapes(Parent);
|
|
// Start moving the component
|
|
StartMove(X, Y);
|
|
end;
|
|
|
|
procedure TJvMoveableShape.MouseMove(Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
inherited MouseMove(Shift, X, Y);
|
|
// Only need to move the component if the left mouse button is being held down
|
|
if not (ssLeft in Shift) then
|
|
begin
|
|
Moving := False;
|
|
Exit;
|
|
end;
|
|
|
|
if Moving then
|
|
// Move all the selected shapes
|
|
MoveShapes(X - FOrigin.X, Y - FOrigin.Y);
|
|
end;
|
|
|
|
procedure TJvMoveableShape.MouseUp(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
var
|
|
I: Integer;
|
|
TempControl: TControl;
|
|
begin
|
|
inherited MouseUp(Button, Shift, X, Y);
|
|
// Only interested in left mouse button events
|
|
if Button <> mbLeft then
|
|
Exit;
|
|
|
|
EndMove;
|
|
|
|
// If this shape is covering any smaller shapes then send it to the back,
|
|
// so that we can get at the smaller ones
|
|
if not Assigned(Parent) then
|
|
Exit;
|
|
for I := 0 to Parent.ControlCount - 1 do
|
|
begin
|
|
TempControl := Parent.Controls[I];
|
|
if (TempControl <> Self) and
|
|
(TempControl is TJvCustomDiagramShape) and
|
|
TJvCustomDiagramShape(TempControl).CanProcessMouseMsg and
|
|
InRect(TempControl.Left, TempControl.Top, BoundsRect) and
|
|
InRect(TempControl.Left + TempControl.Width,
|
|
TempControl.Top + TempControl.Height, BoundsRect) then
|
|
begin
|
|
// TempControl is not this one, it is a custom shape, that can process
|
|
// mouse messages (eg not a connector), and is completely covered by
|
|
// this control. So bring the convered control to the top of the z-order
|
|
// so that we can access it.
|
|
TempControl.BringToFront;
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvSizeableShape } ===================================================
|
|
|
|
constructor TJvSizeableShape.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FSizingMode := smNone;
|
|
FSizeOrigin := Point(0, 0);
|
|
FSizeRectHeight := 5;
|
|
FSizeRectWidth := 5;
|
|
FMinHeight := FSizeRectHeight;
|
|
FMinWidth := FSizeRectWidth;
|
|
end;
|
|
|
|
procedure TJvSizeableShape.SetSelected(Value: Boolean);
|
|
begin
|
|
if Value <> FSelected then
|
|
begin
|
|
inherited SetSelected(Value);
|
|
// Force redraw to show sizing rectangles
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvSizeableShape.Paint;
|
|
begin
|
|
inherited Paint;
|
|
if not Assigned(Parent) then
|
|
Exit;
|
|
DrawSizingRects;
|
|
end;
|
|
|
|
function TJvSizeableShape.GetSizeRect(SizeRectType: TJvSizingMode): TRect;
|
|
begin
|
|
case SizeRectType of
|
|
smTopLeft:
|
|
Result := Bounds(0, 0, SizeRectWidth, SizeRectHeight);
|
|
smTop:
|
|
Result := Bounds(((ClientRect.Right - ClientRect.Left) div 2) -
|
|
(SizeRectWidth div 2), 0, SizeRectWidth, SizeRectHeight);
|
|
smTopRight:
|
|
Result := Bounds(ClientRect.Right - SizeRectWidth, 0,
|
|
SizeRectWidth, SizeRectHeight);
|
|
smLeft:
|
|
Result := Bounds(0, ((ClientRect.Bottom - ClientRect.Top) div 2) -
|
|
(SizeRectHeight div 2), SizeRectWidth, SizeRectHeight);
|
|
smRight:
|
|
Result := Bounds(ClientRect.Right - SizeRectWidth,
|
|
((ClientRect.Bottom - ClientRect.Top) div 2) -
|
|
(SizeRectHeight div 2), SizeRectWidth, SizeRectHeight);
|
|
smBottomLeft:
|
|
Result := Bounds(0, ClientRect.Bottom - SizeRectHeight,
|
|
SizeRectWidth, SizeRectHeight);
|
|
smBottom:
|
|
Result := Bounds(((ClientRect.Right - ClientRect.Left) div 2) -
|
|
(SizeRectWidth div 2), ClientRect.Bottom - SizeRectHeight,
|
|
SizeRectWidth, SizeRectHeight);
|
|
smBottomRight:
|
|
Result := Bounds(ClientRect.Right - SizeRectWidth,
|
|
ClientRect.Bottom - SizeRectHeight, SizeRectWidth, SizeRectHeight);
|
|
smNone:
|
|
Result := Bounds(0, 0, 0, 0);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvSizeableShape.DrawSizingRects;
|
|
var
|
|
OldBrush: TBrush;
|
|
SMode: TJvSizingMode;
|
|
begin
|
|
if not FSelected or not CanProcessMouseMsg then
|
|
Exit;
|
|
with Canvas do
|
|
begin
|
|
// Draw the sizing rectangles
|
|
OldBrush := TBrush.Create;
|
|
try
|
|
OldBrush.Assign(Brush);
|
|
Brush.Style := bsSolid;
|
|
Brush.Color := clBlack;
|
|
Pen.Color := clBlack;
|
|
for SMode := smTopLeft to smBottomRight do
|
|
FillRect(GetSizeRect(SMode));
|
|
finally
|
|
Brush.Assign(OldBrush);
|
|
OldBrush.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvSizeableShape.CheckForSizeRects(X, Y: Integer);
|
|
const
|
|
cCursors: array [TJvSizingMode] of TCursor =
|
|
(crSizeNWSE, crSizeNS, crSizeNESW, crSizeWE, crSizeWE,
|
|
crSizeNESW, crSizeNS, crSizeNWSE, crDefault);
|
|
var
|
|
SMode: TJvSizingMode;
|
|
begin
|
|
FSizingMode := smNone;
|
|
if not Selected then
|
|
Exit;
|
|
|
|
for SMode := smTopLeft to smBottomRight do
|
|
if InRect(X, Y, GetSizeRect(SMode)) then
|
|
begin
|
|
SizingMode := SMode;
|
|
Break;
|
|
end;
|
|
Cursor := cCursors[SizingMode];
|
|
end;
|
|
|
|
procedure TJvSizeableShape.ResizeControl(X, Y: Integer);
|
|
var
|
|
L, T, W, H, DeltaX, DeltaY: Integer;
|
|
begin
|
|
L := Left;
|
|
T := Top;
|
|
W := Width;
|
|
H := Height;
|
|
DeltaX := X - FSizeOrigin.X;
|
|
DeltaY := Y - FSizeOrigin.Y;
|
|
// Calculate the new boundaries on the control. Also change FSizeOrigin to
|
|
// reflect change in boundaries if necessary.
|
|
case FSizingMode of
|
|
smTopLeft:
|
|
begin
|
|
// Ensure that don't move the left edge if this would make the
|
|
// control too narrow
|
|
if (L + DeltaX >= 0) and (W - DeltaX > MinWidth) then
|
|
begin
|
|
L := L + DeltaX;
|
|
W := W - DeltaX;
|
|
end;
|
|
// Ensure that don't move the top edge if this would make the
|
|
// control too short
|
|
if (T + DeltaY >= 0) and (H - DeltaY > MinHeight) then
|
|
begin
|
|
T := T + DeltaY;
|
|
H := H - DeltaY;
|
|
end;
|
|
end;
|
|
smTop:
|
|
begin
|
|
if (T + DeltaY >= 0) and (H - DeltaY > MinHeight) then
|
|
begin
|
|
T := T + DeltaY;
|
|
H := H - DeltaY;
|
|
end;
|
|
end;
|
|
smTopRight:
|
|
begin
|
|
W := W + DeltaX;
|
|
if (T + DeltaY >= 0) and (H - DeltaY > MinHeight) then
|
|
begin
|
|
T := T + DeltaY;
|
|
H := H - DeltaY;
|
|
end;
|
|
FSizeOrigin.X := X;
|
|
end;
|
|
smLeft:
|
|
begin
|
|
if (L + DeltaX >= 0) and (W - DeltaX > MinWidth) then
|
|
begin
|
|
L := L + DeltaX;
|
|
W := W - DeltaX;
|
|
end;
|
|
end;
|
|
smRight:
|
|
begin
|
|
W := W + DeltaX;
|
|
FSizeOrigin.X := X;
|
|
end;
|
|
smBottomLeft:
|
|
begin
|
|
if (L + DeltaX >= 0) and (W - DeltaX > MinWidth) then
|
|
begin
|
|
L := L + DeltaX;
|
|
W := W - DeltaX;
|
|
end;
|
|
H := H + DeltaY;
|
|
FSizeOrigin.Y := Y;
|
|
end;
|
|
smBottom:
|
|
begin
|
|
H := H + DeltaY;
|
|
FSizeOrigin.X := X;
|
|
FSizeOrigin.Y := Y;
|
|
end;
|
|
smBottomRight:
|
|
begin
|
|
W := W + DeltaX;
|
|
H := H + DeltaY;
|
|
FSizeOrigin.X := X;
|
|
FSizeOrigin.Y := Y;
|
|
end;
|
|
smNone: ;
|
|
end;
|
|
SetBounds(L, T, W, H);
|
|
end;
|
|
|
|
procedure TJvSizeableShape.MouseDown(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
begin
|
|
if (FSizingMode = smNone) or (Button <> mbLeft) or (ssShift in Shift) then
|
|
begin
|
|
// Do moving instead of sizing
|
|
FSizingMode := smNone;
|
|
inherited MouseDown(Button, Shift, X, Y);
|
|
Exit;
|
|
end;
|
|
|
|
// If sizing then make this the only selected control
|
|
UnselectAllShapes(Parent);
|
|
BringToFront;
|
|
{ TODO : check on all Shapes selected }
|
|
// FSelected := True;
|
|
FSizeOrigin := Point(X, Y);
|
|
end;
|
|
|
|
procedure TJvSizeableShape.MouseMove(Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
if Moving then
|
|
inherited MouseMove(Shift, X, Y)
|
|
else
|
|
if (FSizingMode <> smNone) and (ssLeft in Shift) then
|
|
ResizeControl(X, Y)
|
|
else
|
|
// Check if over a sizing rectangle
|
|
CheckForSizeRects(X, Y);
|
|
end;
|
|
|
|
procedure TJvSizeableShape.MouseUp(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
begin
|
|
if Button = mbLeft then
|
|
FSizingMode := smNone;
|
|
inherited MouseUp(Button, Shift, X, Y);
|
|
end;
|
|
|
|
procedure TJvSizeableShape.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
|
|
begin
|
|
// Check that the control bounds are sensible. The control must be at least
|
|
// as large as a sizing rectangle
|
|
NoLessThan(ALeft, 0);
|
|
NoLessThan(ATop, 0);
|
|
NoLessThan(AWidth, FMinWidth);
|
|
NoLessThan(AHeight, FMinHeight);
|
|
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
|
|
end;
|
|
|
|
//=== { TJvTextShape } =======================================================
|
|
|
|
constructor TJvTextShape.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FAutoSize := True;
|
|
FText := '';
|
|
FFont := TFont.Create;
|
|
FFont.OnChange := FontChange;
|
|
end;
|
|
|
|
destructor TJvTextShape.Destroy;
|
|
begin
|
|
FreeAndNil(FFont);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvTextShape.RefreshText;
|
|
var
|
|
I, Count: Integer;
|
|
TempStr: string;
|
|
begin
|
|
FMinHeight := FSizeRectHeight;
|
|
FMinWidth := FSizeRectWidth;
|
|
TempStr := '';
|
|
Count := 1;
|
|
if AutoSize and Assigned(Parent) then
|
|
begin
|
|
Canvas.Font := Font;
|
|
for I := 1 to Length(FText) do
|
|
begin
|
|
if FText[I] = Lf then
|
|
begin
|
|
// Check the width of this line
|
|
FMinWidth := Max([FMinWidth, Canvas.TextWidth(TempStr)]);
|
|
TempStr := '';
|
|
// Count the line feeds
|
|
Inc(Count);
|
|
end
|
|
else
|
|
TempStr := TempStr + FText[I];
|
|
end;
|
|
if Count = 1 then
|
|
// In case there is only one line
|
|
FMinWidth := Max([FMinWidth, Canvas.TextWidth(FText)]);
|
|
// Calculate the height of the text rectangle
|
|
FMinHeight := Max([FMinHeight, Canvas.TextHeight(FText) * Count]);
|
|
end;
|
|
SetBounds(Left, Top, FMinWidth, FMinHeight);
|
|
end;
|
|
|
|
{$IFDEF VisualCLX}
|
|
function TJvTextShape.GetText: TCaption;
|
|
begin
|
|
Result := FText;
|
|
end;
|
|
{$ENDIF VisualCLX}
|
|
|
|
procedure TJvTextShape.SetText(const Value: TCaption);
|
|
begin
|
|
if FText <> Value then
|
|
begin
|
|
FText := Value;
|
|
RefreshText;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvTextShape.SetAutoSize(Value: Boolean);
|
|
begin
|
|
if FAutoSize <> Value then
|
|
begin
|
|
FAutoSize := Value;
|
|
RefreshText;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvTextShape.SetFont(Value: TFont);
|
|
begin
|
|
FFont.Assign(Value);
|
|
end;
|
|
|
|
procedure TJvTextShape.FontChange(Sender: TObject);
|
|
begin
|
|
RefreshText;
|
|
end;
|
|
|
|
{$IFDEF VisualCLX}
|
|
procedure TJvTextShape.SetParent(const AParent: TWidgetControl);
|
|
{$ENDIF VisualCLX}
|
|
{$IFDEF VCL}
|
|
procedure TJvTextShape.SetParent(AParent: TWinControl);
|
|
{$ENDIF VCL}
|
|
begin
|
|
inherited SetParent(AParent);
|
|
RefreshText;
|
|
end;
|
|
|
|
procedure TJvTextShape.Paint;
|
|
var
|
|
TempRect: TRect;
|
|
begin
|
|
if not Assigned(Parent) then
|
|
Exit;
|
|
Canvas.Font := Font;
|
|
TempRect := ClientRect; // So can pass as a var parameter
|
|
{$IFDEF VCL}
|
|
DrawText(Canvas.Handle, PCaptionChar(FText), Length(FText), TempRect,
|
|
DT_CENTER or DT_NOPREFIX or DT_WORDBREAK);
|
|
{$ENDIF VCL}
|
|
{$IFDEF VisualCLX}
|
|
DrawText(Canvas, FText, Length(FText), TempRect,
|
|
DT_CENTER or DT_NOPREFIX or DT_WORDBREAK);
|
|
{$ENDIF VisualCLX}
|
|
inherited Paint;
|
|
end;
|
|
|
|
procedure TJvTextShape.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
|
|
begin
|
|
// Check that the control bounds are sensible. Note that this also works
|
|
// if try to set Left, Top etc properties, as their access methods call
|
|
// SetBounds().
|
|
NoLessThan(AWidth, FMinWidth);
|
|
NoLessThan(AHeight, FMinHeight);
|
|
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
|
|
end;
|
|
|
|
//=== { TJvBitmapShape } =====================================================
|
|
|
|
constructor TJvBitmapShape.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FImages := nil;
|
|
FImageIndex := 0;
|
|
end;
|
|
|
|
procedure TJvBitmapShape.SetSelected(Value: Boolean);
|
|
begin
|
|
if Value <> FSelected then
|
|
begin
|
|
inherited SetSelected(Value);
|
|
// Force redraw to show focus rectangle
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBitmapShape.SetImages(Value: TImageList);
|
|
begin
|
|
if Value <> FImages then
|
|
begin
|
|
FImages := Value;
|
|
if FImages <> nil then
|
|
// Set the size of the component to the image size
|
|
SetBounds(Left, Top, FImages.Width, FImages.Height);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBitmapShape.SetImageIndex(Value: Integer);
|
|
begin
|
|
if Value <> FImageIndex then
|
|
begin
|
|
FImageIndex := Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvBitmapShape.Paint;
|
|
var
|
|
OldPen: TPen;
|
|
begin
|
|
inherited Paint;
|
|
if (not Assigned(Parent)) or (not Assigned(FImages)) or
|
|
(FImageIndex < 0) or (FImageIndex >= FImages.Count) then
|
|
// The component has not been placed on a form yet, or does not have an
|
|
// associated image
|
|
Exit;
|
|
|
|
// Draw a focus rectangle
|
|
OldPen := Canvas.Pen;
|
|
Canvas.Pen.Style := psDot;
|
|
Canvas.Brush.Style := bsClear;
|
|
|
|
if Selected then
|
|
Canvas.Pen.Mode := pmNot
|
|
else
|
|
Canvas.Pen.Mode := pmNop;
|
|
|
|
// (rom) draws a rectangle
|
|
Canvas.Polyline([Point(0, 0), Point(Width - 1, 0),
|
|
Point(Width - 1, Height - 1), Point(0, Height - 1), Point(0, 0)]);
|
|
Canvas.Pen := OldPen;
|
|
|
|
// Draw the bitmap
|
|
{$IFDEF VCL}
|
|
FImages.DrawingStyle := dsTransparent;
|
|
{$ENDIF VCL}
|
|
FImages.Draw(Canvas, 0, 0, FImageIndex);
|
|
end;
|
|
|
|
procedure TJvBitmapShape.Notification(AComponent: TComponent; Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if Operation = opRemove then
|
|
if AComponent = FImages then
|
|
FImages := nil;
|
|
end;
|
|
|
|
//=== { TJvStandardShape } ===================================================
|
|
|
|
constructor TJvStandardShape.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
// Set a default shape and size and colors
|
|
FShapeType := stRectangle;
|
|
Width := 100;
|
|
Height := 60;
|
|
FLineColor := clBlack;
|
|
end;
|
|
|
|
procedure TJvStandardShape.SetShapeType(Value: TShapeType);
|
|
begin
|
|
if FShapeType <> Value then
|
|
begin
|
|
FShapeType := Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvStandardShape.Paint;
|
|
var
|
|
TempRect: TRect;
|
|
S: Integer;
|
|
begin
|
|
inherited Paint;
|
|
if not Assigned(Parent) then
|
|
Exit;
|
|
|
|
TempRect := ClientRect; // So can pass as a var parameter
|
|
InflateRect(TempRect, -SizeRectWidth, -SizeRectHeight);
|
|
|
|
// Draw shape outline
|
|
Canvas.Brush.Style := bsClear;
|
|
Canvas.Pen.Color := FLineColor;
|
|
S := Min([TempRect.Right - TempRect.Left + 1, TempRect.Bottom - TempRect.Top + 1]);
|
|
|
|
if FShapeType in [stSquare, stRoundSquare, stCircle] then
|
|
begin
|
|
TempRect.Right := TempRect.Left + S;
|
|
TempRect.Bottom := TempRect.Top + S;
|
|
end;
|
|
|
|
case FShapeType of
|
|
stRectangle, stSquare:
|
|
Canvas.Rectangle(TempRect.Left, TempRect.Top, TempRect.Right, TempRect.Bottom);
|
|
stRoundRect, stRoundSquare:
|
|
Canvas.RoundRect(TempRect.Left, TempRect.Top, TempRect.Right, TempRect.Bottom,
|
|
S div 4, S div 4);
|
|
stCircle, stEllipse:
|
|
Canvas.Ellipse(TempRect.Left, TempRect.Top, TempRect.Right, TempRect.Bottom);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvConnection } ======================================================
|
|
|
|
constructor TJvConnection.Create;
|
|
begin
|
|
inherited Create;
|
|
FShape := nil;
|
|
FSide := csRight;
|
|
FOffset := 0;
|
|
end;
|
|
|
|
procedure TJvConnection.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TJvConnection then
|
|
begin
|
|
FShape := TJvConnection(Source).FShape;
|
|
FSide := TJvConnection(Source).FSide;
|
|
FOffset := TJvConnection(Source).FOffset;
|
|
end
|
|
else
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
function TJvConnection.ConnPoint(TerminatorRect: TRect): TPoint;
|
|
var
|
|
X, Y, W: Integer;
|
|
begin
|
|
Result := Point(0, 0);
|
|
X := 0;
|
|
Y := 0;
|
|
W := TerminatorRect.Right - TerminatorRect.Left;
|
|
|
|
if FShape = nil then
|
|
Exit;
|
|
|
|
case FSide of
|
|
csLeft:
|
|
begin
|
|
X := FShape.Left - W;
|
|
Y := FShape.Top + FOffset;
|
|
end;
|
|
csRight:
|
|
begin
|
|
X := FShape.Left + FShape.Width - 1 + W;
|
|
Y := FShape.Top + FOffset;
|
|
end;
|
|
csTop:
|
|
begin
|
|
X := FShape.Left + FOffset;
|
|
Y := FShape.Top - W;
|
|
end;
|
|
csBottom:
|
|
begin
|
|
X := FShape.Left + FOffset;
|
|
Y := FShape.Top + FShape.Height - 1 + W;
|
|
end;
|
|
end;
|
|
Result := Point(X, Y);
|
|
end;
|
|
|
|
function TJvConnection.TermPoint(TerminatorRect: TRect): TPoint;
|
|
begin
|
|
Result.X := 0;
|
|
Result.Y := 0;
|
|
if Shape = nil then
|
|
Exit;
|
|
with Result do
|
|
case Side of
|
|
csLeft:
|
|
begin
|
|
X := Shape.Left;
|
|
Y := Shape.Top + Offset;
|
|
end;
|
|
csRight:
|
|
begin
|
|
X := Shape.Left + Shape.Width - 1;
|
|
Y := Shape.Top + Offset;
|
|
end;
|
|
csTop:
|
|
begin
|
|
X := Shape.Left + Offset;
|
|
Y := Shape.Top;
|
|
end;
|
|
csBottom:
|
|
begin
|
|
X := Shape.Left + Offset;
|
|
Y := Shape.Top + Shape.Height - 1;
|
|
end;
|
|
else
|
|
X := 0;
|
|
Y := 0;
|
|
end;
|
|
end;
|
|
|
|
function TJvConnection.LeftMost(TerminatorRect: TRect): TPoint;
|
|
begin
|
|
Result := TermPoint(TerminatorRect);
|
|
if Shape = nil then
|
|
Exit;
|
|
case Side of
|
|
csLeft:
|
|
Result.X := Shape.Left - RectWidth(TerminatorRect);
|
|
csRight:
|
|
Result.X := Shape.Left + Shape.Width;
|
|
csTop, csBottom:
|
|
Result.X := Shape.Left + Offset - (RectHeight(TerminatorRect) div 2);
|
|
end;
|
|
end;
|
|
|
|
function TJvConnection.RightMost(TerminatorRect: TRect): TPoint;
|
|
begin
|
|
Result := TermPoint(TerminatorRect);
|
|
if Shape = nil then
|
|
Exit;
|
|
case Side of
|
|
csLeft:
|
|
Result.X := Shape.Left - 1;
|
|
csRight:
|
|
Result.X := Shape.Left + Shape.Width - 1 + RectWidth(TerminatorRect);
|
|
csTop, csBottom:
|
|
Result.X := Shape.Left + Offset + (RectHeight(TerminatorRect) div 2);
|
|
end;
|
|
end;
|
|
|
|
function TJvConnection.TopMost(TerminatorRect: TRect): TPoint;
|
|
begin
|
|
Result := TermPoint(TerminatorRect);
|
|
if Shape = nil then
|
|
Exit;
|
|
case Side of
|
|
csLeft, csRight:
|
|
Result.Y := Shape.Top + Offset - (RectHeight(TerminatorRect) div 2);
|
|
csTop:
|
|
Result.Y := Shape.Top - RectWidth(TerminatorRect) - 1;
|
|
csBottom:
|
|
Result.Y := Shape.Top + Shape.Height;
|
|
end;
|
|
end;
|
|
|
|
function TJvConnection.BottomMost(TerminatorRect: TRect): TPoint;
|
|
begin
|
|
Result := TermPoint(TerminatorRect);
|
|
if Shape = nil then
|
|
Exit;
|
|
case Side of
|
|
csLeft, csRight:
|
|
Result.Y := Shape.Top + Offset + (RectHeight(TerminatorRect) div 2);
|
|
csTop:
|
|
Result.Y := Shape.Top - 1;
|
|
csBottom:
|
|
Result.Y := Shape.Top + Shape.Height + RectWidth(TerminatorRect);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvConnector } =======================================================
|
|
|
|
constructor TJvConnector.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FCanProcessMouseMsg := False;
|
|
FLineWidth := 1;
|
|
FLineColor := clBlack;
|
|
FStartTermRect := Rect(0, 0, 0, 0);
|
|
FEndTermRect := Rect(0, 0, 0, 0);
|
|
FStartConn := TJvConnection.Create;
|
|
FEndConn := TJvConnection.Create;
|
|
FMidPoint := Point(0, 0);
|
|
end;
|
|
|
|
destructor TJvConnector.Destroy;
|
|
begin
|
|
FreeAndNil(FStartConn);
|
|
FreeAndNil(FEndConn);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvConnector.Paint;
|
|
var
|
|
EndPt: TPoint;
|
|
begin
|
|
inherited Paint;
|
|
if not Assigned(Parent) then
|
|
Exit;
|
|
if Assigned(FStartConn.Shape) and Assigned(FEndConn.Shape) then
|
|
begin
|
|
// Draw the terminators (arrows etc)
|
|
DrawStartTerminator;
|
|
DrawEndTerminator;
|
|
with Canvas do
|
|
begin
|
|
// Draw the connecting line
|
|
Brush.Style := bsClear;
|
|
Pen.Width := FLineWidth;
|
|
Pen.Color := FLineColor;
|
|
// Convert from Parent coordinates to control coordinates
|
|
PenPos := Convert(FStartConn.ConnPoint(FStartTermRect));
|
|
EndPt := Convert(FEndConn.ConnPoint(FEndTermRect));
|
|
LineTo(EndPt.X, EndPt.Y);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvConnector.Notification(AComponent: TComponent; Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if Operation = opRemove then
|
|
begin
|
|
// (rom) added Assigned to fix a crash
|
|
if Assigned(FStartConn) and (AComponent = FStartConn.FShape) then
|
|
FStartConn.FShape := nil;
|
|
if Assigned(FEndConn) and (AComponent = FEndConn.FShape) then
|
|
FEndConn.FShape := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvConnector.DrawStartTerminator;
|
|
begin
|
|
end;
|
|
|
|
procedure TJvConnector.DrawEndTerminator;
|
|
begin
|
|
end;
|
|
|
|
procedure TJvConnector.MoveCaption;
|
|
var
|
|
NewMidPoint: TPoint;
|
|
ALeft, ATop, ARight, ABottom: Integer;
|
|
begin
|
|
if Assigned(FCaption) then
|
|
begin
|
|
if (FMidPoint.X = 0) and (FMidPoint.Y = 0) then
|
|
FMidPoint := GetMidPoint;
|
|
NewMidPoint := GetMidPoint;
|
|
// Move the caption relative to the mid point of the connector
|
|
// Not resizing anything, just moving an unconnected shape, so can use
|
|
// faster update method than SetBounds
|
|
FCaption.Invalidate;
|
|
ALeft := FCaption.Left + NewMidPoint.X - FMidPoint.X;
|
|
ATop := FCaption.Top + NewMidPoint.Y - FMidPoint.Y;
|
|
ARight := ALeft + FCaption.Width;
|
|
ABottom := ATop + FCaption.Height;
|
|
FCaption.UpdateBoundsRect(Rect(ALeft, ATop, ARight, ABottom));
|
|
// Save the new mid point
|
|
FMidPoint := NewMidPoint;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvConnector.CheckSize(var AWidth, AHeight: Integer);
|
|
begin
|
|
// Ensure the control is at least as big as the line width
|
|
NoLessThan(AHeight, FLineWidth);
|
|
NoLessThan(AWidth, FLineWidth);
|
|
// Ensure the control is at least as big as the start terminator rectangle
|
|
NoLessThan(AHeight, RectHeight(FStartTermRect));
|
|
NoLessThan(AWidth, RectWidth(FStartTermRect));
|
|
// Ensure the control is at least as big as the end terminator rectangle
|
|
NoLessThan(AHeight, RectHeight(FEndTermRect));
|
|
NoLessThan(AWidth, RectWidth(FEndTermRect));
|
|
end;
|
|
|
|
procedure TJvConnector.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
|
|
begin
|
|
CheckSize(AWidth, AHeight);
|
|
// Resize the connector
|
|
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
|
|
// Move the caption
|
|
MoveCaption;
|
|
end;
|
|
|
|
procedure TJvConnector.SetBoundingRect;
|
|
var
|
|
ALeft, ATop, AWidth, AHeight: Integer;
|
|
begin
|
|
if (FStartConn.Shape = nil) or (FEndConn.Shape = nil) then
|
|
Exit;
|
|
ALeft := Min([FStartConn.LeftMost(FStartTermRect).X,
|
|
FEndConn.LeftMost(FEndTermRect).X]);
|
|
ATop := Min([FStartConn.TopMost(FStartTermRect).Y,
|
|
FEndConn.TopMost(FEndTermRect).Y]);
|
|
AWidth := Max([FStartConn.RightMost(FStartTermRect).X,
|
|
FEndConn.RightMost(FEndTermRect).X]) - ALeft + 2;
|
|
AHeight := Max([FStartConn.BottomMost(FStartTermRect).Y,
|
|
FEndConn.BottomMost(FEndTermRect).Y]) - ATop + 2;
|
|
CheckSize(AWidth, AHeight);
|
|
Invalidate;
|
|
UpdateBoundsRect(Rect(ALeft, ATop, ALeft + AWidth - 1, ATop + AHeight - 1));
|
|
MoveCaption;
|
|
end;
|
|
|
|
procedure TJvConnector.SetLineWidth(Value: Integer);
|
|
begin
|
|
// Ensure that can always see the line!
|
|
if Value >= 1 then
|
|
FLineWidth := Value;
|
|
end;
|
|
|
|
function TJvConnector.GetConn(Index: Integer): TJvConnection;
|
|
begin
|
|
case Index of
|
|
1:
|
|
Result := FStartConn;
|
|
2:
|
|
Result := FEndConn;
|
|
else
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvConnector.SetConn(Index: Integer; Value: TJvConnection);
|
|
begin
|
|
case Index of
|
|
1:
|
|
FStartConn.Assign(Value);
|
|
2:
|
|
FEndConn.Assign(Value);
|
|
end;
|
|
SetBoundingRect;
|
|
end;
|
|
|
|
procedure TJvConnector.SetConnections(TheStartConn, TheEndConn: TJvConnection);
|
|
begin
|
|
StartConn := TheStartConn;
|
|
EndConn := TheEndConn;
|
|
end;
|
|
|
|
function TJvConnector.GetTermRect(Index: Integer): TRect;
|
|
begin
|
|
case Index of
|
|
1:
|
|
Result := FStartTermRect;
|
|
2:
|
|
Result := FEndTermRect;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvConnector.SetTermRect(Index: Integer; Value: TRect);
|
|
begin
|
|
if (Value.Right - Value.Left >= 0) and (Value.Bottom - Value.Top >= 0) then
|
|
begin
|
|
case Index of
|
|
1:
|
|
FStartTermRect := Value;
|
|
2:
|
|
FEndTermRect := Value;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvConnector.SetCaption(Value: TJvTextShape);
|
|
begin
|
|
inherited SetCaption(Value);
|
|
MoveCaption;
|
|
end;
|
|
|
|
function TJvConnector.Convert(APoint: TPoint): TPoint;
|
|
begin
|
|
Result := ScreenToClient(Parent.ClientToScreen(APoint));
|
|
end;
|
|
|
|
function TJvConnector.IsConnected(ConnectedShape: TJvCustomDiagramShape): Boolean;
|
|
begin
|
|
Result := (FStartConn <> nil) and (FEndConn <> nil) and (ConnectedShape <> nil) and
|
|
((FStartConn.Shape = ConnectedShape) or (FEndConn.Shape = ConnectedShape));
|
|
end;
|
|
|
|
function TJvConnector.GetMidPoint: TPoint;
|
|
var
|
|
A, B: TPoint;
|
|
begin
|
|
Result := Point(0, 0);
|
|
if (not Assigned(FStartConn)) or (not Assigned(FEndConn)) then
|
|
Exit;
|
|
A := FStartConn.ConnPoint(FStartTermRect);
|
|
B := FEndConn.ConnPoint(FEndTermRect);
|
|
Result := Point(Min([A.X, B.X]) + Abs(A.X - B.X) div 2,
|
|
Min([A.Y, B.Y]) + Abs(A.Y - B.Y) div 2);
|
|
end;
|
|
|
|
//=== { TJvSingleHeadArrow } =================================================
|
|
|
|
constructor TJvSingleHeadArrow.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
EndTermRect := Rect(0, 0, 25, 10);
|
|
end;
|
|
|
|
procedure TJvSingleHeadArrow.DrawArrowHead(ConnPt, TermPt: TPoint);
|
|
var
|
|
PointPt, Corner1Pt, Corner2Pt: TPoint;
|
|
begin
|
|
with Canvas do
|
|
begin
|
|
Brush.Style := bsSolid;
|
|
Brush.Color := FLineColor;
|
|
Pen.Color := FLineColor;
|
|
|
|
// Draw a line connecting the Conn and Term points
|
|
PenPos := ConnPt;
|
|
LineTo(TermPt.X, TermPt.Y);
|
|
// Set the basic points (to be modified depending on arrow head direction
|
|
PointPt := TermPt;
|
|
Corner1Pt := ConnPt;
|
|
Corner2Pt := ConnPt;
|
|
|
|
if ConnPt.X < TermPt.X then
|
|
begin
|
|
// Draw a right pointing arrow head
|
|
Inc(Corner1Pt.X, 10);
|
|
Inc(Corner2Pt.X, 10);
|
|
Dec(Corner1Pt.Y, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.Y, RectHeight(EndTermRect) div 2);
|
|
end
|
|
else
|
|
if ConnPt.X > TermPt.X then
|
|
begin
|
|
// Draw a left pointing arrow head
|
|
Dec(Corner1Pt.X, 10);
|
|
Dec(Corner2Pt.X, 10);
|
|
Dec(Corner1Pt.Y, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.Y, RectHeight(EndTermRect) div 2);
|
|
end
|
|
else
|
|
if ConnPt.Y < TermPt.Y then
|
|
begin
|
|
// Draw a down pointing arrow head
|
|
Inc(Corner1Pt.Y, 10);
|
|
Inc(Corner2Pt.Y, 10);
|
|
Dec(Corner1Pt.X, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.X, RectHeight(EndTermRect) div 2);
|
|
end
|
|
else
|
|
begin
|
|
// Draw a up pointing arrow head
|
|
Dec(Corner1Pt.Y, 10);
|
|
Dec(Corner2Pt.Y, 10);
|
|
Dec(Corner1Pt.X, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.X, RectHeight(EndTermRect) div 2);
|
|
end;
|
|
Polygon([PointPt, Corner1Pt, Corner2Pt]);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvSingleHeadArrow.DrawEndTerminator;
|
|
var
|
|
ConnPt, TermPt: TPoint;
|
|
begin
|
|
inherited DrawEndTerminator;
|
|
if Assigned(FEndConn.Shape) then
|
|
begin
|
|
ConnPt := Convert(FEndConn.ConnPoint(EndTermRect));
|
|
TermPt := Convert(FEndConn.TermPoint(EndTermRect));
|
|
DrawArrowHead(ConnPt, TermPt);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvSingleHeadOpenDashArrow } =========================================
|
|
|
|
constructor TJvSingleHeadOpenDashArrow.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
EndTermRect := Rect(0, 0, 25, 10);
|
|
end;
|
|
|
|
procedure TJvSingleHeadOpenDashArrow.Paint;
|
|
begin
|
|
Canvas.Pen.Style := psDash;
|
|
inherited Paint;
|
|
Canvas.Pen.Style := psSolid;
|
|
end;
|
|
|
|
procedure TJvSingleHeadOpenDashArrow.DrawArrowHead(ConnPt, TermPt: TPoint);
|
|
var
|
|
PointPt, Corner1Pt, Corner2Pt: TPoint;
|
|
begin
|
|
with Canvas do
|
|
begin
|
|
Brush.Style := bsClear;
|
|
Brush.Color := clWindow;
|
|
Pen.Color := FLineColor;
|
|
|
|
// Draw a line connecting the Conn and Term points
|
|
PenPos := ConnPt;
|
|
LineTo(TermPt.X, TermPt.Y);
|
|
// Set the basic points (to be modified depending on arrow head direction
|
|
PointPt := TermPt;
|
|
Corner1Pt := ConnPt;
|
|
Corner2Pt := ConnPt;
|
|
|
|
if ConnPt.X < TermPt.X then
|
|
begin
|
|
// Draw a right pointing arrow head
|
|
Inc(Corner1Pt.X, 10);
|
|
Inc(Corner2Pt.X, 10);
|
|
Dec(Corner1Pt.Y, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.Y, RectHeight(EndTermRect) div 2);
|
|
end
|
|
else
|
|
if ConnPt.X > TermPt.X then
|
|
begin
|
|
// Draw a left pointing arrow head
|
|
Dec(Corner1Pt.X, 10);
|
|
Dec(Corner2Pt.X, 10);
|
|
Dec(Corner1Pt.Y, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.Y, RectHeight(EndTermRect) div 2);
|
|
end
|
|
else
|
|
if ConnPt.Y < TermPt.Y then
|
|
begin
|
|
// Draw a down pointing arrow head
|
|
Inc(Corner1Pt.Y, 10);
|
|
Inc(Corner2Pt.Y, 10);
|
|
Dec(Corner1Pt.X, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.X, RectHeight(EndTermRect) div 2);
|
|
end
|
|
else
|
|
begin
|
|
// Draw a up pointing arrow head
|
|
Dec(Corner1Pt.Y, 10);
|
|
Dec(Corner2Pt.Y, 10);
|
|
Dec(Corner1Pt.X, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.X, RectHeight(EndTermRect) div 2);
|
|
end;
|
|
// Polyline([Corner1Pt,PointPt,Corner2Pt]);
|
|
MoveTo(PointPt.X, PointPt.Y);
|
|
LineTo(Corner1Pt.X, Corner1Pt.Y);
|
|
MoveTo(PointPt.X, PointPt.Y);
|
|
LineTo(Corner2Pt.X, Corner2Pt.Y);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvSingleHeadOpenDashArrow.DrawEndTerminator;
|
|
var
|
|
ConnPt, TermPt: TPoint;
|
|
begin
|
|
inherited DrawEndTerminator;
|
|
if Assigned(FEndConn.Shape) then
|
|
begin
|
|
ConnPt := Convert(FEndConn.ConnPoint(EndTermRect));
|
|
TermPt := Convert(FEndConn.TermPoint(EndTermRect));
|
|
DrawArrowHead(ConnPt, TermPt);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvBluntSingleHeadOpenDashArrow } ====================================
|
|
|
|
constructor TJvBluntSingleHeadOpenDashArrow.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
StartTermRect := Rect(0, 0, 10, 10);
|
|
end;
|
|
|
|
procedure TJvBluntSingleHeadOpenDashArrow.DrawStartTerminator;
|
|
var
|
|
ConnPt, TermPt: TPoint;
|
|
begin
|
|
inherited DrawStartTerminator;
|
|
if not Assigned(FStartConn.Shape) then
|
|
Exit;
|
|
ConnPt := Convert(FStartConn.ConnPoint(StartTermRect));
|
|
TermPt := Convert(FStartConn.TermPoint(StartTermRect));
|
|
with Canvas do
|
|
begin
|
|
// Draw a line connecting the Conn and Term points
|
|
Pen.Color := FLineColor;
|
|
PenPos := ConnPt;
|
|
LineTo(TermPt.X, TermPt.Y);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvBluntSingleHeadArrow } ============================================
|
|
|
|
constructor TJvBluntSingleHeadArrow.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
StartTermRect := Rect(0, 0, 10, 10);
|
|
end;
|
|
|
|
procedure TJvBluntSingleHeadArrow.DrawStartTerminator;
|
|
var
|
|
ConnPt, TermPt: TPoint;
|
|
begin
|
|
inherited DrawStartTerminator;
|
|
if not Assigned(FStartConn.Shape) then
|
|
Exit;
|
|
ConnPt := Convert(FStartConn.ConnPoint(StartTermRect));
|
|
TermPt := Convert(FStartConn.TermPoint(StartTermRect));
|
|
with Canvas do
|
|
begin
|
|
// Draw a line connecting the Conn and Term points
|
|
Pen.Color := FLineColor;
|
|
PenPos := ConnPt;
|
|
LineTo(TermPt.X, TermPt.Y);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvSubCaseArrow } ====================================================
|
|
|
|
constructor TJvSubCaseArrow.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
EndTermRect := Rect(0, 0, 25, 10);
|
|
StartTermRect := Rect(0, 0, 10, 10);
|
|
end;
|
|
|
|
procedure TJvSubCaseArrow.DrawArrowHead(ConnPt, TermPt: TPoint);
|
|
var
|
|
PointPt, Corner1Pt, Corner2Pt: TPoint;
|
|
begin
|
|
with Canvas do
|
|
begin
|
|
Brush.Style := bsSolid;
|
|
Brush.Color := FLineColor;
|
|
Pen.Color := FLineColor;
|
|
|
|
// Draw a line connecting the Conn and Term points
|
|
PenPos := ConnPt;
|
|
LineTo(TermPt.X, TermPt.Y);
|
|
// Set the basic points (to be modified depending on arrow head direction
|
|
PointPt := TermPt;
|
|
Corner1Pt := ConnPt;
|
|
Corner2Pt := ConnPt;
|
|
|
|
if ConnPt.X < TermPt.X then
|
|
begin
|
|
// Draw a right pointing arrow head
|
|
Inc(Corner1Pt.X, 10);
|
|
Inc(Corner2Pt.X, 10);
|
|
Dec(Corner1Pt.Y, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.Y, RectHeight(EndTermRect) div 2);
|
|
end
|
|
else
|
|
if ConnPt.X > TermPt.X then
|
|
begin
|
|
// Draw a left pointing arrow head
|
|
Dec(Corner1Pt.X, 10);
|
|
Dec(Corner2Pt.X, 10);
|
|
Dec(Corner1Pt.Y, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.Y, RectHeight(EndTermRect) div 2);
|
|
end
|
|
else
|
|
if ConnPt.Y < TermPt.Y then
|
|
begin
|
|
// Draw a down pointing arrow head
|
|
Inc(Corner1Pt.Y, 10);
|
|
Inc(Corner2Pt.Y, 10);
|
|
Dec(Corner1Pt.X, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.X, RectHeight(EndTermRect) div 2);
|
|
end
|
|
else
|
|
begin
|
|
// Draw a up pointing arrow head
|
|
Dec(Corner1Pt.Y, 10);
|
|
Dec(Corner2Pt.Y, 10);
|
|
Dec(Corner1Pt.X, RectHeight(EndTermRect) div 2);
|
|
Inc(Corner2Pt.X, RectHeight(EndTermRect) div 2);
|
|
end;
|
|
Brush.Color := clWindow;
|
|
Polygon([PointPt, Corner1Pt, Corner2Pt]);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvSubCaseArrow.DrawEndTerminator;
|
|
var
|
|
ConnPt, TermPt: TPoint;
|
|
begin
|
|
inherited DrawEndTerminator;
|
|
if Assigned(FEndConn.Shape) then
|
|
begin
|
|
ConnPt := Convert(FEndConn.ConnPoint(EndTermRect));
|
|
TermPt := Convert(FEndConn.TermPoint(EndTermRect));
|
|
DrawArrowHead(ConnPt, TermPt);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvSubCaseArrow.DrawStartTerminator;
|
|
var
|
|
ConnPt, TermPt: TPoint;
|
|
begin
|
|
inherited DrawStartTerminator;
|
|
if not Assigned(FStartConn.Shape) then
|
|
Exit;
|
|
ConnPt := Convert(FStartConn.ConnPoint(StartTermRect));
|
|
TermPt := Convert(FStartConn.TermPoint(StartTermRect));
|
|
with Canvas do
|
|
begin
|
|
// Draw a line connecting the Conn and Term points
|
|
Pen.Color := FLineColor;
|
|
PenPos := ConnPt;
|
|
LineTo(TermPt.X, TermPt.Y);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvDoubleHeadArrow } =================================================
|
|
|
|
constructor TJvDoubleHeadArrow.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
StartTermRect := EndTermRect;
|
|
end;
|
|
|
|
procedure TJvDoubleHeadArrow.DrawStartTerminator;
|
|
var
|
|
ConnPt, TermPt: TPoint;
|
|
begin
|
|
inherited DrawStartTerminator;
|
|
if Assigned(FStartConn.Shape) then
|
|
begin
|
|
ConnPt := Convert(FStartConn.ConnPoint(StartTermRect));
|
|
TermPt := Convert(FStartConn.TermPoint(StartTermRect));
|
|
DrawArrowHead(ConnPt, TermPt);
|
|
end;
|
|
end;
|
|
|
|
//=== Initialisation and cleanup routines ====================================
|
|
|
|
procedure RegisterStorageClasses;
|
|
begin
|
|
{$IFDEF COMPILER7_UP}
|
|
GroupDescendentsWith(TJvConnection, TControl);
|
|
{$ENDIF COMPILER7_UP}
|
|
RegisterClasses([TJvCustomDiagramShape, TJvMoveableShape,
|
|
TJvSizeableShape, TJvConnection, TJvConnector, TJvSingleHeadArrow,
|
|
TJvBluntSingleHeadArrow, TJvDoubleHeadArrow, TJvBitmapShape,
|
|
TJvTextShape, TJvStandardShape, TJvSingleHeadOpenDashArrow,
|
|
TJvBluntSingleHeadOpenDashArrow, TJvSubCaseArrow]);
|
|
end;
|
|
|
|
initialization
|
|
{$IFDEF UNITVERSIONING}
|
|
RegisterUnitVersion(HInstance, UnitVersioning);
|
|
{$ENDIF UNITVERSIONING}
|
|
RegisterStorageClasses;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
finalization
|
|
UnregisterUnitVersion(HInstance);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
end.
|
|
|