{************************************************************}
{                   Delphi VCL Extensions                    }
{                                                            }
{                TsgDrawingNavigator class                   }
{                                                            }
{      Copyright (c) 2002-2013 SoftGold software company     }
{                                                            }
{************************************************************}

unit sgDrawingNavigator;
{$INCLUDE SGDXF.INC}
interface

uses
   Windows, Messages, Classes, Forms, Controls, Graphics, StdCtrls, Menus,
   SysUtils, sgConsts, sgTools, sgOrbit3D, sgRectangle, CADImage, DXFConv
{$IFDEF SGDEL_XE2}
  , System.Types
{$ENDIF}
   ;

const
  WM_SCALECHANGING  = WM_USER + 32;
  WM_PICTUREMOVE    = WM_USER + 33;

  // notification WM_PICTURECHANGE:
  //  - WParam = Integer(Self),
  //  - LParam = Integer(FTmpGraphic); reference to previous graphic
  WM_PICTURECHANGE  = WM_USER + 34;

  // notification WM_GETIMAGEEXTENTS:
  //  - WParam = addres of PFRect,
  //  - LParam = Integer(FPicture.Graphic) reference to graphic
  WM_GETIMAGEEXTENTS  = WM_USER + 35;

  // notification WM_GETIMAGEBOX:
  //  - WParam = addres of PFRect,
  //  - LParam = Integer(FPicture.Graphic) reference to graphic
  WM_GETIMAGEBOX  = WM_USER + 36;

  iMinPicSize = 2;
  iScrollSmallChange = 4;
  mmPerInch = 25.4;
  iMaxNonWinNTDim = $7FFF;
  iMaxWinNTDim = $20DFFFFF; // empirical

  cnstCursorHand = 5;
  cnstCursorCross = 6;

{$IFNDEF SGDEL_4}  // for Delphi 3

  CM_MOUSEWHEEL = CM_BASE + 67;

type

  TMouseWheelEvent = procedure(Sender: TObject; Shift: TShiftState;
    WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean) of object;
  TMouseWheelUpDownEvent = procedure(Sender: TObject; Shift: TShiftState;
    MousePos: TPoint; var Handled: Boolean) of object;

  TWMMouseWheel = packed record
    Msg: Cardinal;
    Keys: SmallInt;
    WheelDelta: SmallInt;
    case Integer of
      0: (
        XPos: Smallint;
        YPos: Smallint);
      1: (
        Pos: TSmallPoint;
        Result: Longint);
  end;

  TCMMouseWheel = record
    Msg: Cardinal;
    ShiftState: TShiftState;
    Unused: Byte;
    WheelDelta: SmallInt;
    case Integer of
      0: (
        XPos: Smallint;
        YPos: Smallint);
      1: (
        Pos: TSmallPoint;
        Result: Longint);
  end;

  TsgCustomControl = class(TCustomControl)
  private
    FOnMouseWheel: TMouseWheelEvent;
    FOnMouseWheelDown: TMouseWheelUpDownEvent;
    FOnMouseWheelUp: TMouseWheelUpDownEvent;
    FOnResize: TNotifyEvent;
    FWheelAccumulator: Integer;
    procedure CMMouseWheel(var Message: TCMMouseWheel); message CM_MOUSEWHEEL;
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
    procedure WMMouseWheel(var Message: TWMMouseWheel); message WM_MOUSEWHEEL;
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
  protected
    function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
      MousePos: TPoint): Boolean; dynamic;
    function DoMouseWheelDown(Shift: TShiftState;
      MousePos: TPoint): Boolean; dynamic;
    function DoMouseWheelUp(Shift: TShiftState;
      MousePos: TPoint): Boolean; dynamic;
    procedure Resize; dynamic;
    property OnMouseWheel: TMouseWheelEvent read FOnMouseWheel
      write FOnMouseWheel;
    property OnMouseWheelDown: TMouseWheelUpDownEvent read FOnMouseWheelDown
      write FOnMouseWheelDown;
    property OnMouseWheelUp: TMouseWheelUpDownEvent read FOnMouseWheelUp
      write FOnMouseWheelUp;
    property OnResize: TNotifyEvent read FOnResize write FOnResize;
    property WheelAccumulator: Integer read FWheelAccumulator
      write FWheelAccumulator;
  public
    procedure MouseWheelHandler(var Message: TMessage); dynamic;
  end;
{$ELSE}
type
  TsgCustomControl = class(TCustomControl);
{$ENDIF}

type
  TsgDrawingNavigatorStates = (dnsLoading, dnsZooming, dnsMoving,
    dnsDrawing, dnsSclollChanging, dnsSetView, dnsWheel, dnsAfterRotate,
    dnsPictureChanging);
  TsgDrawingNavigatorStateSet = set of TsgDrawingNavigatorStates;

  TGetImageExtents = function(var ALeft, ATop, AZ1, ARight, ABottom,
    AZ2: Double): Boolean of object;

  TsgDrawingNavigator = class(TsgCustomControl)
  private
    FActualGraphic: TGraphic;
    FAppActive: Boolean;
    FAutoFocus: Boolean;
    FBufferBMP: HBITMAP;
    FBufferDC: HDC;
    FBufferSaved: Boolean;
    FCenterAlignment: Boolean;
    FClients: TList;
    FClipRectPopupMenu: TPopupMenu;
    FDrawOnEveryMouseWheel: Boolean;
    FExtents: TFRect;
    FHorzScrollBar: TScrollBar;
    FHorzScrollBarVisibility: Boolean;
    FIterateParam: TsgCADIterate;
    FLinearDimensionsMode: TsgDistUnitsMode;
    FLinearDimensionsScaleFactor: Double;
    FLockChangeVisibleArea: Boolean;
    FLoadedFromConverter: Boolean;
    FMaxDim: Integer;
    FMouseDownPt: TPoint;
    FMouseDragging: Boolean;
    FMouseMovePrevPoint: TPoint;
    FMouseMoveZoomingProcess: Boolean;
    FMouseScaling: Boolean;
    FMoveByMouse: Boolean;
    FNeedBroadcastPaintEvent: Boolean;
    FOldGDIObj: HGDIOBJ;
    FOnChangeScale: TNotifyEvent;
    FOnGetImageExtents: TGetImageExtents;
    FOnGetSysMenuIcons: TNotifyEvent;
    FOnPaint: TNotifyEvent;
    FOnPictureChange: TNotifyEvent;
    FOnPictureMove: TNotifyEvent;
    FOnProgress: TProgressEvent;
    FOrbit3D: TsgCustomOrbit3D;
    FPicture: TPicture;
    FPictureChanged: Boolean;
    FPictureRect2D: TF2DRect;
    FPictureRectChanged: Boolean;
    FRectangle: TsgRectangle;
    FRectZooming: Boolean;
    FRectZoomingProcess: Boolean;
    FRotOffs: TFPoint;
    FScalingOnMouseWheel: Boolean;
    FSnapControl: TSnapControl;
    FState: TsgDrawingNavigatorStateSet;
    FStretch: Boolean;
    FSysMenuIcons: TsgSysMenuIcons;
    FTmpGraphic: TGraphic;
    FTmpLayout: TObject;
    FTool: TsgControlTool;
    FUpdateCount: Integer;
    FVertScrollBar: TScrollBar;
    FVertScrollBarVisibility: Boolean;
    FViewRectMode: Boolean;
    FVPortOpened: Boolean;
    FWidthInPixels: Double;
    FZoomChangeByMouseMove: Single;
    FZoomInChange: Single;
    FZoomingRect: TRect;
    FZoomOutChange: Single;
    FOnFitToSize: TList;
    FBlockFocusUpdate: Boolean;
    FOnGetActualGraphic: TNotifyEvent;
    procedure BroadcastClients(var Message);
    procedure CMColorChanged(var Message: TMessage); message CM_COLORCHANGED;
    procedure CMCursorChanged(var Message: TMessage); message CM_CURSORCHANGED;
    function CreateBuffer: Boolean;
    function CreateHorScroll: Boolean;
    function CreateVertScroll: Boolean;
    procedure CreateSnap;
    procedure DestroyBuffer;
    procedure DoChangeScrolls;
    procedure DrawScrollBarsJoint(const ADC: HDC);
    procedure DrawThrowBuffer;
    procedure DrawZoomingRect(ARect: TRect);
    function GetBufferDC: HDC;
    function GetClipRectangle: Boolean;
    function GetClipRectCoords: TRect;
    function GetImageBox: TFRect;
    function GetImageExtents: TFRect;
    function GetImageView(const ALayoutIndex: Integer = -1): TsgView;
    function GetMouseMovePrevPoint: TPoint;
    function GetOrbit3D: TsgCustomOrbit3D;
    function GetOrbitAutoTarget: Boolean;
    function GetPictureHeight: Integer;
    function GetPictureRect: TRect;
    function GetPictureRect2D: TF2DRect;
    function GetPictureRectCenter: TPoint;
    function GetPictureRectCenter2D: TF2DPoint;
    function GetPictureRectHeight: Double;
    function GetPictureRectWidth: Double;
    function GetPictureWidth: Integer;
    procedure GetPointToImageSizeRatio(ACoordX, ACoordY: Double;
      var AXScaled, AYScaled: Extended);
    function GetRectangle: TsgRectangle;
    function GetScale: Double;
    function GetSimilarRect(const AExtents, AViewFRect: TFRect;
      var APictureRect: TF2DRect): TF2DRect;
    function GetSysMenuIconsVisible: Boolean;
    procedure HScrollEvent(Sender: TObject; ScrollCode: TScrollCode;
      var ScrollPos: Integer);
    function GetLoadedFromConverter: Boolean;
    procedure ReCreateBuffer;
    procedure RectZoomingOperation(ACoordX, ACoordY: Integer; AErase: Boolean);
    procedure RenderBuffer(const ADC: HDC);
    procedure SaveBuffer(const ADC: HDC);
    procedure SetAutoFocus(const AValue: Boolean);
    procedure SetClipRectangle(const AValue: Boolean);
    procedure SetClipRectCoords(const AValue: TRect);
    procedure SetClipRectPopupMenu(const AValue: TPopupMenu);
    procedure SetDrawOnEveryMouseWheel(const AValue: Boolean);
    procedure SetHorzScrollBarVisibility(const AValue: Boolean);
    procedure SetLinearDimensionsScale(const AFactor: Double);
    procedure SetOnGetImageExtents(const AValue: TGetImageExtents);
    procedure SetOrbitAutoTarget(const AValue: Boolean);
    procedure SetPicture(const AValue: TPicture);
    procedure SetPictureChanged(const AValue: Boolean);
    procedure SetPictureRect(const ARect: TRect);
    procedure SetPictureRect2D(const AValue: TF2DRect);
    procedure SetPictureRectChanged(const AValue: Boolean);
    procedure SetRectZooming(const AValue: Boolean);
    procedure SetScale(const AValue: Double);
    procedure SetScrollsPosition;
    procedure SetScrollsRange;
    procedure SetStretch(const AValue: Boolean);
    procedure SetVertScrollBarVisibility(const AValue: Boolean);
    procedure SetVisibleArea;
    procedure SetSysMenuIconsVisible(const AValue: Boolean);
    procedure StartMoving(const ACoordX, ACoordY: Integer);
    procedure StopMouseOperations(var Message: TMessage); message CM_MOUSELEAVE;
    function TrySetPictureRect2D(const ANewRect: TF2DRect): Boolean;
    function ViewRectHeight: Integer;
    function ViewRectWidth: Integer;
    procedure VScrollEvent(Sender: TObject; ScrollCode: TScrollCode;
      var ScrollPos: Integer);
    function GetEnterClipRectEvent: TNotifyEvent;
    procedure SetEnterClipRectEvent(const Value: TNotifyEvent);
    procedure SetBlockFocusUpdate(const Value: Boolean);
    procedure StopMoveByMouse;
  protected
    procedure AfterRotate(Sender: TObject); virtual;
    function ApplicationHook(var Message: TMessage): Boolean;
    procedure BeforeRotate(Sender: TObject); virtual;
    function ConvertUnits(APoint: TFPoint; var AUnits: string): TFPoint;
    function CoordinateConvertion(ACoordX, ACoordY: Integer;
      var APointInUCS: TFPoint): TFPoint; virtual;
    procedure CreateHandle; override;
    procedure CreateOrbit3D(AClass: TClass); virtual;
    function CreateSnapShotBitmap: Graphics.TBitmap; virtual;
    procedure DoBroadcastPaintEvent(ADC: HDC); virtual;
    procedure DoChangeScale; dynamic;
    procedure DoGetActualGraphic; dynamic;
    function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
      MousePos: TPoint): Boolean; override;
    function DoMouseWheelDown(Shift: TShiftState;
      MousePos: TPoint): Boolean; override;
    function DoMouseWheelUp(Shift: TShiftState;
      MousePos: TPoint): Boolean; override;
    procedure DoOnColorChanged(const AColor: TColor); virtual;
    procedure DoOnCursorChanged; virtual;
    procedure DoOnStopMouseOperations; virtual;
    procedure DoPictureChange(Sender: TObject); virtual;
    procedure DoPictureMove; dynamic;
    procedure DoFitToSize;
    function FinishEntity(Entity: TsgDXFEntity): Integer; virtual;
    function GetZoomInEnable: Boolean; virtual;
    function GetZoomOutEnable: Boolean; virtual;
    function GetActualGraphic: TGraphic; virtual;
    function GetEntity(Entity: TsgDXFEntity): Integer; virtual;
    function GetOrbit3DClass: TClass; virtual;
    function GetPoint(APoint: TFPoint): TPoint;
    function GetRenderBuffer: Boolean; virtual;
    function GetSizesAsString: string; virtual; //gets string with information of drawing measurement
    function GetHasTriangledMesh: Boolean; virtual;
    function InitializeIterateParams: PsgCADIterate; virtual;
    function InitialPicRect(const AClientWidth, AClientHeight: Integer): TRect;
    function InitialPicRect2D(const AClientWidth, AClientHeight: Double): TF2DRect;
    function IsClipRectActive: Boolean;
    function IsMouseInSysButtons: Boolean;
    procedure LockChangeVisibleArea;
    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;

    procedure Notification(AComponent: TComponent;
      Operation: TOperation); override;
    procedure OrbitMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer); dynamic;
    procedure OrbitMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); dynamic;
    procedure OrbitMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer); dynamic;

    procedure ObtainFocus(var Message: TMessage); message WM_MOUSEMOVE;
    function OpenVPort: Boolean; virtual;
    procedure Paint; override;
    function PerformBroadcastEvents(const AMsg: Cardinal;
      const AWParam: WPARAM; const  ALParam: LPARAM): LRESULT;
    procedure Progress(Sender: TObject; Stage: TProgressStage; PercentDone:
      Byte; RedrawNow: Boolean; const R: TRect; const Msg: string); dynamic;
    procedure Resize; override;
    procedure SetActualGraphic(AGraphic: TGraphic); virtual;
    procedure SaveCurrentView(const ARect: PRect = nil); virtual;
    procedure SetCommonUnitsWidth(ADC: HDC);
    procedure SetParent(AParent: TWinControl); override;
    function ShowRectConvertUnits(ARect: TFRect;
      const AConvertUnits: Boolean): Boolean;
    function ViewRect: TRect;
    procedure UnlockChangeVisibleArea;
{$IFDEF SGDEL_5}
    procedure WMContextMenu(var Message: TWMContextMenu); message WM_CONTEXTMENU;
{$ENDIF}
    procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
    function ZoomRect(ARect: TRect): Boolean;
    function ZoomRectEx(ARect, APictureRect: TRect): Boolean;
    function ZoomRectEx2D(ARect, APictureRect: TF2DRect): Boolean;
    property AppActive: Boolean read FAppActive;
    property ActualGraphicRef: TGraphic read FActualGraphic;
    property BufferDC: HDC read GetBufferDC;
    property BufferBMP: HBITMAP read FBufferBMP;
    property OnGetActualGraphic: TNotifyEvent read FOnGetActualGraphic write FOnGetActualGraphic;
    property OnFitToSize: TList read FOnFitToSize;
    property IterateParam: TsgCADIterate read FIterateParam;
    property MaxDim: Integer read FMaxDim write FMaxDim;
    property MouseMovePrevPoint: TPoint read GetMouseMovePrevPoint;
    property PictureChanged: Boolean read FPictureChanged
      write SetPictureChanged;
    property PictureRectChanged: Boolean read FPictureRectChanged
      write SetPictureRectChanged;
    property SysMenuIcons: TsgSysMenuIcons read FSysMenuIcons;
    property Rectangle: TsgRectangle read GetRectangle;
    property State: TsgDrawingNavigatorStateSet read FState;
    property ControlTool: TsgControlTool read FTool;
    property UpdateCount: Integer read FUpdateCount write FUpdateCount;
    property OnGetSysMenuIcons: TNotifyEvent read FOnGetSysMenuIcons write FOnGetSysMenuIcons;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function AddClient(AClient: TObject): Integer;
    function AlterScale(AFactor: Double; AIsAbsolute: Boolean;
      APosition: TPoint): Boolean; virtual;
    function BeginUpdate: Integer; virtual;
    procedure CallPreviousView; virtual;
    function Center: TPoint;
    procedure Clear; virtual;
    procedure CorrectPictureRectByExtents;
    procedure CreateParams(var Params: TCreateParams); override;
    function Empty: Boolean; virtual;
    function EndUpdate: Integer; virtual;
    procedure FitToSize; virtual;
    function GetDrawingCoords(ACoordX, ACoordY: Integer;
      var AUnits: string): TFPoint; virtual;
    function GetDrawingInternalCoords(ACoordX, ACoordY: Integer): TFPoint; virtual;
    function GetDrawingInternalCoordsWithoutSnap(ACoordX,
      ACoordY: Integer): TFPoint;
    function GetDrawingUCSCoords(ACoordX, ACoordY: Integer;
      var AUnits: string): TFPoint; virtual;
    function GetRealPoint(ACoordX, ACoordY: Double;
      var APointInUCS: TFPoint): TFPoint; virtual;
    function HasPreviousView: Boolean;
    procedure ImgLock;
    procedure ImgUnLock;
    procedure Invalidate; override;
    function IsCanBeEdited: Boolean; virtual;
    function IsCanShowCoords: Boolean; virtual;
    function IsCanShowSizes: Boolean; virtual; //if Result is not see SizesAsString
    function IsUseGraphic: Boolean; virtual;
    procedure LoadFromConverter(const AConverter: TsgDXFConverter;
      const ACADImage: TsgCADImage); virtual;
    procedure LoadFromFile(const AFileName: string); virtual;
    procedure MovePictureRect(const ADeltaX, ADeltaY: Integer); virtual;
    function MovePictureRect2D(const ADeltaX, ADeltaY: Double): Boolean; virtual;
    function RemoveClient(AClient: TObject): Integer;
    procedure Repaint; override;
    procedure RotToView(const A3DView: TsgDXFViewDirection); virtual;
    function Rotate(Axis: TsgAxes; Angle: Extended): Boolean; virtual;
    function ShowPoint(const APoint: TFPoint; AScale: Double): Boolean;
    function ShowRect(ARect: TFRect): Boolean;
    procedure CNKeyDown(var Message: TWMKeyDown); message CN_KEYDOWN;
    procedure WndProc(var Message: TMessage); override;
    procedure UpdateSnap;
    property ActualGraphic: TGraphic read GetActualGraphic
      write SetActualGraphic;
    property BlockFocusUpdate: Boolean read FBlockFocusUpdate write SetBlockFocusUpdate;
    property OrbitAutoTarget: Boolean read GetOrbitAutoTarget write SetOrbitAutoTarget;
    property Canvas;
    property ClipRectangle: Boolean read GetClipRectangle
      write SetClipRectangle;
    property ClipRectCoords: TRect read GetClipRectCoords
      write SetClipRectCoords;
    property ClipRectPopupMenu: TPopupMenu read FClipRectPopupMenu
      write SetClipRectPopupMenu;
    property DrawOnEveryMouseWheel: Boolean read FDrawOnEveryMouseWheel
      write SetDrawOnEveryMouseWheel;
    property HasTriangledMesh: Boolean read GetHasTriangledMesh;
    property HorzScrollBar: TScrollBar read FHorzScrollBar;
    property HorzScrollBarVisibility: Boolean read FHorzScrollBarVisibility
      write SetHorzScrollBarVisibility;
    property LinearDimensionsMode: TsgDistUnitsMode read FLinearDimensionsMode
      write FLinearDimensionsMode;
    property LinearDimensionsScale: Double read FLinearDimensionsScaleFactor
      write SetLinearDimensionsScale;
    property LoadedFromConverter: Boolean read GetLoadedFromConverter;
    property OnEnterClipRect: TNotifyEvent read GetEnterClipRectEvent
      write SetEnterClipRectEvent;
    property Orbit3D: TsgCustomOrbit3D read GetOrbit3D;
    property PictureHeight: Integer read GetPictureHeight;
    property PictureRect: TRect read GetPictureRect write SetPictureRect;
    property PictureRect2D: TF2DRect read GetPictureRect2D write SetPictureRect2D;
    property PictureRectCenter: TPoint read GetPictureRectCenter;
    property PictureRectCenter2D: TF2DPoint read GetPictureRectCenter2D;
    property PictureRectHeight: Double read GetPictureRectHeight;
    property PictureRectWidth: Double read GetPictureRectWidth;
    property PictureWidth: Integer read GetPictureWidth;
    property ScalingOnMouseWheel: Boolean read FScalingOnMouseWheel
      write FScalingOnMouseWheel;
    property SnapControl: TSnapControl read FSnapControl;
    property SizesAsString: string read GetSizesAsString;
    property SysMenuIconsVisible: Boolean read GetSysMenuIconsVisible write SetSysMenuIconsVisible;
    property VertScrollBar: TScrollBar read FVertScrollBar;
    property VertScrollBarVisibility: Boolean read FVertScrollBarVisibility
      write SetVertScrollBarVisibility;
    property ViewRectMode: Boolean read FViewRectMode write FViewRectMode;
    property ZoomInChange: Single read FZoomInCHange write FZoomInChange;
    property ZoomInEnable: Boolean read GetZoomInEnable;
    property ZoomOutChange: Single read FZoomOutChange write FZoomOutChange;
    property ZoomOutEnable: Boolean read GetZoomOutEnable;
  published
    property Align;
    property AutoFocus: Boolean read FAutoFocus write SetAutoFocus;
    property CenterAlignment: Boolean read FCenterAlignment
      write FCenterAlignment default False;
    property Color;
    property DragCursor;
    property DragMode;
    property Enabled;
    property MouseDragging: Boolean read FMouseDragging
      write FMouseDragging default True;
    property MouseScaling: Boolean read FMouseScaling
      write FMouseScaling default False;
    property OnChangeScale: TNotifyEvent read FOnChangeScale
      write FOnChangeScale;
    property OnClick;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnGetImageExtents: TGetImageExtents read FOnGetImageExtents
      write SetOnGetImageExtents;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
    property OnPictureChange: TNotifyEvent read FOnPictureChange
      write FOnPictureChange;
    property OnPictureMove: TNotifyEvent read FOnPictureMove
      write FOnPictureMove;
    property OnProgress: TProgressEvent read FOnProgress write FOnProgress;
    property OnStartDrag;
    property ParentShowHint;
    property Picture: TPicture read FPicture write SetPicture;
    property PopupMenu;
    property RectZooming: Boolean read FRectZooming
      write SetRectZooming default True;
    property Scale: Double read GetScale write SetScale;
    property ShowHint;
    property Stretch: Boolean read FStretch write SetStretch default False;
    property Visible;
{$IFDEF SGDEL_4}
    property Anchors;
    property Constraints;
    property DragKind;
    property OnEndDock;
    property OnResize;
    property OnStartDock;
{$ENDIF}
{$IFDEF SGDEL_5}
    property OnContextPopup;
{$ENDIF}
  end;

  TsgDrawingNavigatorClass = class of TsgDrawingNavigator;

  TsgNaviOrbit3D = class(TsgOrbit3D)
  private
    FNavigator: TsgDrawingNavigator;
    procedure SetNavigator(const Value: TsgDrawingNavigator);
  protected
    procedure CMCursorChanged(var Message: TMessage); message CM_CURSORCHANGED;
    procedure DoAfterCustomRotate; override;
    procedure DoBeforeCustomRotate; override;
    procedure Rotate(const AAxis: Integer; const AAngle: Double); override;
    procedure VisibleChanging; override;
  public
    property Navigator: TsgDrawingNavigator read FNavigator write SetNavigator;
  end;

  function CompareDouble(const AValue1, AValue2: Double): Integer;
  procedure CorrectRect(var ARect: TRect);

implementation

uses
  Math, sgFunction;

{$R *.RES}

type
  TsgRectangleAccess = class(TsgRectangle);
  TsgCADConverter = class(TsgDXFConverter);
  TsgCADImageAccess = class(TsgCADImage);
  TsgDXFLayoutAccess = class(TsgDXFLayout);
  TControlAccess = class(TControl);

  TsgPicture = class(TPicture)
  private
    FOwner: TPersistent;
  protected
    function GetOwner: TPersistent; override;
  public
    constructor Create(const AOwner: TPersistent);
  end;

var
  DottedRectPen, DottedRectBrush: HGDIOBJ;

procedure InitDottedRectObjects;
const
  cnstBrush: TLogBrush = (lbStyle: BS_NULL; lbColor: 0; lbHatch: 0);
begin
  DottedRectPen := CreatePen(PS_DOT, 1, 0);
  DottedRectBrush := CreateBrushIndirect(cnstBrush);
end;

procedure ClearDottedRectObjects;
begin
  DeleteObject(DottedRectPen);
  DeleteObject(DottedRectBrush);
end;

function GetBitmapSize(AHandle: HBITMAP; var ASize: TPoint): Boolean;
var
  vSize: Integer;
  vBitmap: Windows.TBitmap;
begin
  ASize := Point(0, 0);
  vSize := GetObject(AHandle, SizeOf(Windows.TBitmap), @vBitmap);
  Result := vSize = SizeOf(Windows.TBitmap);
  if Result then
    ASize := Point(vBitmap.bmWidth, vBitmap.bmHeight);
end;

function CompareDouble(const AValue1, AValue2: Double): Integer;
begin
  if sgIsZero(AValue1 - AValue2) then
    Result := 0
  else
    if AValue1 < AValue2 then
      Result := -1
    else
      Result := 1;
end;

procedure CopyDC(ASrc, ADst: HDC; AWidth, AHeight: Integer);
begin
  BitBlt(ADst, 0, 0, AWidth, AHeight, ASrc, 0, 0, SRCCOPY);
end;

procedure CorrectRect2D(var ARect: TF2DRect);
begin
  if ARect.Left > ARect.Right then
    SwapSGFloats(ARect.Left, ARect.Right);
  if ARect.Top > ARect.Bottom then
    SwapSGFloats(ARect.Top, ARect.Bottom);
end;

procedure CorrectRect(var ARect: TRect);
begin
  if ARect.Left > ARect.Right then
    SwapInts(ARect.Left, ARect.Right);
  if ARect.Top > ARect.Bottom then
    SwapInts(ARect.Top, ARect.Bottom);
end;

procedure DrawDottedRect(const ADC: HDC; const ARect: TRect);
var
  vPrevBkMode, vPrevPenMode: Integer;
  vPrevPen, vPrevBrush: HGDIOBJ;
begin
  vPrevPen := SelectObject(ADC, DottedRectPen);
  vPrevBrush := SelectObject(ADC, DottedRectBrush);
  vPrevPenMode := SetROP2(ADC, R2_NOTXORPEN);
  vPrevBkMode := SetBkMode(ADC, TRANSPARENT);
  Rectangle(ADC, ARect.Left, ARect.Top, ARect.Right, ARect.Bottom);
  SetBkMode(ADC, vPrevBkMode);
  SetROP2(ADC, vPrevPenMode);
  SelectObject(ADC, vPrevBrush);
  SelectObject(ADC, vPrevPen);
end;

function RoundPoint2D(const APoint: TF2DPoint): TPoint;
begin
  Result.X := Round(APoint.X);
  Result.Y := Round(APoint.Y);
end;

function GetRectCenter(ARect: TRect): TPoint; overload;
begin
  Result := Point(Round((ARect.Right + ARect.Left) / 2),
    Round((ARect.Bottom + ARect.Top) / 2));
end;

function GetRectCenter(ARect: TFRect): TFPoint; overload;
begin
  Result := MakeFPoint((ARect.Right + ARect.Left) / 2,
    (ARect.Bottom + ARect.Top) / 2,
    (ARect.Z1 + ARect.Z2) / 2);
end;

function GetRectCenter(ARect: TF2DRect): TF2DPoint; overload;
begin
  Result := MakeF2DPoint((ARect.Right + ARect.Left) / 2,
    (ARect.Bottom + ARect.Top) / 2);
end;

function IsEqualFRects(const ARect1, ARect2: TFRect): Boolean;
begin
  Result := IsEqualFPoints(ARect1.TopLeft, ARect2.TopLeft) and
    IsEqualFPoints(ARect1.BottomRight, ARect2.BottomRight)
end;

function IsAbsoluteEqualFRects(const ARect1, ARect2: TFRect): Boolean;
begin
  Result := (ARect1.Left = ARect2.Left) and (ARect1.Right = ARect2.Right)
    and (ARect1.Z1 = ARect2.Z2) and (ARect1.Top = ARect2.Top)
    and (ARect1.Bottom = ARect2.Bottom);
end;

function IsEqualRects(const ARect1, ARect2: TRect): Boolean;
begin
  Result := (ARect1.Left = ARect2.Left) and (ARect1.Top = ARect2.Top) and
    (ARect1.Right = ARect2.Right) and (ARect1.Bottom = ARect2.Bottom)
end;

function TransformRect2D(const ASrc: TF2DRect; const AFSrc, AFDst: TFRect): TF2DRect;
var
  vXKf, vYKf: Double;
  {
  vSrc2dCenter, vSrcCenter, vDstCenter: TFPoint;
  procedure DoLeftRight;
  begin
    vXKf := (ASrc.Right - ASrc.Left) / vXKf;
    vSrcCenter.X := 0.5 * (AFSrc.Left + AFSrc.Right);
    vDstCenter.X := 0.5 * (AFDst.Left + AFDst.Right);
    vSrc2dCenter.X := 0.5 * (ASrc.Left + ASrc.Right) +
      (vDstCenter.X - vSrcCenter.X) * vXKf;
    Result.Left := Round(vSrc2dCenter.X + (AFDst.Left - vDstCenter.X) * vXKf);
    Result.Right := Round(vSrc2dCenter.X - (vDstCenter.X - AFDst.Right) * vXKf);
  end;

  procedure DoTopBottom;
  begin
    vYKf := (ASrc.Bottom - ASrc.Top) / vYKf;
    vSrcCenter.Y := 0.5 * (AFSrc.Top + AFSrc.Bottom);
    vDstCenter.Y := 0.5 * (AFDst.Top + AFDst.Bottom);
    vSrc2dCenter.Y := 0.5 * (ASrc.Top + ASrc.Bottom) +
      (vDstCenter.Y - vSrcCenter.Y) * vYKf;
    Result.Top := Round(vSrc2dCenter.Y + (AFDst.Top - vDstCenter.Y) * vYKf);
    Result.Bottom := Round(vSrc2dCenter.Y -
      (vDstCenter.Y - AFDst.Bottom) * vYKf);
  end;
  {}
  procedure DoTopBottom(const ASrc: TF2DRect; const AFSrc, AFDst: TFRect;
    const AYKf: Double; var ARect: TF2DRect);
  var
    vYKf: Double;
  begin
    vYKf := (ASrc.Bottom - ASrc.Top) / AYKf;
    ARect.Top := ASrc.Top - (AFDst.Top - AFSrc.Top) * vYKf;
    ARect.Bottom := ARect.Top + (AFDst.Top - AFDst.Bottom) * vYKf;
  end;

  procedure DoLeftRight(const ASrc: TF2DRect; const AFSrc, AFDst: TFRect;
    const AXKf: Double; var ARect: TF2DRect);
  var
    vXKf: Double;
  begin
    vXKf := (ASrc.Right - ASrc.Left) / AXKf;
    ARect.Left := ASrc.Left - (AFSrc.Left - AFDst.Left) * vXKf;
    ARect.Right := ARect.Left + (AFDst.Right - AFDst.Left) * vXKf;
  end;
  {}

begin
  Result := ASrc;
  if not IsBadRect(AFSrc) and not IsBadRect(AFDst) then
  begin
    vXKf := AFSrc.Right - AFSrc.Left;
    vYKf := AFSrc.Top - AFSrc.Bottom;
    if Abs(vXKf) < fAccuracy then
      vXKf := 0.0;
    if Abs(vYKf) < fAccuracy then
      vYKf := 0.0;
    case Byte(vXKf = 0) + (Byte(vYKf = 0) shl 1) of
      1: DoTopBottom(ASrc, AFSrc, AFDst, vYKf, Result);
      2: DoLeftRight(ASrc, AFSrc, AFDst, vXKf, Result);
      3:;
    else
      DoLeftRight(ASrc, AFSrc, AFDst, vXKf, Result);
      DoTopBottom(ASrc, AFSrc, AFDst, vYKf, Result);
    end;
  end;
end;
(*
function TransformRect(const ASrc: TRect; const AFSrc, AFDst: TFRect): TRect;
var
  vXKf, vYKf: Double;
  {
  vSrc2dCenter, vSrcCenter, vDstCenter: TFPoint;
  procedure DoLeftRight;
  begin
    vXKf := (ASrc.Right - ASrc.Left) / vXKf;
    vSrcCenter.X := 0.5 * (AFSrc.Left + AFSrc.Right);
    vDstCenter.X := 0.5 * (AFDst.Left + AFDst.Right);
    vSrc2dCenter.X := 0.5 * (ASrc.Left + ASrc.Right) +
      (vDstCenter.X - vSrcCenter.X) * vXKf;
    Result.Left := Round(vSrc2dCenter.X + (AFDst.Left - vDstCenter.X) * vXKf);
    Result.Right := Round(vSrc2dCenter.X - (vDstCenter.X - AFDst.Right) * vXKf);
  end;

  procedure DoTopBottom;
  begin
    vYKf := (ASrc.Bottom - ASrc.Top) / vYKf;
    vSrcCenter.Y := 0.5 * (AFSrc.Top + AFSrc.Bottom);
    vDstCenter.Y := 0.5 * (AFDst.Top + AFDst.Bottom);
    vSrc2dCenter.Y := 0.5 * (ASrc.Top + ASrc.Bottom) +
      (vDstCenter.Y - vSrcCenter.Y) * vYKf;
    Result.Top := Round(vSrc2dCenter.Y + (AFDst.Top - vDstCenter.Y) * vYKf);
    Result.Bottom := Round(vSrc2dCenter.Y -
      (vDstCenter.Y - AFDst.Bottom) * vYKf);
  end;
  {}
  procedure DoTopBottom(const ASrc: TRect; const AFSrc, AFDst: TFRect;
    const AYKf: Double; var ARect: TRect);
  var
    vYKf: Double;
  begin
    vYKf := (ASrc.Bottom - ASrc.Top) / AYKf;
    ARect.Top := Round(ASrc.Top - (AFDst.Top - AFSrc.Top) * vYKf);
    ARect.Bottom := Round(ARect.Top + (AFDst.Top - AFDst.Bottom) * vYKf);
  end;

  procedure DoLeftRight(const ASrc: TRect; const AFSrc, AFDst: TFRect;
    const AXKf: Double; var ARect: TRect);
  var
    vXKf: Double;
  begin
    vXKf := (ASrc.Right - ASrc.Left) / AXKf;
    ARect.Left := Round(ASrc.Left - (AFSrc.Left - AFDst.Left) * vXKf);
    ARect.Right := Round(ARect.Left + (AFDst.Right - AFDst.Left) * vXKf);
  end;
  {}

begin
  Result := ASrc;
  if not IsBadRect(AFSrc) and not IsBadRect(AFDst) then
  begin
    vXKf := AFSrc.Right - AFSrc.Left;
    vYKf := AFSrc.Top - AFSrc.Bottom;
    if Abs(vXKf) < fAccuracy then
      vXKf := 0.0;
    if Abs(vYKf) < fAccuracy then
      vYKf := 0.0;
    case Byte(vXKf = 0) + (Byte(vYKf = 0) shl 1) of
      1: DoTopBottom(ASrc, AFSrc, AFDst, vYKf, Result);
      2: DoLeftRight(ASrc, AFSrc, AFDst, vXKf, Result);
      3:;
    else
      DoLeftRight(ASrc, AFSrc, AFDst, vXKf, Result);
      DoTopBottom(ASrc, AFSrc, AFDst, vYKf, Result);
    end;
  end;
end;
*)

function Rect2DFromRect(const ARect: TRect): TF2DRect;
begin
  Result := MakeF2DRect(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom);
end;

function IsEqualF2DRects(const ARect1, ARect2: TF2DRect): Boolean;
begin
  Result := IsEqualF2DPoints(ARect1.TopLeft, ARect2.TopLeft) and
    IsEqualF2DPoints(ARect1.BottomRight, ARect2.BottomRight);
end;

procedure OffsetRect2D(var R: TF2DRect; DX, DY: Extended);
begin
  R.Left := R.Left + DX;
  R.Top := R.Top + DY;
  R.Right := R.Right + DX;
  R.Bottom := R.Bottom + DY;
end;

function RoundRect2D(const ARect: TF2DRect): TRect;
begin
  Result.Left := Round(ARect.Left);
  Result.Top := Round(ARect.Top);
  Result.Right := Result.Left + Round(ARect.Right - ARect.Left);
  Result.Bottom := Result.Top + Round(ARect.Bottom - ARect.Top);
end;

{ TsgNaviOrbit3D }

procedure TsgNaviOrbit3D.VisibleChanging;
begin
  inherited VisibleChanging;
  if Visible and Assigned(FNavigator) then
    FNavigator.PictureChanged := True;
end;

procedure TsgNaviOrbit3D.CMCursorChanged(var Message: TMessage);
begin
  if Assigned(FNavigator) then
    FNavigator.Cursor := Cursor
  else
    inherited;
end;

procedure TsgNaviOrbit3D.DoAfterCustomRotate;
begin
  if Assigned(FNavigator) then FNavigator.EndUpdate;
  inherited DoAfterCustomRotate;
end;

procedure TsgNaviOrbit3D.DoBeforeCustomRotate;
begin
  inherited DoBeforeCustomRotate;
  if Assigned(FNavigator) then FNavigator.BeginUpdate;
end;

procedure TsgNaviOrbit3D.Rotate(const AAxis: Integer; const AAngle: Double);
begin
  if Assigned(FNavigator) then
    FNavigator.Rotate(TsgAxes(AAxis), AAngle);
end;

procedure TsgNaviOrbit3D.SetNavigator(const Value: TsgDrawingNavigator);
begin
  if FNavigator <> Value then
  begin
    if FNavigator <> nil then
      FNavigator.RemoveFreeNotification(Self);
    FNavigator := Value;
    if FNavigator <> nil then
      FNavigator.FreeNotification(Self);
  end;
end;

{$IFNDEF SGDEL_4} // for Delphi 3

{ TsgCustomControl }

procedure TsgCustomControl.CMMouseWheel(var Message: TCMMouseWheel);
begin
  Message.Result := 0;
  if DoMouseWheel(Message.ShiftState, Message.WheelDelta,
     SmallPointToPoint(Message.Pos)) then
    Message.Result := 1
  else
    if Parent <> nil then
      TMessage(Message).Result := Parent.Perform(CM_MOUSEWHEEL,
        TMessage(Message).WParam, TMessage(Message).LParam);
end;

procedure TsgCustomControl.SetBounds(ALeft, ATop, AWidth,
  AHeight: Integer);
var
  vWidth, vHeight: Integer;
begin
  vWidth := AWidth;
  vHeight := AHeight;
  inherited;
  if (vWidth <> AWidth) or (vHeight <> AHeight)
     and not (csLoading in ComponentState) then
    Resize;
end;

procedure TsgCustomControl.WMMouseWheel(var Message: TWMMouseWheel);
begin
//  if not Mouse.WheelPresent then
//  begin
//    Mouse.WheelPresent := True;
//    Mouse.SettingChanged(SPI_GETWHEELSCROLLLINES);
//  end;
  TCMMouseWheel(Message).ShiftState := KeysToShiftState(Message.Keys);
  MouseWheelHandler(TMessage(Message));
  if Message.Result = 0 then
    inherited;
end;

procedure TsgCustomControl.WMSize(var Message: TWMSize);
begin
  inherited;
  if not (csLoading in ComponentState) then
    Resize;
end;

procedure TsgCustomControl.MouseWheelHandler(var Message: TMessage);
//var
//  Form: TCustomForm;
begin
//  Form := GetParentForm(Self);
//  if (Form <> nil) and (Form <> Self) then
//    Form.MouseWheelHandler(TMessage(Message))
//  else
  TMessage(Message).Result := Perform(CM_MOUSEWHEEL, TMessage(Message).WParam,
    TMessage(Message).LParam);
end;

function TsgCustomControl.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
  MousePos: TPoint): Boolean;
var
  vIsNeg: Boolean;
begin
  Result := False;
  if Assigned(FOnMouseWheel) then
    FOnMouseWheel(Self, Shift, WheelDelta, MousePos, Result);
  if not Result then
  begin
    Inc(FWheelAccumulator, WheelDelta);
    while Abs(FWheelAccumulator) >= WHEEL_DELTA do
    begin
      vIsNeg := FWheelAccumulator < 0;
      FWheelAccumulator := Abs(FWheelAccumulator) - WHEEL_DELTA;
      if vIsNeg then
      begin
        if FWheelAccumulator <> 0 then
          FWheelAccumulator := -FWheelAccumulator;
        Result := DoMouseWheelDown(Shift, MousePos);
      end
      else
        Result := DoMouseWheelUp(Shift, MousePos);
    end;
  end;
end;

function TsgCustomControl.DoMouseWheelDown(Shift: TShiftState;
  MousePos: TPoint): Boolean;
begin
  Result := False;
  if Assigned(FOnMouseWheelDown) then
    FOnMouseWheelDown(Self, Shift, MousePos, Result);
end;

function TsgCustomControl.DoMouseWheelUp(Shift: TShiftState;
  MousePos: TPoint): Boolean;
begin
  Result := False;
  if Assigned(FOnMouseWheelUp) then
    FOnMouseWheelUp(Self, Shift, MousePos, Result);
end;

procedure TsgCustomControl.Resize;
begin
  if Assigned(FOnResize) then
    FOnResize(Self);
end;
{$ENDIF}

{ TsgDrawingNavigator }

procedure TsgDrawingNavigator.AfterRotate(Sender: TObject);
var
  vM: TFMatrix;
  vScale: TFPoint;
begin
  if TsgCADImageAccess(FTool.Img).RotCount = 0 then
  try
    Include(FState, dnsAfterRotate);
    if FTool.IsNotCustomDraw then
    begin
      if TsgCADImageAccess(FTool.Img).IsRotToView then
      begin
        FTool.Img.Offset := FTool.Img.Center;
        FitToSize;
      end
      else
      begin
        vScale := ExtractMatrixAbsoluteScale(FTool.Img.DrawMatrix);
        vScale.Y := -vScale.Y;
        vM := FMatScale(FTool.GetRealImageMatrix, vScale);
        if OrbitAutoTarget then
          vM.E0 := SubFPoint(FRotOffs, AffineTransformPoint(FTool.Img.Center, vM));
        FTool.SetDrawMatrix(vM);
        if not OrbitAutoTarget then
        begin
          FTool.MovePositionToPoint(FRotOffs, Center);
          FTool.ResetRotCenter(Center);
        end;
        PictureRect2D := FTool.GetPictureRect2D;
      end;
    end;
  finally
    Exclude(FState, dnsAfterRotate);
  end;
end;

procedure TsgDrawingNavigator.BeforeRotate(Sender: TObject);
begin
  FExtents := GetImageExtents;
  if TsgCADImageAccess(FTool.Img).RotCount = 1 then
  begin
    if FTool.IsNotCustomDraw then
    begin
      if OrbitAutoTarget then
      begin
        FRotOffs := FPointXMat(FTool.Img.Center, FTool.GetDrawMatrix);
        FTool.Img.TranslateDrawMatrix(-FRotOffs.X, -FRotOffs.Y, -FRotOffs.Z);
      end
      else
        FRotOffs := FTool.PointToPosition(Center);
    end;
  end;
end;

procedure TsgDrawingNavigator.BroadcastClients(var Message);

  procedure DoBroadcast;
  var
    I: Integer;
  begin
    I := 0;
    while I < FClients.Count do
    try
      try
        TObject(FClients.List[I]).Dispatch(Message);
      finally
        if TMessage(Message).Result = 0 then
          Inc(I)
        else
          I := FClients.Count;
      end
    except
    end;
  end;

begin
  case TMessage(Message).Msg of
    WM_LBUTTONDOWN..WM_MOUSELAST, WM_SETFOCUS, WM_PAINT, CM_CHILDKEY, CM_MOUSEENTER,
    CM_MOUSELEAVE, WM_SCALECHANGING .. WM_PICTURECHANGE:
      DoBroadcast;
    WM_KEYFIRST..WM_KEYLAST:
      DoBroadcast;
    WM_MOUSEMOVE:
      if FAppActive then
        DoBroadcast;
    CM_CURSORCHANGED:
      DoBroadcast;
  end; { case TMessage(Message).Msg }
end;

procedure TsgDrawingNavigator.Clear;
begin
  FLoadedFromConverter := False;
end;

procedure TSGDrawingNavigator.CMColorChanged(var Message: TMessage);
begin
  inherited;
  DoOnColorChanged(Color);
end;

procedure TSGDrawingNavigator.CMCursorChanged(var Message: TMessage);
begin
  inherited;
  DoOnCursorChanged;
end;

function TsgDrawingNavigator.CoordinateConvertion(ACoordX, ACoordY: Integer;
  var APointInUCS: TFPoint): TFPoint;
begin
  if Assigned(FTool.Img) then
    Result := FSnapControl.GetMouseCoordUCS(ACoordX, ACoordY, APointInUCS).CADPointInUCS
  else
    Result := GetRealPoint(ACoordX, ACoordY, APointInUCS);
end;

function TsgDrawingNavigator.CreateBuffer: Boolean;
var
  vErr: Cardinal;
  vDC: HDC;
  vSize: TPoint;

  procedure DoDeleteBitmap(var ABufferBMP: HBITMAP);
  begin
    DeleteObject(ABufferBMP);
    ABufferBMP := 0;
  end;

begin
  if GetBitmapSize(FBufferBMP, vSize) and
     ((vSize.X <> ClientWidth) or (vSize.Y <> ClientHeight)) then
    DestroyBuffer;
  if FBufferDC = 0 then
  try
    vDC := GetDC(0);
    try
      FBufferBMP := CreateCompatibleBitmap(vDC, ClientWidth, ClientHeight);
      vErr := GetLastError;
    finally
      ReleaseDC(0, vDC);
    end;
    if FBufferBMP <> 0 then
    begin
      if vErr = 0 then
      begin
        FBufferDC := CreateCompatibleDC(0);
        if FBufferDC <> 0 then
        begin
          FOldGDIObj := SelectObject(FBufferDC, FBufferBMP);
          Perform(WM_ERASEBKGND, FBufferDC, FBufferDC)
        end
        else
          DoDeleteBitmap(FBufferBMP);
      end
      else
        DoDeleteBitmap(FBufferBMP);
    end;
  except
    FBufferDC := 0;
    FBufferBMP := 0;
    FOldGDIObj := 0;
  end;
  Result := (FBufferDC <> 0) and (FBufferBMP <> 0);
end;

function TsgDrawingNavigator.CreateHorScroll: Boolean;
begin
  Result := False;
  if FHorzScrollBar = nil then
  begin
    FHorzScrollBar := TScrollBar.Create(Self);
    FHorzScrollBar.Visible := False;
    FHorzScrollBar.Height := GetSystemMetrics(SM_CXHTHUMB);
    FHorzScrollBar.SmallChange := iScrollSmallChange;
    FHorzScrollBar.TabStop := False;
    FHorzScrollBar.OnScroll := HScrollEvent;
    Result := True;
  end;
end;

procedure TsgDrawingNavigator.CreateOrbit3D(AClass: TClass);
begin
  FOrbit3D.Free;
  FOrbit3D := TsgOrbit3DClass(AClass).Create(Self);
  Orbit3D.Align := alClient;
  Orbit3D.Visible := False;
  if Orbit3D is TsgNaviOrbit3D then
    TsgNaviOrbit3D(Orbit3D).Navigator := Self;
  TControlAccess(Orbit3D).OnMouseDown := OrbitMouseDown;
  TControlAccess(Orbit3D).OnMouseUp := OrbitMouseUp;
  TControlAccess(Orbit3D).OnMouseMove := OrbitMouseMove;
end;

function TsgDrawingNavigator.CreateVertScroll: Boolean;
begin
  Result := False;
  if FVertScrollBar = nil then
  begin
    FVertScrollBar := TScrollBar.Create(Self);
    FVertScrollBar.Visible := False;
    FVertScrollBar.Kind := sbVertical;
    FVertScrollBar.Width := GetSystemMetrics(SM_CXHTHUMB);
    FVertScrollBar.SmallChange := iScrollSmallChange;
    FVertScrollBar.TabStop := False;
    FVertScrollBar.OnScroll := VScrollEvent;
    Result := True;
  end;
end;

procedure TsgDrawingNavigator.DestroyBuffer;
begin
  SelectObject(FBufferDC, FOldGDIObj);
  DeleteObject(FBufferBMP);
  DeleteDC(FBufferDC);
  FBufferDC := 0;
  FOldGDIObj := 0;
  FBufferBMP := 0;
  FBufferSaved := False;
end;

procedure TsgDrawingNavigator.DoChangeScrolls;
var
  vGripH, vGripW: Integer;
  vHorzBarVisibleNew, vVertBarVisibleNew: Boolean;
  vRect: TRect;

  procedure MakeHorScroll(const AGripW, AGripH: Integer;
    const AVisible: Boolean);
  begin
    FHorzScrollBar.Left := 0;
    FHorzScrollBar.Width := ClientWidth - AGripW;
    FHorzScrollBar.Top := ClientHeight - AGripH;
    FHorzScrollBar.Visible := AVisible;
  end;

  procedure MakeVertScroll(const AGripW, AGripH: Integer;
    const AVisible: Boolean);
  begin
    FVertScrollBar.Left := ClientWidth - AGripW;
    FVertScrollBar.Top := 0;
    FVertScrollBar.Height := ClientHeight - AGripH;
    FVertScrollBar.Visible := AVisible;
  end;

begin
  if not (dnsSclollChanging in FState) then
  try
    Include(FState, dnsSclollChanging);
    UnionRect(vRect, ClientRect, GetPictureRect);
    vHorzBarVisibleNew := (((vRect.Right - vRect.Left) > ClientWidth)
      and not Empty) and FHorzScrollBarVisibility;
    vVertBarVisibleNew := (((vRect.Bottom - vRect.Top) > ClientHeight)
      and not Empty) and FVertScrollBarVisibility;
    vGripH := FHorzScrollBar.Height;
    vGripW := FVertScrollBar.Width;
    if vHorzBarVisibleNew xor vVertBarVisibleNew then
    begin
      if vHorzBarVisibleNew then
        vGripW := 0;
      if vVertBarVisibleNew then
        vGripH := 0;
    end
    else
      if not (vHorzBarVisibleNew and vVertBarVisibleNew) then
      begin
        vGripH := 0;
        vGripW := 0;
      end;
    MakeHorScroll(vGripW, vGripH, vHorzBarVisibleNew);
    MakeVertScroll(vGripW, vGripH, vVertBarVisibleNew);
    SetScrollsRange;
    SetScrollsPosition;
    if [dnsMoving, dnsSetView] * FState = [] then
      SaveCurrentView;
  finally
    Exclude(FState, dnsSclollChanging);
  end;
end;

{ TsgDrawingNavigator.DoPictureChange

  TPicture.OnChange handler. }

procedure TsgDrawingNavigator.DoPictureChange(Sender: TObject);
var
  vIsNewGraphic: Boolean;
  vCurrentLayout: TsgDXFLayoutAccess;

  procedure SetNewMaxDim;
  begin
    FMaxDim := iMaxNonWinNTDim;
    if (Win32Platform = VER_PLATFORM_WIN32_NT) and
       (Assigned(FTool.Img) or (FPicture.Graphic is TMetafile)) then
      FMaxDim := iMaxWinNTDim;
  end;

begin
  if not (dnsPictureChanging in FState)  then
  begin
    Include(FState, dnsPictureChanging);
    try
      Perform(WM_PICTURECHANGE, TsgNativeUInt(Self), TsgNativeInt(FTmpGraphic));
      if Assigned(FOnPictureChange) then
        FOnPictureChange(Self);
      PictureChanged := True;
      BeginUpdate;
      try
        vIsNewGraphic := FPicture.Graphic <> FTmpGraphic;
        // refresh FTool
        if vIsNewGraphic then
          FTool.UpdateImg
        else
          FTool.Update(Assigned(FTool.Img) and (FTmpLayout = FTool.Img.CurrentLayout));
        // refresh FSnapControl
        if vIsNewGraphic then
          FSnapControl.Update;
        if Assigned(FTool.Img) then
        begin
          if vIsNewGraphic then // new FPicture.Graphic inherits from TsgCADImage
          begin
            FOrbit3D.Visible := False;
            SetNewMaxDim;
            FTool.Img.OnAfterRotate := AfterRotate;
            FTool.Img.OnBeforeRotate := BeforeRotate;
            FTmpLayout := FTool.Img.CurrentLayout;
            FExtents := FTool.Img.Extents;
            FPictureRect2D := InitialPicRect2D(ClientWidth, ClientHeight);
            FTool.ApplyScale(FPictureRect2D);
            SetCommonUnitsWidth(0);
            PictureRectChanged := True;
          end;
          vCurrentLayout := TsgDXFLayoutAccess(FTool.Img.CurrentLayout);
          if FTmpLayout <> vCurrentLayout then
          begin
            if not vCurrentLayout.View.HasDrawMatrix then
            begin
              if FTool.IsNotCustomDraw then
              begin
                FTool.ApplyScale(InitialPicRect2D(ClientWidth, ClientHeight));
                FPictureRect2D := FTool.GetPictureRect2D;
              end;
              FitToSize;
            end
            else
            begin
              FBufferSaved := False;
              SetCommonUnitsWidth(0);
              FOrbit3D.Visible := False;
              FExtents := GetImageExtents;
              if FTool.IsNotCustomDraw then
              begin
                if vCurrentLayout.View.HasDrawMatrix then
                  FTool.SetDrawMatrix(vCurrentLayout.View.DrawMatrix)
                else
                  FTool.ApplyScale(InitialPicRect2D(ClientWidth, ClientHeight));
                FPictureRect2D := FTool.GetPictureRect2D;
              end
              else
              begin
                if vCurrentLayout.View.HasView then
                  PictureRect := vCurrentLayout.View.ActiveView
                else
                begin
                  if vCurrentLayout.View.HasBaseView then
                    PictureRect := vCurrentLayout.View.ActiveView
                  else
                    FitToSize;
                end;
              end;
            end;
            FTmpLayout := vCurrentLayout;
          end;
          if not IsEqualFRects(FExtents, FTool.Img.Extents) then
          begin
            CorrectPictureRectByExtents;
            FExtents := GetImageExtents;
          end;
        end
        else
        begin
          FTmpLayout := nil;
          FExtents := cnstBadRect;
          FOrbit3D.Visible := False;
          if vIsNewGraphic then // new Picture.Graphic
          begin
            SetCommonUnitsWidth(0);
            SetNewMaxDim;
            PictureRect2D := InitialPicRect2D(ClientWidth, ClientHeight);
          end;
        end;
        FTmpGraphic := FPicture.Graphic;
      finally
        EndUpdate;
      end;
    finally
      Exclude(FState, dnsPictureChanging);
    end;
  end;
end;

procedure TsgDrawingNavigator.DrawScrollBarsJoint(const ADC: HDC);
var
  vRect: TRect;
begin
  if FHorzScrollBar.Visible and FVertScrollBar.Visible then
  begin
    vRect.Right := ClientWidth;
    vRect.Bottom := ClientHeight;
    vRect.Left := vRect.Right - FVertScrollBar.Width;
    vRect.Top := vRect.Bottom - FHorzScrollBar.Height;
    DrawFrameControl(ADC, vRect, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
    ExcludeClipRect(ADC, vRect.Left, vRect.Top, vRect.Right, vRect.Bottom);
  end;
end;

procedure TsgDrawingNavigator.DrawThrowBuffer;
var
  vDC: HDC;
begin
  if CreateBuffer then
  begin
    if not FBufferSaved then
      Perform(WM_ERASEBKGND, FBufferDC, FBufferDC);
    Perform(WM_PAINT, FBufferDC, 0); // need to paint controls
    Canvas.Lock;
    try
      vDC := Canvas.Handle; // via GetDC
      try
        RenderBuffer(vDC);
        DoBroadcastPaintEvent(vDC);
      finally
        Canvas.Handle := 0;
      end;
    finally
      Canvas.Unlock;
    end;
  end
  else
    Perform(WM_PAINT, Canvas.Handle, 0);
end;

procedure TsgDrawingNavigator.DrawZoomingRect(ARect: TRect);
begin
  DrawDottedRect(Canvas.Handle, ARect);
end;

function TsgDrawingNavigator.GetActualGraphic: TGraphic;
begin
  Result := FTool.GetActualGraphic;
  if Result is TsgCADImage then
    TsgCADImage(Result).Printing := pntDirectColor;
  SetActualGraphic(Result);
end;

function TsgDrawingNavigator.GetOrbitAutoTarget: Boolean;
begin
  Result := TsgNaviOrbit3D(FOrbit3D).AutoTarget;
end;

function TsgDrawingNavigator.GetBufferDC: HDC;
begin
  CreateBuffer;
  Result := FBufferDC;
end;

function TsgDrawingNavigator.GetClipRectangle: Boolean;
begin
  Result := FRectangle.Active;
end;

function TsgDrawingNavigator.GetClipRectCoords: TRect;
begin
  Result := FRectangle.Rect;
end;

function TsgDrawingNavigator.GetImageBox: TFRect;
begin
  if Assigned(FTool.Img) and Assigned(FTool.Img.CurrentLayout) then
    Result := FTool.Img.CurrentLayout.Box
  else
    if Assigned(FPicture.Graphic) then
      Result := MakeFRect(0, FPicture.Height, 0, FPicture.Width, 0, 0)
    else
      Result := cnstBadRect;
  PerformBroadcastEvents(WM_GETIMAGEBOX, WPARAM(@Result), LPARAM(FPicture.Graphic));
end;

function TsgDrawingNavigator.GetImageExtents: TFRect;
begin
  if Assigned(FTool.Img) then
    Result := FTool.Img.Extents
  else
    if Assigned(FPicture.Graphic) then
      Result := MakeFRect(0, FPicture.Height, 0, FPicture.Width, 0, 0)
    else
      Result := cnstBadRect;
  PerformBroadcastEvents(WM_GETIMAGEEXTENTS, WPARAM(@Result), LPARAM(FPicture.Graphic));
end;

function TsgDrawingNavigator.GetImageView(const ALayoutIndex: Integer = -1): TsgView;
var
  vLayout: TsgDXFLayout;
begin
  Result := nil;
  if Assigned(FTool.Img) then
  begin
    if ALayoutIndex < 0 then
      vLayout := FTool.Img.CurrentLayout
    else
      vLayout := FTool.Img.Layouts[ALayoutIndex];
    Result := TsgDXFLayoutAccess(vLayout).View;
  end;
end;

function TsgDrawingNavigator.GetLoadedFromConverter: Boolean;
begin
  Result := FLoadedFromConverter;
end;

function TsgDrawingNavigator.GetMouseMovePrevPoint: TPoint;
begin
  Result := FMouseMovePrevPoint;
end;

function TsgDrawingNavigator.GetOrbit3D: TsgCustomOrbit3D;
begin
  Result := FOrbit3D;
end;

function TsgDrawingNavigator.GetOrbit3DClass: TClass;
begin
  Result := TsgNaviOrbit3D;
end;

function TsgDrawingNavigator.GetPictureHeight: Integer;
begin
  Result := Round(PictureRectHeight);
end;

function TsgDrawingNavigator.GetPictureRect: TRect;
begin
  Result := RoundRect2D(FPictureRect2D);
end;

function TsgDrawingNavigator.GetPictureRectCenter: TPoint;
begin
  Result := RoundPoint2D(GetPictureRectCenter2D);
end;

function TsgDrawingNavigator.GetPictureRectCenter2D: TF2DPoint;
begin
  Result := GetRectCenter(PictureRect2D);
end;

function TsgDrawingNavigator.GetPictureRectHeight: Double;
begin
  Result := FPictureRect2D.Bottom - FPictureRect2D.Top;
end;

function TsgDrawingNavigator.GetPictureRectWidth: Double;
begin
  Result := FPictureRect2D.Right - FPictureRect2D.Left;
end;

function TsgDrawingNavigator.GetPictureWidth: Integer;
begin
  Result := Round(PictureRectWidth);
end;

procedure TsgDrawingNavigator.GetPointToImageSizeRatio(ACoordX,
  ACoordY: Double; var AXScaled, AYScaled: Extended);
var
  vExtents: TFRect;
  R: TF2DRect;
  vSx, vSy: Double;
begin
  R := PictureRect2D;
  SwapDoubles(R.Top, R.Bottom);
  if IsRectEmptyF2D(R) or (R.Right = R.Left) or (R.Top = R.Bottom) then
  begin
    AXScaled := 0;
    AYScaled := 0;
  end
  else
  begin
    AXScaled := (ACoordX - R.Left) / (R.Right - R.Left);
    AYScaled := 1 - (ACoordY - R.Bottom) / (R.Top - R.Bottom);
    if FTool.IsNotCustomDraw and not FTool.Img.IsWithoutBorder then
    begin
      vSx := FTool.Img.BorderSize;
      vSy := vSx;
      case FTool.Img.BorderType of
        btRatio:;
        btGlobal:
          begin
            vExtents := GetImageExtents;
            vSx := vSx / (vExtents.Right - vExtents.Left - 2 * vSx);
            vSy := vSy / (vExtents.Top - vExtents.Bottom - 2 * vSy);
          end;
      end;
      AXScaled := (AXScaled + vSx) / (1 + 2 * vSx);
      AYScaled := (AYScaled + vSy) / (1 + 2 * vSy);
    end;
  end;
end;

function TsgDrawingNavigator.GetPictureRect2D: TF2DRect;
begin
  Result := FPictureRect2D;
end;

function TsgDrawingNavigator.GetRectangle: TsgRectangle;
begin
  Result := FRectangle;
end;

function TsgDrawingNavigator.GetScale: Double;
begin
  Result := 1.0;
  if not Empty and (FWidthInPixels <> 0) then
    Result := (FPictureRect2D.Right - FPictureRect2D.Left) / FWidthInPixels;
end;

function TsgDrawingNavigator.GetSimilarRect(const AExtents, AViewFRect: TFRect;
  var APictureRect: TF2DRect): TF2DRect;
var
  vAbsW, vAbsH, vViewW, vViewH, vPicRectW, vPicRectH: Double;
  vScaleW, vScaleH, vScale: Double;
  vW, vH, vOffsX, vOffsY: Double;
begin
  FillChar(Result, SizeOf(Result), 0);
  FillChar(APictureRect, SizeOf(APictureRect), 0);
  vAbsW := AExtents.Right - AExtents.Left;
  vAbsH := AExtents.Top - AExtents.Bottom;
  vViewW := AViewFRect.Right - AViewFRect.Left;
  vViewH := AViewFRect.Top - AViewFRect.Bottom;
  if (Abs(vAbsW) > fAccuracy) and (Abs(vAbsH) > fAccuracy) and
     (Abs(vViewW) > fAccuracy) and (Abs(vViewH) > fAccuracy) then
  begin
    Result := Rect2DFromRect(ClientRect);
    if (Abs(Result.Right - Result.Left) >= 1.0) and
       (Abs(Result.Bottom - Result.Top) >= 1.0) then
    begin
      vW := Result.Right - Result.Left;
      vH := Result.Bottom - Result.Top;
      vScaleW := vW / vViewW;
      vScaleH := vH / vViewH;
      vScale := Min(vScaleW, vScaleH);
      vPicRectW := vAbsW * vScale;
      vPicRectH := vAbsH * vScale;
      if vPicRectW > FMaxDim then
        vPicRectW := FMaxDim;
      if vPicRectH > FMaxDim then
        vPicRectH := FMaxDim;
      APictureRect.Right := vPicRectW;
      APictureRect.Bottom := vPicRectH;
      if APictureRect.Bottom - APictureRect.Top <= iMinPicSize then
      begin
        vScale := Max(vScaleW, vScaleH);
        vPicRectW := vAbsW * vScale;
        vPicRectH := vAbsH * vScale;
        if vPicRectW > FMaxDim then
          vPicRectW := FMaxDim;
        if vPicRectH > FMaxDim then
          vPicRectH := FMaxDim;
        APictureRect.Right := vPicRectW;
        APictureRect.Bottom := vPicRectH;
      end;
      vPicRectW := (AExtents.Left - AViewFRect.Left) * vScale;
      vPicRectH := (AExtents.Top - AViewFRect.Top) * vScale;
      if Abs(vPicRectW) > FMaxDim then
        vOffsX := FMaxDim
      else
        vOffsX := Round(vPicRectW);
      if Abs(vPicRectH) > FMaxDim then
      begin
        vOffsY := 0;
        if vPicRectH < 0 then
          vOffsY := FMaxDim
        else
          if vPicRectH > 0 then
            vOffsY := -FMaxDim
      end
      else
        vOffsY := -vPicRectH;
      OffsetRect2D(APictureRect, vOffsX, vOffsY);
      if (APictureRect.Left > Result.Left) and (APictureRect.Top > Result.Top)
        and (APictureRect.Right < Result.Right)
        and (APictureRect.Bottom < Result.Bottom) then
        Result := APictureRect;
    end;
  end;
end;

function TsgDrawingNavigator.GetSizesAsString: string;
begin
  Result := '';
end;

function TsgDrawingNavigator.GetSysMenuIconsVisible: Boolean;
begin
  Result := Assigned(FSysMenuIcons);
end;

function TsgDrawingNavigator.GetZoomInEnable: Boolean;
begin
  Result :=
    not((Abs(FPictureRect2D.Right - FPictureRect2D.Left) * FZoomInChange >= FMaxDim)
    or(Abs(FPictureRect2D.Bottom - FPictureRect2D.Top) * FZoomInChange >= FMaxDim));
end;

function TsgDrawingNavigator.GetZoomOutEnable: Boolean;
begin
  Result := not((Abs(FPictureRect2D.Right - FPictureRect2D.Left) <= iMinPicSize)
    or(Abs(FPictureRect2D.Bottom - FPictureRect2D.Top) <= iMinPicSize));
end;

procedure TsgDrawingNavigator.HScrollEvent(Sender: TObject;
  ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
  if not (dnsSclollChanging in FState) and not (dnsMoving in FState) then
    case ScrollCode of
      scLineUp: MovePictureRect(FHorzScrollBar.SmallChange, 0);
      scLineDown: MovePictureRect(-FHorzScrollBar.SmallChange, 0);
      scEndScroll:; // do nothing
    else
      MovePictureRect2D(-FHorzScrollBar.Position - FPictureRect2D.Left, 0);
    end;
end;

procedure TsgDrawingNavigator.ReCreateBuffer;
begin
  if FBufferDC <> 0 then
    DestroyBuffer;
  CreateBuffer;
end;

procedure TsgDrawingNavigator.RectZoomingOperation(ACoordX, ACoordY: Integer;
  AErase: Boolean);
var
  vPoint: TPoint;
begin
  if FRectZoomingProcess then
  begin
    if AErase then
      DrawZoomingRect(FZoomingRect);
    vPoint := Point(ACoordX, ACoordY);
    if PtInRect(ViewRect, vPoint) then
      FZoomingRect.BottomRight := vPoint;
    DrawZoomingRect(FZoomingRect);
  end;
end;

procedure TsgDrawingNavigator.RenderBuffer(const ADC: HDC);
begin
  CopyDC(FBufferDC, ADC, ClientWidth, ClientHeight);
end;

procedure TsgDrawingNavigator.Repaint;
begin
  FPictureChanged := True;
  inherited Repaint;
end;

procedure TsgDrawingNavigator.SaveBuffer(const ADC: HDC);
begin
  CopyDC(ADC, FBufferDC, ClientWidth, ClientHeight);
  FBufferSaved := True;
end;

procedure TsgDrawingNavigator.SetActualGraphic(AGraphic: TGraphic);
begin
  FActualGraphic.Free;
  FActualGraphic := AGraphic;
  DoGetActualGraphic;
end;

procedure TsgDrawingNavigator.SetAutoFocus(const AValue: Boolean);
begin
  FAutoFocus := AValue;
end;

procedure TsgDrawingNavigator.SetBlockFocusUpdate(const Value: Boolean);
begin
  FBlockFocusUpdate := Value;
end;

procedure TsgDrawingNavigator.SetOrbitAutoTarget(const AValue: Boolean);
begin
  TsgNaviOrbit3D(FOrbit3D).AutoTarget := AValue;
end;

procedure TsgDrawingNavigator.SetClipRectangle(const AValue: Boolean);
begin
  if AValue then
    FRectangle.Active := True
  else
    if FRectangle.Active then
      FRectangle.Active := False;
  if not FRectangle.Active then
  begin
    Perform(WM_CANCELMODE, 0, 0);
    Cursor := TCursor(cnstCursorCross);
  end
  else
  begin
    FRectZooming := False;
    FRectZoomingProcess := False;
  end;
end;

procedure TsgDrawingNavigator.SetClipRectCoords(const AValue: TRect);
begin
  FRectangle.Rect := AValue;
end;

procedure TsgDrawingNavigator.SetClipRectPopupMenu(
  const AValue: TPopupMenu);
begin
  FClipRectPopupMenu := AValue;
  if AValue <> nil then
  begin
    {$IFDEF SGDEL_4}
    AValue.ParentBiDiModeChanged(Self);
    {$ENDIF}
    AValue.FreeNotification(Self);
  end;
end;

procedure TsgDrawingNavigator.SetCommonUnitsWidth(ADC: HDC);
var
  vExtents: TFRect;
  vUnits: Integer;
  vUnitsUserFactor, vAltUnitsUserFactor, vmmPerPixelX: Double;
  vUnitsUserName, vAltUnitsUserName: string;
begin
  if Assigned(FTool.Img) then
  begin
    vExtents := GetImageExtents;
    FWidthInPixels := FTool.ConvertUnits(vExtents.BottomRight).X -
      FTool.ConvertUnits(vExtents.TopLeft).X;
    vUnits := Integer(GetDistanceUnits(FTool.Img, FTool.Img.CurrentLayout,
      vUnitsUserName, vUnitsUserFactor,
      vAltUnitsUserName, vAltUnitsUserFactor));
    if ADC = 0 then
      vmmPerPixelX := GetScreenMMPerPixel
    else
      vmmPerPixelX := GetDeviceCaps(ADC, HORZSIZE) / GetDeviceCaps(ADC, HORZRES);
    case vUnits of
      1: FWidthInPixels := FWidthInPixels * mmPerInch / vmmPerPixelX; // duInch
      2: FWidthInPixels := FWidthInPixels / vmmPerPixelX;             // duMM
    end;
  end
  else
    if not Empty then
      FWidthInPixels := FPicture.Width
    else
      FWidthInPixels := 0;
end;

procedure TsgDrawingNavigator.SetDrawOnEveryMouseWheel(const AValue: Boolean);
begin
  if FDrawOnEveryMouseWheel xor AValue then
    if FUpdateCount = 0 then
      FDrawOnEveryMouseWheel := AValue;
end;

procedure TsgDrawingNavigator.SetEnterClipRectEvent(const Value: TNotifyEvent);
begin
  FRectangle.OnEnterRect := Value;
end;

procedure TsgDrawingNavigator.SetHorzScrollBarVisibility(
  const AValue: Boolean);
begin
  FHorzScrollBarVisibility := AValue;
  DoChangeScrolls;
end;

procedure TsgDrawingNavigator.SetLinearDimensionsScale(const AFactor: Double);
begin
  if AFactor > 0 then
    FLinearDimensionsScaleFactor := AFactor;
end;

procedure TsgDrawingNavigator.SetOnGetImageExtents(
  const AValue: TGetImageExtents);
begin
  FOnGetImageExtents := AValue;
end;

procedure TsgDrawingNavigator.SetPicture(const AValue: TPicture);
begin
  FPicture.Assign(AValue);
end;

procedure TsgDrawingNavigator.SetPictureChanged(const AValue: Boolean);
begin
  FPictureChanged := AValue;
end;

procedure TsgDrawingNavigator.SetPictureRect(const ARect: TRect);
begin
  SetPictureRect2D(Rect2DFromRect(ARect));
end;

procedure TsgDrawingNavigator.SetPictureRectChanged(const AValue: Boolean);
begin
  FPictureRectChanged := AValue;
  if FPictureRectChanged then
    Perform(WM_ERASEBKGND, FBufferDC, FBufferDC);
end;

procedure TsgDrawingNavigator.SetPictureRect2D(const AValue: TF2DRect);
var
  vRect: TF2DRect;
  vDx, vDy: Double;

  procedure PrepareRect(var APictureRect: TF2DRect);
  var
    vWidth, vHeight: Double;
    vDstSize: Double;
    vExtents: TFRect;
    vAbsWidth, vAbsHeight, vSize: Double;
    vFlag: Boolean;
  begin
    if not Stretch then
    begin
      if not FTool.IsNotCustomDraw then
      begin
        // Set proportionality
        if FState * [dnsMoving, dnsLoading] = [] then
        begin
          vWidth := APictureRect.Right - APictureRect.Left;
          vHeight := APictureRect.Bottom - APictureRect.Top;
          vExtents := GetImageExtents;
          vAbsWidth := vExtents.Right - vExtents.Left;
          vAbsHeight := vExtents.Top - vExtents.Bottom;
          if sgIsZero(vAbsWidth) then
            vAbsWidth := 0.0;
          if sgIsZero(vAbsHeight) then
            vAbsHeight := 0.0;
          vFlag := vAbsHeight = 0.0;
          vFlag := (vAbsWidth = 0.0) or vFlag;
          if not vFlag then
          begin
            if vHeight = 0 then
            begin
              vSize := vWidth * vAbsHeight / vAbsWidth;
              if vSize > FMaxDim then
              begin
                vSize := vHeight * vAbsWidth / vAbsHeight;
                if vSize > FMaxDim then
                  vDstSize := FMaxDim
                else
                  if vSize < iMinPicSize then
                    vDstSize := iMinPicSize
                  else
                    vDstSize := vSize;
                APictureRect.Right := APictureRect.Left + vDstSize;
              end
              else
              begin
                if vSize < iMinPicSize then
                  vDstSize := iMinPicSize
                else
                  vDstSize := vSize;
                APictureRect.Bottom := APictureRect.Top + vDstSize;
              end;
            end
            else
            begin
              vSize := vHeight * vAbsWidth / vAbsHeight;
              if vSize > FMaxDim then
              begin
                vSize := vWidth * vAbsHeight / vAbsWidth;
                if vSize > FMaxDim then
                  vDstSize := FMaxDim
                else
                  if vSize < iMinPicSize then
                    vDstSize := iMinPicSize
                  else
                    vDstSize := vSize;
                APictureRect.Bottom := APictureRect.Top + vDstSize;
              end
              else
              begin
                if vSize < iMinPicSize then
                  vDstSize := iMinPicSize
                else
                  vDstSize := vSize;
                APictureRect.Right := APictureRect.Left + vDstSize;
              end;
            end;
          end;
        end;
        // Centering
        if FCenterAlignment then
        begin
          vWidth := APictureRect.Right - APictureRect.Left;
          if vWidth <= ViewRectWidth then
            OffsetRect2D(APictureRect, (ViewRectWidth - vWidth) / 2
              - APictureRect.Left, 0);
          vHeight := APictureRect.Bottom - APictureRect.Top;
          if vHeight <= ViewRectHeight then
            OffsetRect2D(APictureRect, 0, (ViewRectHeight - vHeight) / 2
              - APictureRect.Top);
        end;
      end;
    end
    else
      APictureRect := Rect2DFromRect(ViewRect);
  end;

begin
  FPictureRectChanged := False;
  if (AValue.Right - AValue.Left < FMaxDim) and
     (AValue.Bottom - AValue.Top < FMaxDim) then
  begin
    vRect := AValue;
    CorrectRect2D(vRect);
    PrepareRect(vRect);
    vDx := vRect.Right - vRect.Left;
    vDy := vRect.Bottom - vRect.Top;
    if (Abs((FPictureRect2D.Right - FPictureRect2D.Left) - vDx) >= 1.0) or
       (Abs((FPictureRect2D.Bottom - FPictureRect2D.Top) - vDy) >= 1.0) then
    begin
      FPictureRect2D := vRect;
      PictureRectChanged := True;
      DoChangeScrolls;
      DoChangeScale;
    end
    else
      if (Abs(vRect.Left - FPictureRect2D.Left) >= 1.0) or
         (Abs(vRect.Top - FPictureRect2D.Top) >= 1.0) then
      begin
        FPictureRect2D := vRect;
        PictureRectChanged := True;
        DoChangeScrolls;
        DoPictureMove;
      end;
  end;
end;

procedure TsgDrawingNavigator.SetRectZooming(const AValue: Boolean);
begin
  FRectZooming := AValue;
  if FRectZooming then
    SetClipRectangle(False);
end;

procedure TsgDrawingNavigator.SetScale(const AValue: Double);
begin
  AlterScale(AValue, True, Center);
end;

procedure TsgDrawingNavigator.SetScrollsPosition;
var
  vRect: TRect;
begin
  UnionRect(vRect, GetPictureRect, ClientRect);
  if FHorzScrollBar <> nil then
    FHorzScrollBar.Position := -vRect.Left;
  if FVertScrollBar <> nil then
    FVertScrollBar.Position := -vRect.Top;
end;

procedure TsgDrawingNavigator.SetScrollsRange;
const
  iMargin = 48;
var
  I: Integer;
  vLW: Cardinal;
  vRect: TRect;

  procedure CheckRange(var ARange: Integer);
  begin
    if ARange > MaxWord shr 1 then
      ARange := MaxWord shr 1;
    if ARange < iMargin then
      ARange := iMargin;
  end;

  procedure CheckMax(var ASize: Cardinal);
  begin
    if ASize > Cardinal(FMaxDim) then
      ASize := FMaxDim;
  end;

  function PicRectHorMinPos(const ARect: TRect): Integer;
  var
    vD: Double;
  begin
    vD := ClientWidth - (ARect.Right - ARect.Left);//PicRectWidth;
    if Abs(vD) > FMaxDim then
      vD := -FMaxDim;
    Result := Round(vD);
  end;

  function PicRectVertMinPos(const ARect: TRect): Integer;
  var
    vD: Double;
  begin
    vD := ClientHeight - (ARect.Bottom - ARect.Top);//PicRectHeight;
    if Abs(vD) > FMaxDim then
      vD := -FMaxDim;
    Result := Round(vD);
  end;

begin
  UnionRect(vRect, ClientRect, GetPictureRect);
  if FHorzScrollBar <> nil then
  begin
    FHorzScrollBar.Min := 0;
    vLW := -PicRectHorMinPos(vRect);
    CheckMax(vLW);
    FHorzScrollBar.Max := vLW;
    I := ViewRect.Right - ViewRect.Left - iMargin;
    CheckRange(I);
    FHorzScrollBar.LargeChange := I;
    FHorzScrollBar.SmallChange := I div iMargin;
  end;
  if FVertScrollBar <> nil then
  begin
    FVertScrollBar.Min := 0;
    vLW := -PicRectVertMinPos(vRect);
    CheckMax(vLW);
    FVertScrollBar.Max := vLW;
    I := ViewRect.Bottom - ViewRect.Top - iMargin;
    CheckRange(I);
    FVertScrollBar.LargeChange := I;
    FVertScrollBar.SmallChange := I div iMargin;
  end;
end;

procedure TsgDrawingNavigator.SetStretch(const AValue: Boolean);
begin
  if FStretch <> AValue then
  begin
    FStretch := AValue;
    FitToSize;
  end
end;

procedure TsgDrawingNavigator.SetSysMenuIconsVisible(const AValue: Boolean);
begin
  if SysMenuIconsVisible <> AValue then
    if AValue then
    begin
      FSysMenuIcons := TsgSysMenuIcons.Create(Self);
      if Assigned(FOnGetSysMenuIcons) then
        FOnGetSysMenuIcons(Self);
      Self.Repaint;
    end
    else
      FreeAndNil(FSysMenuIcons);
end;

procedure TsgDrawingNavigator.SetVertScrollBarVisibility(const AValue: Boolean);
begin
  FVertScrollBarVisibility := AValue;
  DoChangeScrolls;
end;

procedure TsgDrawingNavigator.SetVisibleArea;
var
  vPoint: TPoint;
begin
  if Assigned(FTool.Img) and not FLockChangeVisibleArea then
  begin
    vPoint := Point(ViewRectWidth, ViewRectHeight);
    if (vPoint.X < 0) or (vPoint.Y < 0) then
      vPoint := Point(1, 1);
    FTool.Img.SetVisibleArea(vPoint);
  end;
end;

procedure TsgDrawingNavigator.StartMoving(const ACoordX, ACoordY: Integer);
begin
  if FMouseDragging then
  begin
    FMoveByMouse := True;
    FMouseMovePrevPoint := Point(ACoordX, ACoordY);
    Cursor := TCursor(cnstCursorHand);
  end;
end;

procedure TsgDrawingNavigator.StopMouseOperations(var Message: TMessage);
begin
  DoOnStopMouseOperations;
end;

procedure TsgDrawingNavigator.StopMoveByMouse;
begin
  if FMoveByMouse then
  begin
    FMoveByMouse := False;
    MouseCapture := False;
    if Assigned(FOrbit3D) and FOrbit3D.Visible then
      Cursor := FOrbit3D.Cursor
    else
      Cursor := TCursor(cnstCursorCross);
  end;
end;

function TsgDrawingNavigator.TrySetPictureRect2D(
  const ANewRect: TF2DRect): Boolean;
var
  vTmpRect: TF2DRect;
begin
  vTmpRect := FPictureRect2D;
  PictureRect2D := ANewRect;
  Result := not IsEqualF2DRects(vTmpRect, FPictureRect2D);
end;

procedure TsgDrawingNavigator.UnlockChangeVisibleArea;
begin
  FLockChangeVisibleArea := False;
end;

procedure TsgDrawingNavigator.UpdateSnap;
begin
  FreeAndNil(FSnapControl);
  CreateSnap;
  FSnapControl.Update;
end;

function TsgDrawingNavigator.ViewRectHeight: Integer;
var
  vViewRect: TRect;
begin
  vViewRect := ViewRect;
  Result := vViewRect.Bottom - vViewRect.Top;
end;

function TsgDrawingNavigator.ViewRectWidth: Integer;
var
  vViewRect: TRect;
begin
  vViewRect := ViewRect;
  Result := vViewRect.Right - vViewRect.Left;
end;

procedure TsgDrawingNavigator.VScrollEvent(Sender: TObject;
  ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
  if not (dnsSclollChanging in FState) and not (dnsMoving in FState) then
    case ScrollCode of
      scLineUp: MovePictureRect(0, FVertScrollBar.SmallChange);
      scLineDown: MovePictureRect(0, -FVertScrollBar.SmallChange);
      scEndScroll:; // do nothing
    else
      MovePictureRect2D(0, -FVertScrollBar.Position - FPictureRect2D.Top);
    end;
end;

function TsgDrawingNavigator.ApplicationHook(var Message: TMessage): Boolean;
begin
  if Message.Msg = WM_DISPLAYCHANGE then
    ReCreateBuffer
  else
  if Message.Msg = WM_ACTIVATEAPP then
    if TWMActivateApp(Message).Active then
      FAppActive := True
    else
      FAppActive := False;
  Result := False;
end;

function TsgDrawingNavigator.ConvertUnits(APoint: TFPoint;
  var AUnits: string): TFPoint;
var
  vFactor: Double;
begin
  FTool.CoordinateParameters(AUnits, vFactor);
  APoint := FTool.ConvertUnits(APoint);
  Result := MakeFPoint(APoint.X * vFactor, APoint.Y * vFactor,
    APoint.Z * vFactor);
end;

procedure TsgDrawingNavigator.CreateHandle;
begin
  inherited CreateHandle;
  ReCreateBuffer;
end;

procedure TsgDrawingNavigator.DoBroadcastPaintEvent(ADC: HDC);
begin
  if FNeedBroadcastPaintEvent then
  begin
    FNeedBroadcastPaintEvent := False;
    PerformBroadcastEvents(WM_PAINT, ADC, 0);
  end;
end;

procedure TsgDrawingNavigator.DoChangeScale;
begin
  if FUpdateCount = 0 then
  begin
    if FDoubleBuffered then
      DrawThrowBuffer
    else
      Perform(WM_PAINT, Canvas.Handle, 0);
  end;
  FExtents := GetImageExtents;
  Perform(WM_SCALECHANGING, 0, 0);
  if Assigned(FOnChangeScale) then
    FOnChangeScale(Self);
end;

function TsgDrawingNavigator.DoMouseWheel(Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint): Boolean;
begin
  if not FDrawOnEveryMouseWheel then
    BeginUpdate;
  try
    Result := inherited DoMouseWheel(Shift, WheelDelta, MousePos);
  finally
    if not FDrawOnEveryMouseWheel then
      EndUpdate;
  end;
end;

function TsgDrawingNavigator.DoMouseWheelDown(Shift: TShiftState;
  MousePos: TPoint): Boolean;
begin
  Result := inherited DoMouseWheelDown(Shift, MousePos);
  if (not Result) and not (dnsZooming in FState) then
  begin
    if (Shift = [ssShift]) or ((Shift = []) and FScalingOnMouseWheel) or
       (Shift = [ssLeft]) then
      Result := AlterScale(FZoomOutChange, False, ScreenToClient(MousePos))
    else
      if (Shift = [ssCtrl]) then
        Result := AlterScale(0.99, False, ScreenToClient(MousePos))
  end;
end;

function TsgDrawingNavigator.DoMouseWheelUp(Shift: TShiftState;
  MousePos: TPoint): Boolean;
begin
  Result := inherited DoMouseWheelUp(Shift, MousePos);
  if (not Result) and (not (dnsZooming in FState)) then
  begin
    if (Shift = [ssShift]) or ((Shift = []) and FScalingOnMouseWheel) or
       (Shift = [ssLeft]) then
      Result := AlterScale(FZoomInChange, False, ScreenToClient(MousePos))
    else
      if (Shift = [ssCtrl]) then
        Result := AlterScale(1.01, False, ScreenToClient(MousePos))
  end;
end;

procedure TsgDrawingNavigator.DoOnColorChanged(const AColor: TColor);
begin

end;

procedure TsgDrawingNavigator.DoOnCursorChanged;
begin
  if HandleAllocated then
    Perform(WM_SETCURSOR, WindowHandle, HTCLIENT);
end;

procedure TsgDrawingNavigator.DoOnStopMouseOperations;
begin
  FMouseMoveZoomingProcess := False;
  FMoveByMouse := False;
end;

procedure TsgDrawingNavigator.DoPictureMove;
begin
  if FUpdateCount = 0 then
  begin
    if dnsMoving in FState then
      DrawThrowBuffer
    else Invalidate;
  end;
  FExtents := GetImageExtents;
  Perform(WM_PICTUREMOVE, 0, 0);
  if Assigned(FOnPictureMove) then
    FOnPictureMove(Self);
end;

procedure TsgDrawingNavigator.DoFitToSize;
var
  I: Integer;
  vEvent: TNotifyEvent;
begin
  FitToSize;
  if Assigned(FOnFitToSize) then
  begin
    for I := 0 to FOnFitToSize.Count - 1 do
    begin
      vEvent := sgFunction.GetNotifyEventFromList(FOnFitToSize, I);
      vEvent(Self);
    end;
  end;
end;

procedure TsgDrawingNavigator.DoGetActualGraphic;
begin
  if Assigned(FOnGetActualGraphic) then
    FOnGetActualGraphic(Self);
end;

function TsgDrawingNavigator.GetPoint(APoint: TFPoint): TPoint;
type
  PDoubleBytes = ^TDoubleBytes;
  TDoubleBytes = array [0 .. 2 * SizeOf(Double) - 1] of Byte;
var
  vPVal: PDoubleBytes;
begin
  vPVal := @APoint;
  if Assigned(FTool.Img) and Assigned(FTmpGraphic) then
    Result := FTool.Img.GetPoint(APoint)
  else
    try
      APoint.X := 1/0;
      if not IsNan(vPVal^[0]) and not IsNan(vPVal^[SizeOf(Double)])
        and (Abs(APoint.X) < FMaxDim) and (Abs(APoint.Y) < FMaxDim) then
        Result := Point(Round(APoint.X), Round(APoint.Y))
      else
        Result := Point(0, 0);
    except
      Result := Point(0, 0);
    end;
end;

function TsgDrawingNavigator.GetRenderBuffer: Boolean;
begin
  Result := FBufferSaved and not FPictureRectChanged and not FPictureChanged;
end;

function TsgDrawingNavigator.InitializeIterateParams: PsgCADIterate;
begin
  FillChar(FIterateParam, SizeOf(FIterateParam), 0);
  FIterateParam.Matrix := cnstIdentityMat;
  Result := @FIterateParam;
end;

function TsgDrawingNavigator.InitialPicRect(
  const AClientWidth, AClientHeight: Integer): TRect;
begin
  Result := RoundRect2D(InitialPicRect2D(AClientWidth, AClientHeight));
end;

function TsgDrawingNavigator.InitialPicRect2D(const AClientWidth,
  AClientHeight: Double): TF2DRect;
var
  vBox, vExtents: TFRect;
  vScale: Double;
  vAbsW, vAbsH: Double;
begin
  Result := MakeF2DRect(0, 0, ClientWidth, ClientHeight);
  vBox := GetImageBox;
  if not IsBadRect(vBox) then
  begin
    vExtents := GetRealBox(vBox, FTool.GetRealImageMatrix);
    vAbsW := vExtents.Right - vExtents.Left;
    vAbsH := vExtents.Top - vExtents.Bottom;
    if (vAbsW <> 0) and (vAbsH <> 0) then
    begin
      vScale := (AClientWidth - 1) / vAbsW;
      if vAbsH * vScale > AClientHeight - 1 then
        vScale := (AClientHeight - 1) / vAbsH;
      vAbsW := vScale * vAbsW;
      vAbsH := vScale * vAbsH;
      Result.Left := 0.5 * (AClientWidth - vAbsW);
      Result.Top := 0.5 * (AClientHeight - vAbsH);
      Result.Right := Result.Left + vAbsW;
      Result.Bottom := Result.Top + vAbsH;
    end;
  end;
end;

function TsgDrawingNavigator.IsCanBeEdited: Boolean;
begin
  Result := True;
end;

function TsgDrawingNavigator.IsCanShowCoords: Boolean;
begin
  Result := True;
end;

function TsgDrawingNavigator.IsCanShowSizes: Boolean;
begin
  Result := True;
end;

function TsgDrawingNavigator.IsUseGraphic: Boolean;
begin
  Result := True;
end;

function TsgDrawingNavigator.IsClipRectActive: Boolean;
begin
  Result := FRectangle.Active and (not IsRectEmpty(FRectangle.Rect));
end;

function TsgDrawingNavigator.IsMouseInSysButtons: Boolean;
var
  vMousePos: TPoint;
begin
  Result := False;
  if SysMenuIconsVisible and GetCursorPos(vMousePos) then
    Result := SysMenuIcons.PointInIcons(Self.ScreenToClient(vMousePos));
end;

procedure TsgDrawingNavigator.MouseDown(Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);

  function RectangleOperation(const ACoordX, ACoordY: Integer): Boolean;
  var
    vPoint: TPoint;
  begin
    Result := False;
    if IsClipRectActive then
      if IsPointInRectI(FRectangle.Rect, FMouseDownPt) or
         TsgRectangleAccess(FRectangle).Capture then
        if (Button = mbRight) and (ClipRectPopupMenu <> nil) then
        begin
          vPoint := ClientToScreen(Point(X, Y));
          ClipRectPopupMenu.Popup(vPoint.X, vPoint.Y);
          StopMoveByMouse;
          Result := True;
        end;
  end;

begin
  inherited MouseDown(Button, Shift, X, Y);
  FMouseDownPt := Point(X, Y);
  if not RectangleOperation(X, Y) then
  begin
    case Button of
      mbLeft:
        if not FOrbit3D.Visible then
          if FRectZooming then
          begin
            if FRectZoomingProcess then
              RectZoomingOperation(X, Y, True)
            else
            begin
              FRectZoomingProcess := True;
              FZoomingRect := Rect(X, Y, X, Y);
            end;
          end
          else
            if FMouseScaling then
              FMouseMoveZoomingProcess := True;
      mbMiddle:
        begin
          if (Shift * [ssMiddle, ssDouble] = [ssMiddle, ssDouble]) then
            DoFitToSize
          else
          begin
            if FRectangle.Active then
              MouseDown(mbLeft, [], X, Y);
            StartMoving(X, Y);
          end;
        end;
      mbRight:
        begin
          if FRectangle.Active then
            MouseDown(mbLeft, [], X, Y);
          StartMoving(X, Y);
        end;
    end;
  end;
end;

procedure TsgDrawingNavigator.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited MouseMove(Shift, X, Y);
  if FAppActive then
  begin
    if FMoveByMouse and (Shift * [ssRight, ssMiddle] = []) then
      StopMoveByMouse;
    if FMoveByMouse and (Shift * [ssRight, ssMiddle] <> []) then
    begin
      MouseCapture := True;
      Cursor := TCursor(cnstCursorHand);
      MovePictureRect(X - FMouseMovePrevPoint.X, Y - FMouseMovePrevPoint.Y);
      FMouseMovePrevPoint := Point(X, Y);
    end
    else
      if FRectZoomingProcess then
        RectZoomingOperation(X, Y, True)
      else
        if FMouseMoveZoomingProcess then
        begin
          if FZoomChangeByMouseMove <> 0 then
          begin
            if Y > FMouseMovePrevPoint.Y then
              AlterScale(FZoomChangeByMouseMove, False, FMouseDownPt)
            else
              if Y < FMouseMovePrevPoint.Y then
                AlterScale(1 / FZoomChangeByMouseMove, False, FMouseDownPt);
            FMouseMovePrevPoint.Y := Y;
          end;
        end;
    if (IsMouseInSysButtons) and (not (Self.ClassType = TsgDrawingNavigator)) then
      Cursor := crDefault;
  end;
end;

procedure TsgDrawingNavigator.MouseUp(Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  inherited MouseUp(Button, Shift, X, Y);
  if not FRectangle.Active and not FOrbit3D.Visible then
  begin
    if FRectZoomingProcess then
    begin
      DrawZoomingRect(FZoomingRect);
      ZoomRect(FZoomingRect);
      FRectZoomingProcess := False;
    end;
    FMouseMoveZoomingProcess := False;
    if ((X <> FMouseDownPt.X) or (Y <> FMouseDownPt.Y))
      and (Button in [mbRight, mbMiddle]) then
      SaveCurrentView;
  end;
  StopMoveByMouse;
end;

procedure TsgDrawingNavigator.ObtainFocus(var Message: TMessage);
begin
  if not FBlockFocusUpdate then
  begin
    inherited;
    if (Focused) and (not FAppActive) then
      FAppActive := True;
    if not (csDesigning in ComponentState) and AutoFocus then
      if Enabled and Visible and CanFocus and Application.Active then
      begin
        if not Focused then
          SetFocus;
        if GetFocus <> Handle then
          Windows.SetFocus(Handle);
      end;
  end;
end;

function TsgDrawingNavigator.OpenVPort: Boolean;
var
  P: TsgDXFVport;
  vRect: TFRect;
  vRect2D: TRect;
  vOffset, vScale: TFPoint;
  vDrawMatrix: TFMatrix;
  vRotMatrix: TFMatrix;
  vView: TsgView;
begin
  Result := False;
  vRect := ActiveVPort(FPicture.Graphic);

  if not IsBadRect(vRect) then
  begin
    P := TsgCADConverter(FTool.Img.Converter).ActiveVPort;
    if FTool.IsNotCustomDraw and not sgIsZero(P.ViewHeight) then
    begin
      vRotMatrix := TsgCADConverter(FTool.Img.Converter).ViewTwistMatrix;
      vDrawMatrix := vRotMatrix;
      FMatOffset(vDrawMatrix, Reverse(P.ViewCenterPoint));
      vScale.X := ClientHeight / P.ViewHeight;
      vScale.Y := vScale.X;
      vScale.Z := vScale.X;
      if FTool.Img.Converter.IsCrossoverMatrix then
        vScale.Y := -vScale.Y;
      vDrawMatrix := FMatScale(vDrawMatrix, vScale);
      vOffset := SubFPoint(FPointXMat(P.ViewDirection, vDrawMatrix),
      FPointXMat(P.ViewTarget, vDrawMatrix));
      vOffset := AddFPoint(vOffset,
          MakeFPoint(P.ViewAspectRatio * ClientHeight / 2, ClientHeight / 2));
      vDrawMatrix := FMatTranslate(vDrawMatrix, vOffset);
    end;

    if FTool.IsNotCustomDraw and not sgIsZero(P.ViewHeight) then
    begin
      if FTool.Img.CurrentLayout.IsModel then
      begin
        BeginUpdate;
        try
          FTool.Img.CurrentLayout.SetRotMatrix(vRotMatrix);
          FTool.Img.SetDrawMatrix(vDrawMatrix);
          Result := TrySetPictureRect2D(FTool.GetPictureRect2D);
        finally
          EndUpdate;
        end;
      end
      else
      begin
        FTool.Img.Layouts[0].SetRotMatrix(vRotMatrix);
        vView := TsgDXFLayoutAccess(FTool.Img.Layouts[0]).View;
        vView.DrawMatrix := vDrawMatrix;
        vRect2D := GetPictureRect;
        if (vRect2D.Left <> vRect2D.Right) or (vRect2D.Top <> vRect2D.Bottom) then
        begin
          if vView.HasView then
          begin
            if not IsEqualRects(vView.ActiveView, vRect2D) then
              vView.PushView(vRect2D);
          end
          else
            vView.PushView(vRect2D);
        end;
      end;
    end
    else
      Result := ShowRect(vRect);
  end;
end;

procedure TsgDrawingNavigator.OrbitMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button <> mbLeft then
    MouseDown(Button, Shift, X - TControl(Sender).Left, Y - TControl(Sender).Top);
end;

procedure TsgDrawingNavigator.OrbitMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
  if Shift <> [ssLeft] then
    MouseMove(Shift, X - TControl(Sender).Left, Y - TControl(Sender).Top);
end;

procedure TsgDrawingNavigator.OrbitMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button <> mbLeft then
    MouseUp(Button, Shift, X - TControl(Sender).Left, Y - TControl(Sender).Top);
end;

procedure TsgDrawingNavigator.Paint;
var
  vDC: HDC;

  procedure DoCustomPaint(const ADC: HDC);
  begin
    if (ADC <> FBufferDC) or FOrbit3D.Visible or FPictureChanged then
      Perform(WM_ERASEBKGND, ADC, ADC); // draw window background
    SetVisibleArea;
    Canvas.StretchDraw(GetPictureRect, FPicture.Graphic);
    FRectZoomingProcess := False;
    if ADC <> FBufferDC then
    begin
      if FBufferDC <> 0 then
        SaveBuffer(ADC) // save windowed buffer
      else
        FBufferSaved := False;
      DoBroadcastPaintEvent(ADC);
    end
    else
      FBufferSaved := FBufferDC <> 0;
    PictureRectChanged := False;
    PictureChanged := False;
  end;

  procedure DoBufferPaintToWindow(const ADC: HDC);
  begin
    RenderBuffer(ADC);
    DoBroadcastPaintEvent(ADC);
    PictureRectChanged := False;
  end;

begin
  FTool.Update;
  vDC := Canvas.Handle;
  DrawScrollBarsJoint(vDC);
  if FUpdateCount = 0 then
  begin
    if FDoubleBuffered then
    begin
      if GetRenderBuffer then
      begin
        if vDC <> FBufferDC then
          if FBufferDC <> 0 then
            DoBufferPaintToWindow(vDC) // draw bitmap buffer to windowed canvas
          else
            DoCustomPaint(vDC);
      end
      else
        DoCustomPaint(vDC);
    end
    else
      DoCustomPaint(vDC);
  end;
end;

function TsgDrawingNavigator.PerformBroadcastEvents(const AMsg: Cardinal;
  const AWParam: WPARAM; const  ALParam: LPARAM): LRESULT;
var
  Message: TMessage;
  vPRect: PFRect;
begin
  if FOrbit3D.Visible and (AMsg = WM_PAINT) then
    FOrbit3D.Perform(WM_PAINT, AWParam, 0);
  Message.Msg := AMsg;
  Message.WParam := AWParam;
  Message.LParam := ALParam;
  Message.Result := 0;
  BroadcastClients(Message);
  Result := Message.Result;
  if Result = 0 then
    case AMsg of
      WM_PAINT:
        if Assigned(FOnPaint) then
          FOnPaint(Self);
      WM_SCALECHANGING:
        if Assigned(FOnChangeScale) then
          FOnChangeScale(Self);
      WM_PICTUREMOVE:
        if Assigned(FOnPictureMove) then
          FOnPictureMove(Self);
      WM_PICTURECHANGE:
        if Assigned(FOnPictureChange) then
          FOnPictureChange(Self);
      WM_GETIMAGEEXTENTS:
        if Assigned(FOnGetImageExtents) then
        begin
          vPRect := PFRect(Message.WParam);
          FOnGetImageExtents(vPRect^.Left, vPRect^.Top, vPRect^.Z1,
            vPRect^.Right, vPRect^.Bottom, vPRect^.Z2);
        end;
    end;
end;

procedure TsgDrawingNavigator.Progress(Sender: TObject;
  Stage: TProgressStage; PercentDone: Byte; RedrawNow: Boolean;
  const R: TRect; const Msg: string);
begin
  if Assigned(FOnProgress) then
    FOnProgress(Sender, Stage, PercentDone, RedrawNow, R, Msg);
end;

procedure TsgDrawingNavigator.Resize;
var
  vSize: TPoint;
begin
  inherited Resize;
  if FBufferSaved then
  begin
    if GetBitmapSize(FBufferBMP, vSize) then
    begin
      if (ClientWidth > vSize.X) or (ClientHeight > vSize.Y) then
        ReCreateBuffer
    end
    else
      ReCreateBuffer;
  end
  else
    ReCreateBuffer;
  DoChangeScrolls;
end;

function TsgDrawingNavigator.Rotate(Axis: TsgAxes; Angle: Extended): Boolean;
begin
  Result := False;
  if Assigned(FTool.Img) and not FTool.Img.Empty then
  try
    BeginUpdate;
    try
      FTool.Img.Rotate(Axis, Angle);
      FTool.Img.RefreshCurrentLayout;
    finally
      EndUpdate;
    end;
    Result := True;
  except
  end;
end;

procedure TsgDrawingNavigator.RotToView(const A3DView: TsgDXFViewDirection);
begin
  if Assigned(FTool.Img) then
    FTool.Img.RotToView(A3DView);
end;

procedure TsgDrawingNavigator.SaveCurrentView(const ARect: PRect = nil);
var
  vRect: TRect;
  vView: TsgView;
begin
  if Assigned(FTool.Img) then
  begin
    vView := GetImageView;
    vView.DrawMatrix := FTool.Img.DrawMatrix;
    if ARect = nil then
      vRect := GetPictureRect
    else
      vRect := ARect^;
    if (vRect.Left <> vRect.Right) or (vRect.Top <> vRect.Bottom) then
    begin
      if vView.HasView then
      begin
        if not IsEqualRects(vView.ActiveView, vRect) then
          vView.PushView(vRect);
      end
      else
        vView.PushView(vRect);
    end;
  end;
end;

{$IFNDEF SG_MDI}
type
  TCustomFormAccess = class(TCustomForm);
{$ENDIF}
procedure TsgDrawingNavigator.SetParent(AParent: TWinControl);
{$IFNDEF SG_MDI}
var
  vForm: TCustomFormAccess;
{$ENDIF}
begin
  inherited SetParent(AParent);
  if Assigned(AParent) then
  begin
    FVertScrollBar.Parent := Self;
    FHorzScrollBar.Parent := Self;
  end;
  if Assigned(FOrbit3D) then
  begin
    FOrbit3D.Parent := Self;
    TControlAccess(Orbit3D).RequestAlign;
  end;
  if Assigned(AParent) then
  begin
{$IFNDEF SG_MDI}
    vForm := TCustomFormAccess(GetParentForm(Self{$IFDEF SGDEL_2005}, False{$ENDIF}));
    if Assigned(vForm) then
      SysMenuIconsVisible := vForm.FormStyle = fsMDIChild;
{$ELSE}
    SysMenuIconsVisible := True;
{$ENDIF}
  end;
end;

function TsgDrawingNavigator.ShowRectConvertUnits(ARect: TFRect;
  const AConvertUnits: Boolean): Boolean;
var
  vSx, vH: Double;
  vZoom: TFRect;
  vRect, vTmpPicRect: TF2DRect;
  vTmpR: TRect;
  vPos: TFPoint;

  function IsFRectEmpty(const R: TFRect): Boolean;
  begin
    Result := sgIsZero(R.Right - R.Left) or sgIsZero(R.Top - R.Bottom);
  end;

begin
  Result := False;
  if Assigned(FTool.Img) then
  begin
    if AConvertUnits then
    begin
      ARect.TopLeft := FTool.ConvertUnits(ARect.TopLeft);
      ARect.BottomRight := FTool.ConvertUnits(ARect.BottomRight);
    end;
    if FTool.IsNotCustomDraw and not IsFRectEmpty(ARect) then
    begin
      vZoom := ARect;
      TransRectCorners(vZoom, FTool.Img.DrawMatrix);

      vSx := ClientWidth / (vZoom.Right - vZoom.Left);
      vH := vSx * (vZoom.Top - vZoom.Bottom);
      if vH > ClientHeight then
        vSx := vSx * ClientHeight / vH;
      FTool.Img.ScaleDrawMatrix(vSx, vSx, vSx);

      vZoom := ARect;
      vPos := GetRectCenter(vZoom);
      FTool.MovePositionToPoint(vPos, Center);
      FTool.ResetRotCenter(Center);
      Result := TrySetPictureRect2D(FTool.GetPictureRect2D);
    end
    else
    begin
      vTmpPicRect := InitialPicRect2D(FMaxDim, FMaxDim);
      FTool.ApplyScale(vTmpPicRect);
      vTmpR.TopLeft := FTool.Img.GetPoint(ARect.TopLeft);
      vTmpR.BottomRight := vTmpR.TopLeft;
      ExpandRect(vTmpR, FTool.Img.GetPoint(ARect.BottomRight));
      if IsRectEmpty(vTmpR) then
      begin
        vRect := GetSimilarRect(GetImageExtents, ARect, vTmpPicRect);
        if not IsRectEmpty(RoundRect2D(vTmpPicRect)) then
          if FTool.IsNotCustomDraw then
          begin
            FTool.ApplyScale(vTmpPicRect);
            FTool.ResetRotCenter(Center);
            Result := TrySetPictureRect2D(FTool.GetPictureRect2D);
          end
          else
            Result := TrySetPictureRect2D(vTmpPicRect);
      end
      else
        Result := ZoomRectEx2D(Rect2DFromRect(vTmpR), vTmpPicRect);
    end;
  end;
end;

function TsgDrawingNavigator.ViewRect: TRect;
begin
  Result := ClientRect;
  if not (csDestroying in ComponentState) then
  begin
    if Assigned(FHorzScrollBar) and Assigned(FHorzScrollBar.Parent)
      and FHorzScrollBar.Visible then
      Result.Bottom := Result.Bottom - FHorzScrollBar.Height;
    if Assigned(FVertScrollBar) and Assigned(FVertScrollBar.Parent)
      and FVertScrollBar.Visible then
      Result.Right := Result.Right - FVertScrollBar.Width;
  end;
end;
{$IFDEF SGDEL_5}
procedure TsgDrawingNavigator.WMContextMenu(var Message: TWMContextMenu);
var
  vPoint, vTmpPoint1, vTmpPoint2: TPoint;
  vHandled: Boolean;
  vPopupMenu: TPopupMenu;

  function InvalidPoint(const APoint: TPoint): Boolean;
  begin
    Result := (APoint.X = -1) and (APoint.Y = -1);
  end;

begin
  if Message.Result <> 0 then Exit;
  if csDesigning in ComponentState then
    inherited
  else
  begin
    vHandled := False;
    vPoint.X := Message.XPos;
    vPoint.Y := Message.YPos;
    if InvalidPoint(vPoint) then
      vTmpPoint1 := vPoint
    else
    begin
      vTmpPoint1 := ScreenToClient(vPoint);
      if not PtInRect(ClientRect, vTmpPoint1) then
      begin
        inherited;
        vHandled := True;
      end;
    end;
    if not vHandled then
    begin
      DoContextPopup(vTmpPoint1, vHandled);
      Message.Result := Ord(vHandled);
      if not vHandled then
      begin
        vPopupMenu := GetPopupMenu;
        vTmpPoint2 := ScreenToClient(vPoint);
        if (vPopupMenu <> nil) and vPopupMenu.AutoPopup then
        begin
          SendCancelMode(nil);
          vPopupMenu.PopupComponent := Self;
          if InvalidPoint(vPoint) then
            vPoint := ClientToScreen(Point(0, 0));
          if (vTmpPoint2.X = FMouseDownPt.X)
            and (vTmpPoint2.Y = FMouseDownPt.Y) then
            vPopupMenu.Popup(vPoint.X, vPoint.Y);
          Message.Result := 1;
        end;
        if Message.Result = 0 then
          inherited;
      end;
    end;
  end;
end;
{$ENDIF}

procedure TsgDrawingNavigator.WMEraseBkgnd(var Message: TWmEraseBkgnd);
begin
  inherited;
  if (TMessage(Message).WParam = WPARAM(TMessage(Message).LParam))
    and (Message.DC = FBufferDC) then
    FBufferSaved := False;
end;

procedure TsgDrawingNavigator.CNKeyDown(var Message: TWMKeyDown);

  procedure MoveRect(const ADX, ADY: Double);
  begin
    MovePictureRect2D(ADX, ADY);
    Message.Result := 1;
  end;

var
  vShiftState: TShiftState;
begin
  vShiftState := KeyDataToShiftState(Message.KeyData);
  if vShiftState = [] then
    case TWMKey(Message).CharCode of
      0:
        Message.Result := 1;
      VK_UP:
        MoveRect(0, FVertScrollBar.SmallChange);
      VK_DOWN:
        MoveRect(0, -Integer(FVertScrollBar.SmallChange));
      VK_LEFT:
        MoveRect(FHorzScrollBar.SmallChange, 0);
      VK_RIGHT:
        MoveRect(-Integer(FHorzScrollBar.SmallChange), 0);
      VK_PRIOR:
        if GetKeyState(VK_CONTROL) < 0 then
          MoveRect(0, FVertScrollBar.LargeChange);
      VK_NEXT:
        if GetKeyState(VK_CONTROL) < 0 then
          MoveRect(0, -FVertScrollBar.LargeChange);
      VK_HOME:
        MoveRect(-FPictureRect2D.Left, -FPictureRect2D.Top);
      VK_END:
        MoveRect(ClientWidth - FPictureRect2D.Right, ClientHeight - FPictureRect2D.Bottom);
      VK_ADD:
        begin
          AlterScale(2, False, Center);
          Message.Result := 1;
        end;
      VK_SUBTRACT:
        begin
          AlterScale(0.5, False, Center);
          Message.Result := 1;
        end;
//      VK_MULTIPLY:
//        begin
//          FitToSize;
//          Message.Result := 1;
//        end;
//implemented in the unit "Main.pas" action "actFittoWindow"
    end;
  if Message.Result = 0 then
  begin
    inherited;
    Exit;
  end;
end;

procedure TsgDrawingNavigator.WMPaint(var Message: TWMPaint);
var
  vDC: HDC;
  vPaintStruct: TPaintStruct;

  procedure DoPaintDC(ADC: HDC);
  begin
    Perform(WM_ERASEBKGND, ADC, ADC);
    Message.DC := ADC;
    WMPaint(Message);
    Message.DC := 0;
  end;

  procedure DoPaintEvents(ADC: HDC);
  begin
    Canvas.Lock;
    try
      Canvas.Handle := ADC;
      try
        DoBroadcastPaintEvent(ADC);
      finally
        Canvas.Handle := 0;
      end;
    finally
      Canvas.Unlock;
    end;
  end;

  function DoPaintHandler(ADC: HDC): HDC;
  begin
    Result := ADC;
    if not FDoubleBuffered then
    begin
      DoPaintDC(FBufferDC);
      Result := 0;
    end
    else
      if not GetRenderBuffer then
        DoPaintDC(FBufferDC);
    RenderBuffer(ADC);
    PictureRectChanged := False;
  end;

  procedure DoInherited;
  begin
    FNeedBroadcastPaintEvent := True;
    inherited
  end;

begin
  if not FDoubleBuffered or (Message.DC <> 0) then
    DoInherited
  else
    if CreateBuffer then
    begin
      vDC := BeginPaint(Handle, vPaintStruct);
      FNeedBroadcastPaintEvent := True;
      DoPaintHandler(vDC);
      if FNeedBroadcastPaintEvent then
        DoPaintEvents(vDC);
      EndPaint(Handle, vPaintStruct);
    end
    else DoInherited;
end;

function TsgDrawingNavigator.ZoomRect(ARect: TRect): Boolean;
begin
  Result := ZoomRectEx2D(Rect2DFromRect(ARect), FPictureRect2D);
end;

function TsgDrawingNavigator.ZoomRectEx(ARect, APictureRect: TRect): Boolean;
begin
  Result := ZoomRectEx2D(Rect2DFromRect(ARect), Rect2DFromRect(APictureRect));
end;

function TsgDrawingNavigator.ZoomRectEx2D(ARect, APictureRect: TF2DRect): Boolean;
var
  vWidth, vHeight: Double;
  vNewRect: TF2DRect;
  vScale, vScaleX, vScaleY: Double;
  vMidShiftX, vMidShiftY: Double;
  vPosition: TFPoint;
begin
  Result := False;
  Include(FState, dnsZooming);
  try
    CorrectRect2D(ARect);
    vWidth := ARect.Right - ARect.Left;
    vHeight := ARect.Bottom - ARect.Top;
    if (Abs(vWidth) >= fDoubleResolution) and (Abs(vHeight) >= fDoubleResolution) then
    begin
      vNewRect := APictureRect;
      OffsetRect2D(vNewRect, -ARect.Left, -ARect.Top);
      vScaleX := ClientWidth / vWidth;
      vScaleY := ClientHeight / vHeight;
      vMidShiftX := 0;
      vMidShiftY := 0;
      if vScaleX * vHeight > ClientHeight then
      begin
        vScale := vScaleY;
        vMidShiftX := (ClientWidth - vScale * vWidth) / 2;
      end
      else
      begin
        vScale := vScaleX;
        vMidShiftY := (ClientHeight - vScale * vHeight) / 2;
      end;
      if FTool.IsNotCustomDraw then
      begin
        vPosition := FTool.Point2DToPosition(GetRectCenter(ARect));
        FTool.Img.ScaleDrawMatrix(vScale, vScale, vScale);
        FTool.MovePositionToPoint(vPosition, Center);
        FTool.ResetRotCenter(Center);
        vNewRect := FTool.GetPictureRect2D;
      end
      else
      begin
        vNewRect.Left := vScale * vNewRect.Left;
        vNewRect.Top := vScale * vNewRect.Top;
        vNewRect.Right := vNewRect.Left + vScale * (APictureRect.Right - APictureRect.Left);
        vNewRect.Bottom := vNewRect.Top + vScale * (APictureRect.Bottom - APictureRect.Top);
        OffsetRect2D(vNewRect, vMidShiftX, vMidShiftY);
      end;
      Result := TrySetPictureRect2D(vNewRect);
    end;
  finally
    Exclude(FState, dnsZooming);
  end;
end;

constructor TsgDrawingNavigator.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Brush.Style := bsClear;
  FAppActive := True;
  FClients := TList.Create;
  CreateHorScroll;
  CreateVertScroll;
  Cursor := TCursor(cnstCursorCross);
  FCenterAlignment := False;
  FExtents := cnstBadRect;
  FHorzScrollBarVisibility := True;
  FLinearDimensionsMode := umDrawing;
  FLinearDimensionsScaleFactor := 1.0;
  FMouseDragging := True;
  FMouseMoveZoomingProcess := False;
  FMouseScaling := False;
  FMoveByMouse := False;
  CreateOrbit3D(GetOrbit3DClass);
  FPicture := TsgPicture.Create(Self);
  FPicture.Graphic := nil;
  FPicture.OnChange := DoPictureChange;
  FPicture.OnProgress := Progress;
  FPictureRect2D := MakeF2DRect(0, 0, iMinPicSize, iMinPicSize);
  FRectangle := TsgRectangle.Create(Self);
  FRectZooming := True;
  FRectZoomingProcess := False;
  FScalingOnMouseWheel := True;
  CreateSnap;
  FState := [];
  FStretch := False;
  FTool := TsgControlTool.Create(Self);
  FTmpGraphic := nil;
  FVertScrollBarVisibility := True;
  FVPortOpened := False;
  FWidthInPixels := 0.0;
  FZoomChangeByMouseMove := 1.1;
  FZoomInChange := cnstScales[False];
  FZoomOutChange := cnstScales[True];
  Application.HookMainWindow(ApplicationHook);
  FMaxDim := iMaxNonWinNTDim;
  FLoadedFromConverter := False;
  FOnFitToSize := TList.Create;
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    FMaxDim := iMaxWinNTDim;
end;

destructor TsgDrawingNavigator.Destroy;
begin
  Application.UnhookMainWindow(ApplicationHook);
  DestroyBuffer;
  FActualGraphic.Free;
  FRectangle.Free;
  FSnapControl.Free;
  FreeAndNil(FSysMenuIcons);
  FTool.Free;
  FPicture.Free;
  FClients.Free;
  FClients := nil;
  ClearNotifyEventList(FOnFitToSize);
  FOnFitToSize.Free;
  inherited Destroy;
end;

function TsgDrawingNavigator.AddClient(AClient: TObject): Integer;
begin
  Result := FClients.IndexOf(AClient);
  if Result = -1 then
    Result := FClients.Add(AClient);
end;

function TsgDrawingNavigator.AlterScale(AFactor: Double;
  AIsAbsolute: Boolean; APosition: TPoint): Boolean;
var
  vCompression, vHorzMaxScale, vVertMaxScale: Double;
  vNewRect: TF2DRect;
  vPictureRectWidth, vPictureRectHeight: Double;
  vS, vCenterPrev, vDir: TFPoint;
  vM: TFMatrix;
  vResolution: Double;

  function PerformScale(const AHorzMaxScale, AVertMaxScale: Double;
    var ACompression: Double; const AResolution: Double): Boolean;
  begin
    Result := False;
    if ACompression > AResolution then
      if (ACompression > AHorzMaxScale) or
         (ACompression > AVertMaxScale) then
        ACompression := ACompression - AResolution
      else
        Result := True
    else
      Result := True;
  end;

begin
  Result := False;
  if AFactor > fAccuracy then
  begin
    if AIsAbsolute then
      AFactor := AFactor / Scale;
    vPictureRectWidth := Abs(FPictureRect2D.Right - FPictureRect2D.Left);
    vPictureRectHeight := Abs(FPictureRect2D.Bottom - FPictureRect2D.Top);
    vCompression := 1.0;
    if vPictureRectWidth <= 0 then
      vPictureRectWidth := iMinPicSize;
    if vPictureRectHeight <= 0 then
      vPictureRectHeight := iMinPicSize;
    vHorzMaxScale := FMaxDim / (vPictureRectWidth * AFactor);
    vVertMaxScale := FMaxDim / (vPictureRectHeight * AFactor);
    if vCompression > vHorzMaxScale then
      vCompression := vHorzMaxScale;
    if vCompression > vVertMaxScale then
      vCompression := vVertMaxScale;
    if FTool.IsNotCustomDraw then
      vResolution := fDoubleResolution
    else
      vResolution := fAccuracy;
    repeat
    until PerformScale(vHorzMaxScale, vVertMaxScale, vCompression, vResolution);
    AFactor := AFactor * vCompression;
    if FTool.IsNotCustomDraw then
    begin
      vM := FMatScale(FTool.Img.DrawMatrix, AFactor);
      vS := ExtractMatrixAbsoluteScale(vM, fMaxResolution);
      if not (IsZero(vS.X, fMaxResolution) or IsZero(vS.Y, fMaxResolution) or IsZero(vS.Z, fMaxResolution)) then
        if not sgIsZero(AFactor - 1) and not sgIsZero(AFactor) then
        begin
          BeginUpdate;
          try
            vCenterPrev := FPointXMat(FTool.Img.Center, FTool.Img.DrawMatrix);
            vDir := SubFPoint(vCenterPrev, MakeFPointFromPoint(APosition));
            vM.E0 := SubFPoint(vCenterPrev, AffineTransformPoint(FTool.Img.Center, vM));
            vM.E0 := SubFPoint(vM.E0, PtXScalar(vDir, 1 - AFactor));
            FTool.Img.SetDrawMatrix(vM);
            FTool.ResetRotCenter(Center);
            Result := TrySetPictureRect2D(FTool.GetPictureRect2D);
          finally
            EndUpdate;
          end;
        end;
    end
    else
    begin
      vNewRect.Left := APosition.X - (APosition.X - FPictureRect2D.Left) * AFactor;
      vNewRect.Top := APosition.Y - (APosition.Y - FPictureRect2D.Top) * AFactor;
      vNewRect.Right := APosition.X + (FPictureRect2D.Right - APosition.X) * AFactor;
      vNewRect.Bottom := APosition.Y + (FPictureRect2D.Bottom - APosition.Y) * AFactor;
      if vNewRect.Right - vNewRect.Left < iMinPicSize then
        vNewRect.Right := vNewRect.Left + iMinPicSize;
      if vNewRect.Bottom - vNewRect.Top < iMinPicSize then
        vNewRect.Bottom := vNewRect.Top + iMinPicSize;
      Result := TrySetPictureRect2D(vNewRect);
    end;
  end;
end;

function TsgDrawingNavigator.BeginUpdate: Integer;
begin
  Inc(FUpdateCount);
  Result := FUpdateCount;
end;

procedure TsgDrawingNavigator.CallPreviousView;
var
  vS: Double;
  vView: TsgView;
  vCenter: TPoint;
  vPos: TFPoint;
  vPreviousView: TRect;
begin
  vView := GetImageView;
  if Assigned(vView) then
  begin
    if vView.HasBaseView or vView.HasView then
    begin
      try
        Include(FState, dnsSetView);
        vView.PopView;
        vPreviousView := vView.ActiveView;
        if FTool.IsNotCustomDraw and not EqualRect(vPreviousView, PictureRect) then
        begin
          BeginUpdate;
          try
            vPos := FTool.PointToPosition(GetRectCenter(PictureRect));
            FTool.Img.Offset := FTool.Img.Center;
            vCenter := GetRectCenter(vPreviousView);
            vS := (vPreviousView.Right - vPreviousView.Left) / PictureRectWidth;
            FTool.Img.ScaleDrawMatrix(vS, vS, vS);
            FTool.MovePositionToPoint(vPos, vCenter);
            FTool.ResetRotCenter(Center);
            PictureRect2D := FTool.GetPictureRect2D;
          finally
            EndUpdate;
          end;
        end
        else
          PictureRect := vPreviousView;
      finally
        Exclude(FState, dnsSetView);
      end;
    end;
  end;
end;

function TsgDrawingNavigator.Center: TPoint;
begin
  Result := GetRectCenter(ClientRect);
end;

procedure TsgDrawingNavigator.CorrectPictureRectByExtents;
var
  vWidth, vHeight: Double;
  vPoint: TF2DPoint;
  vRect: TF2DRect;
  vExtents: TFRect;

  procedure DoResetSet(const APicRect: TF2DRect; const AExtents: TFRect);
  begin
    Inc(FUpdateCount);
    try
      PictureRect2D := APicRect;
      FExtents := AExtents;
    finally
      Dec(FUpdateCount);
    end;
  end;

begin
  vExtents := GetImageExtents;
  if FTool.IsNotCustomDraw then
  begin
    FTool.ResetRotCenter(Center);
    if FOrbit3D.Visible and (dnsPictureChanging in FState) then
      FPictureRect2D := FTool.GetPictureRect2D
    else
      PictureRect2D := FTool.GetPictureRect2D;
    FExtents := vExtents;
  end
  else
  begin
    vRect := TransformRect2D(FPictureRect2D, FExtents, vExtents);
    // centering new rectangle (vRect)
    if FOrbit3D.Visible or (dnsAfterRotate in FState) then
    begin
      vPoint := GetRectCenter(FPictureRect2D);
      CorrectRect2D(vRect);
      vWidth := vRect.Right - vRect.Left;
      vHeight := vRect.Bottom - vRect.Top;
      vRect.Left := vPoint.X - vWidth / 2;
      vRect.Right := vRect.Left + vWidth;
      vRect.Top := vPoint.Y - vHeight / 2;
      vRect.Bottom := vRect.Top + vHeight;
    end;
    DoResetSet(vRect, vExtents);
  end;
end;

procedure TsgDrawingNavigator.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.Style := Params.Style or WS_CLIPCHILDREN or WS_GROUP;
end;

procedure TsgDrawingNavigator.CreateSnap;
begin
  FSnapControl := TSnapControl.Create;
  FSnapControl.Control := Self;
end;

function TsgDrawingNavigator.CreateSnapShotBitmap: Graphics.TBitmap;
var
  vDoubleBuffered: Boolean;
begin
  vDoubleBuffered := DoubleBuffered;
  try
    if not DoubleBuffered then
    begin
      DoubleBuffered := True;
      Repaint;
    end;
    Result := TBitmap.Create;
    Result.Handle := CopyImage(BufferBMP, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
  finally
    DoubleBuffered := vDoubleBuffered;
  end;
end;

function TsgDrawingNavigator.Empty: Boolean;
begin
  Result := (FPicture = nil) or (FPicture.Graphic = nil) or
    FPicture.Graphic.Empty;
end;

function TsgDrawingNavigator.EndUpdate: Integer;
begin
  Dec(FUpdateCount);
  if FUpdateCount >= 0 then
  begin
    Result := FUpdateCount;
    if Result = 0 then
    begin
      if FDoubleBuffered then
        DrawThrowBuffer
      else
        Perform(WM_PAINT, Canvas.Handle, 0);
      PictureRectChanged := False;
      PictureChanged := False;
    end;
  end
  else
  begin
    FUpdateCount := 0;
    Result := 0;
  end;
end;

function TsgDrawingNavigator.FinishEntity(Entity: TsgDXFEntity): Integer;
begin
  Result := 0;
end;

procedure TsgDrawingNavigator.FitToSize;
var
  vRect: TF2DRect;
begin
  BeginUpdate;
  try
    if FTool.IsNotCustomDraw then
      FTool.Img.RefreshCurrentLayout;
    vRect := InitialPicRect2D(ClientWidth, ClientHeight);
    FTool.ApplyScale(vRect);
    if FTool.IsNotCustomDraw then
      PictureRect2D := FTool.GetPictureRect2D
    else
      PictureRect2D := vRect;
  finally
    EndUpdate;
  end;
end;

function TsgDrawingNavigator.GetDrawingCoords(ACoordX, ACoordY: Integer;
  var AUnits: string): TFPoint;
var
  vTmpPoint: TFPoint;
begin
  Result := CoordinateConvertion(ACoordX, ACoordY, vTmpPoint);
{  // previos version(6.1)
  vPoint1 := CoordinateConvertion(ACoordX, ACoordY, vPoint2);
  Result := ConvertUnits(vPoint1, AUnits);
}
end;

function TsgDrawingNavigator.GetDrawingInternalCoords(ACoordX,
  ACoordY: Integer): TFPoint;
var
  vTmpPoint: TFPoint;
begin
  Result := CoordinateConvertion(ACoordX, ACoordY, vTmpPoint);
end;

function TsgDrawingNavigator.GetDrawingInternalCoordsWithoutSnap(ACoordX,
  ACoordY: Integer): TFPoint;
var
  vTmpPoint: TFPoint;
begin
  Result := GetRealPoint(ACoordX, ACoordY, vTmpPoint);
end;

function TsgDrawingNavigator.GetDrawingUCSCoords(ACoordX, ACoordY: Integer;
  var AUnits: string): TFPoint;
begin
  CoordinateConvertion(ACoordX, ACoordY, Result);
{  // previos version(6.1)
  CoordinateConvertion(ACoordX, ACoordY, vPoint);
  Result := ConvertUnits(vPoint, AUnits);
}
end;

function TsgDrawingNavigator.GetEnterClipRectEvent: TNotifyEvent;
begin
  Result := FRectangle.OnEnterRect;
end;

function TsgDrawingNavigator.GetEntity(Entity: TsgDXFEntity): Integer;
begin
  Result := 0;
end;

function TsgDrawingNavigator.GetHasTriangledMesh: Boolean;
begin
  Result := False;
end;

function TsgDrawingNavigator.GetRealPoint(ACoordX, ACoordY: Double;
  var APointInUCS: TFPoint): TFPoint;
var
  vXScaled, vYScaled: Extended;
begin
  GetPointToImageSizeRatio(ACoordX, ACoordY, vXScaled, vYScaled);
  if Assigned(FTool.Img) and
     Assigned(FTool.Img.CurrentLayout) then
  begin
{$IFDEF SGDEL_4}
    Result := FTool.Img.GetCADCoords(vXScaled, vYScaled, APointInUCS);
{$ELSE}
    Result := FTool.Img.GetCADCoords(vXScaled, vYScaled);
    APointInUCS := Result;
    FTool.Img.TransformToUCS(APointInUCS);
{$ENDIF}
  end
  else
    Result := MakeFPoint(FPicture.Width * vXScaled,
      FPicture.Height * vYScaled, 0)
end;

function TsgDrawingNavigator.HasPreviousView: Boolean;
var
  vView: TsgView;
begin
  Result := False;
  vView := GetImageView;
  if Assigned(vView) then
    Result := vView.HasView;
end;

procedure TsgDrawingNavigator.ImgLock;
begin
  Inc(FUpdateCount);
end;

procedure TsgDrawingNavigator.ImgUnLock;
begin
  Dec(FUpdateCount);
  if FUpdateCount < 0 then
    FUpdateCount := 0;
end;

procedure TsgDrawingNavigator.Invalidate;
begin
  if FUpdateCount <= 0 then
    inherited Invalidate;
end;

type
  TsgDXFConverterAccess = class(TsgDXFConverter);

procedure TsgDrawingNavigator.LoadFromConverter(const AConverter: TsgDXFConverter;
  const ACADImage: TsgCADImage);
begin
  AConverter.AutoInsert := True;
  AConverter.Params := InitializeIterateParams;
  TsgDXFConverterAccess(AConverter).GetModelLayout.Iterate(AConverter, GetEntity,
    FinishEntity);
  FLoadedFromConverter := True;
  //Converter.Iterate(GetEntity, nil, FIterateParam);
end;

procedure TsgDrawingNavigator.LoadFromFile(const AFileName: string);
begin
  BeginUpdate;
  try
    Clear;
    FLoadedFromConverter := False;
    FPicture.LoadFromFile(AFileName);
  finally
    EndUpdate;
  end;
end;

procedure TsgDrawingNavigator.LockChangeVisibleArea;
begin
  FLockChangeVisibleArea := True;
end;

procedure TsgDrawingNavigator.MovePictureRect(const ADeltaX, ADeltaY: Integer);
begin
  MovePictureRect2D(ADeltaX, ADeltaY);
end;

function TsgDrawingNavigator.MovePictureRect2D(const ADeltaX, ADeltaY: Double): Boolean;
var
  vRect: TF2DRect;
begin
  Result := False;
  if not (dnsMoving in FState) then
  try
    Include(FState, dnsMoving);
    if FTool.IsNotCustomDraw then
    begin
      FTool.Img.TranslateDrawMatrix(ADeltaX, ADeltaY, 0);
      FTool.ResetRotCenter(Center);
      vRect := FTool.GetPictureRect2D;
    end
    else
    begin
      vRect := FPictureRect2D;
      OffsetRect2D(vRect, ADeltaX, ADeltaY);
    end;
    Result := TrySetPictureRect2D(vRect);
  finally
    Exclude(FState, dnsMoving);
  end;
end;

procedure TsgDrawingNavigator.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if Operation = opRemove then
    if (AComponent = FOrbit3D) then
    begin
      if FOrbit3D is TsgNaviOrbit3D then
        TsgNaviOrbit3D(FOrbit3D).FNavigator := nil;
      FOrbit3D := nil;
    end;
end;

function TsgDrawingNavigator.RemoveClient(AClient: TObject): Integer;
begin
 // if FClients <> nil then { TODO 4 : remove this line!!! it araises av, but should be corrected }
    Result := FClients.Remove(AClient);
end;

function TsgDrawingNavigator.ShowPoint(const APoint: TFPoint;
  AScale: Double): Boolean;
var
  vExtents: TFRect;
  vRect: TF2DRect;
  vOffs: TF2DPoint;
  vdX, vdY, vWidth, vHeight: Double;
  vZeroCase: Byte;
  vScale: TFPoint;
begin
  Result := False;
  if AScale < 0 then
    AScale := Scale;
  if not Empty then
  begin
    // getting extents of drawing
    vExtents := GetImageExtents;
    if FTool.IsNotCustomDraw then
    begin
      BeginUpdate;
      try
        vScale := FTool.Img.Scale;
        vWidth := FTool.ConvertUnits(vExtents.BottomRight).X - FTool.ConvertUnits(vExtents.TopLeft).X;
        vWidth := vWidth * Abs(vScale.X); // width in pixels
        AScale := AScale * FWidthInPixels / vWidth;
        FTool.Img.ScaleDrawMatrix(AScale, AScale, AScale);
        FTool.MovePositionToPoint(APoint, Center);
        FTool.Img.Offset := APoint;
        Result := TrySetPictureRect2D(FTool.GetPictureRect2D);
      finally
        EndUpdate;
      end;
    end
    else
    begin
      // Calculate destination picture rectangle
      // with zero top-left,
      vRect := MakeF2DRect(0, 0, iMinPicSize, iMinPicSize);
      // calculate absoluste width and height of drawing (real units)
      vWidth := vExtents.Right - vExtents.Left;
      vHeight := vExtents.Top - vExtents.Bottom;
      vZeroCase := Ord(sgIsZero(vHeight)) shl 1 or Ord(sgIsZero(vWidth));
      case vZeroCase of
        3:;
        2: vRect.Right := AScale * FWidthInPixels;
        1: vRect.Bottom := AScale * FWidthInPixels;
        0:
          begin
            vRect.Right := AScale * FWidthInPixels;
            vRect.Bottom := vRect.Right * vHeight / vWidth;
          end;
      end;
      // calculate offset (real units, if vector drawing)
      vdX := APoint.X - vExtents.Left; // from Left
      vdY := vExtents.Top - APoint.Y; // from Top
      vOffs := MakeF2DPoint(0, 0);
      // calculate rectangle offset to positioning APoint at center of client area
      if Abs(vWidth) > fAccuracy then
        vOffs.X := -(vRect.Right * vdX / vWidth) + ClientWidth shr 1;
      if Abs(vHeight) > fAccuracy then
        vOffs.Y := -(vRect.Bottom * vdY / vHeight) + ClientHeight shr 1;
      OffsetRect2D(vRect, vOffs.X, vOffs.Y);
      Result := TrySetPictureRect2D(vRect);
    end;
  end;
end;

function TsgDrawingNavigator.ShowRect(ARect: TFRect): Boolean;
begin
  Result := ShowRectConvertUnits(ARect, True);
end;

procedure TsgDrawingNavigator.WndProc(var Message: TMessage);
begin
  if TMessage(Message).Msg <> WM_PAINT then
    BroadcastClients(Message);
  case Message.Msg of
    WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
      if not (csDesigning in ComponentState) and CanFocus and (not Focused) then
        Windows.SetFocus(Handle);
  end;
  inherited WndProc(Message);
  case Message.Msg of
    CM_COLORCHANGED:
      FBufferSaved := False;
  end;
end;

{ TsgPicture }

constructor TsgPicture.Create(const AOwner: TPersistent);
begin
  FOwner := AOwner;
  inherited Create;
end;

function TsgPicture.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

initialization
  Screen.Cursors[cnstCursorHand] := Windows.LoadCursor(HInstance, 'HANDFLAT');
  Screen.Cursors[cnstCursorCross] := Windows.LoadCursor(HInstance, 'CROSS');
  InitDottedRectObjects;

finalization
  ClearDottedRectObjects;

end.
