{----------------------------------------------------------------------------- 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 Contributor(s): Michael Beck 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.