{********************************************************************} { } { Developer Express Visual Component Library } { ExpressLayoutControl common routines } { } { Copyright (c) 2001-2009 Developer Express Inc. } { ALL RIGHTS RESERVED } { } { The entire contents of this file is protected by U.S. and } { International Copyright Laws. Unauthorized reproduction, } { reverse-engineering, and distribution of all or any portion of } { the code contained in this file is strictly prohibited and may } { result in severe civil and criminal penalties and will be } { prosecuted to the maximum extent possible under the law. } { } { RESTRICTIONS } { } { THIS SOURCE CODE AND ALL RESULTING INTERMEDIATE FILES } { (DCU, OBJ, DLL, ETC.) ARE CONFIDENTIAL AND PROPRIETARY TRADE } { SECRETS OF DEVELOPER EXPRESS INC. THE REGISTERED DEVELOPER IS } { LICENSED TO DISTRIBUTE THE EXPRESSLAYOUTCONTROL AND ALL } { ACCOMPANYING VCL CONTROLS AS PART OF AN EXECUTABLE PROGRAM } { ONLY. } { } { THE SOURCE CODE CONTAINED WITHIN THIS FILE AND ALL RELATED } { FILES OR ANY PORTION OF ITS CONTENTS SHALL AT NO TIME BE } { COPIED, TRANSFERRED, SOLD, DISTRIBUTED, OR OTHERWISE MADE } { AVAILABLE TO OTHER INDIVIDUALS WITHOUT EXPRESS WRITTEN CONSENT } { AND PERMISSION FROM DEVELOPER EXPRESS INC. } { } { CONSULT THE END USER LICENSE AGREEMENT FOR INFORMATION ON } { ADDITIONAL RESTRICTIONS. } { } {********************************************************************} unit dxLayoutDragAndDrop; {$I cxVer.inc} interface uses Controls, Windows, Graphics, cxClasses, cxControls, cxGraphics, dxLayoutControl; type TdxLayoutDragSource = (dsControl, dsCustomizeFormAvailableItems, dsCustomizeFormTreeViewItems); { TdxLayoutControlDragAndDropObject } TdxLayoutControlDragAndDropObject = class(TcxDragAndDropObject) private FAreaPart: TdxLayoutAreaPart; FDragImage: TcxDragImage; FDragImageOffset: TPoint; FDestinationControl: TdxCustomLayoutControl; FDestinationGroup: TdxLayoutGroup; FDestinationImage: TcxDragImage; FDestinationItem: TdxCustomLayoutItem; FHitTest: TdxCustomLayoutHitTest; FNeedRepaintDestinationImage: Boolean; FSource: TdxLayoutDragSource; FSourceItem: TdxCustomLayoutItem; FSourceItemBounds: TRect; FStartDragPoint: TPoint; function CanDrop: Boolean; function CanRemove: Boolean; function GetSourceControl: TdxCustomLayoutControl; procedure ResetDragAndDropObjects; procedure ResetDragAndDrop; procedure SetAreaPart(Value: TdxLayoutAreaPart); procedure SetDestinationControl(Value: TdxCustomLayoutControl); procedure SetDestinationGroup(Value: TdxLayoutGroup); procedure SetDestinationItem(Value: TdxCustomLayoutItem); procedure SetSourceItem(Value: TdxCustomLayoutItem); protected procedure CreateDragImage; procedure CreateDestinationImage; procedure DirtyChanged; override; function GetDragAndDropCursor(Accepted: Boolean): TCursor; override; procedure PaintDestinationImage; procedure PaintDragImage; procedure RefreshDestinationImage; procedure ShowDragImage(const P: TPoint); property AreaPart: TdxLayoutAreaPart read FAreaPart write SetAreaPart; property DestinationControl: TdxCustomLayoutControl read FDestinationControl write SetDestinationControl; property DestinationGroup: TdxLayoutGroup read FDestinationGroup write SetDestinationGroup; property DestinationItem: TdxCustomLayoutItem read FDestinationItem write SetDestinationItem; property Source: TdxLayoutDragSource read FSource write FSource; property SourceControl: TdxCustomLayoutControl read GetSourceControl; property SourceItemBounds: TRect read FSourceItemBounds; public procedure BeginDragAndDrop; override; procedure DragAndDrop(const P: TPoint; var Accepted: Boolean); override; procedure EndDragAndDrop(Accepted: Boolean); override; procedure Init(ASource: TdxLayoutDragSource; ASourceItem: TdxCustomLayoutItem); virtual; property SourceItem: TdxCustomLayoutItem read FSourceItem write SetSourceItem; end; { TdxLayoutDragAndDropHelper } TdxLayoutDragAndDropHelper = class(TdxLayoutControlPersistent) private FDragItem: TdxCustomLayoutItem; FMouseDownPos: TPoint; procedure BeginDragAndDrop(ASource: TdxLayoutDragSource); function CanBeginDragAndDrop(X, Y: Integer): Boolean; function GetDragAndDropObject: TdxLayoutControlDragAndDropObject; protected property DragAndDropObject: TdxLayoutControlDragAndDropObject read GetDragAndDropObject; public constructor Create(AControl: TdxCustomLayoutControl); override; procedure InitializeDragItem(AItem: TdxCustomLayoutItem; X, Y: Integer); procedure Reset; procedure TryBeginDragAndDrop(X, Y: Integer; ASource: TdxLayoutDragSource); property DragItem: TdxCustomLayoutItem read FDragItem; end; implementation uses Classes, SysUtils, Forms, cxLibraryConsts, cxGeometry; const dxLayoutSelectionBorderDefaultColor: TColor = $BD8753; dxLayoutDestinationColor = $D0D0EE; dxLayoutDestinationBorderColor = $000059; type TdxCustomLayoutControlAccess = class(TdxCustomLayoutControl); TdxCustomLayoutItemAccess = class(TdxCustomLayoutItem); TdxLayoutGroupAccess = class(TdxLayoutGroup); TdxCustomLayoutItemViewInfoAccess = class(TdxCustomLayoutItemViewInfo); TdxLayoutGroupViewInfoAccess = class(TdxLayoutGroupViewInfo); TdxLayoutControlViewInfoAccess = class(TdxLayoutControlViewInfo); TdxCustomLayoutItemPainterAccess = class(TdxCustomLayoutItemPainter); { TdxLayoutControlDragAndDropObject } function TdxLayoutControlDragAndDropObject.CanDrop: Boolean; begin Result := (DestinationItem <> nil) or (SourceControl.ViewInfo.GetHitTest(CurMousePos).HitTestCode in [{//#DG feature is not ready yet htTreeViewItems, }htClientArea, htGroup, htItem]); end; function TdxLayoutControlDragAndDropObject.CanRemove: Boolean; begin Result := (SourceControl.ViewInfo.GetHitTest(CurMousePos).HitTestCode in [htAvailableItems, htNone]) and TdxCustomLayoutItemAccess(SourceItem).CanRemove and (Source <> dsCustomizeFormAvailableItems); end; function TdxLayoutControlDragAndDropObject.GetSourceControl: TdxCustomLayoutControl; begin Result := TdxCustomLayoutControl(inherited Control); end; procedure TdxLayoutControlDragAndDropObject.ResetDragAndDropObjects; begin SourceItem := nil; DestinationItem := nil; DestinationGroup := nil; FHitTest := nil; end; procedure TdxLayoutControlDragAndDropObject.ResetDragAndDrop; begin ResetDragAndDropObjects; TdxCustomLayoutControlAccess(SourceControl).CustomizeFormUpdate([cfutContent, cfutSelection]); end; procedure TdxLayoutControlDragAndDropObject.SetAreaPart(Value: TdxLayoutAreaPart); begin if FAreaPart <> Value then begin FAreaPart := Value; Dirty := True; end; end; procedure TdxLayoutControlDragAndDropObject.SetDestinationControl(Value: TdxCustomLayoutControl); begin if FDestinationControl <> Value then begin FDestinationControl := Value; Dirty := True; end; end; procedure TdxLayoutControlDragAndDropObject.SetDestinationGroup(Value: TdxLayoutGroup); begin if FDestinationGroup <> Value then begin if FDestinationGroup <> nil then FreeAndNil(FDestinationImage); FDestinationGroup := Value; if FDestinationGroup <> nil then CreateDestinationImage; Dirty := True; end; end; procedure TdxLayoutControlDragAndDropObject.SetDestinationItem(Value: TdxCustomLayoutItem); begin if FDestinationItem <> Value then begin FDestinationItem := Value; Dirty := True; end; end; procedure TdxLayoutControlDragAndDropObject.SetSourceItem(Value: TdxCustomLayoutItem); begin if FSourceItem <> Value then FSourceItem := Value; end; procedure TdxLayoutControlDragAndDropObject.CreateDragImage; begin FDragImage := TcxDragImage.Create; PaintDragImage; end; procedure TdxLayoutControlDragAndDropObject.CreateDestinationImage; var R: TRect; begin if (DestinationItem = nil) or TdxCustomLayoutItemAccess(DestinationItem).IsAvailable then Exit; FDestinationImage := TcxDragImage.Create; FDestinationImage.PopupParent := GetParentForm(DestinationControl); R := DestinationGroup.ViewInfo.SelectionArea; OffsetRect(R, -R.Left, -R.Top); FDestinationImage.SetBounds(R.Left, R.Top, R.Right, R.Bottom); end; procedure TdxLayoutControlDragAndDropObject.DirtyChanged; begin inherited DirtyChanged; FNeedRepaintDestinationImage := True; end; function TdxLayoutControlDragAndDropObject.GetDragAndDropCursor(Accepted: Boolean): TCursor; begin if CanDrop then Result := crDefault else if CanRemove then Result := crdxLayoutControlRemove else Result := crdxLayoutControlNoDrop; end; procedure TdxLayoutControlDragAndDropObject.PaintDestinationImage; var R: TRect; begin FDestinationImage.Canvas.SaveState; try R := FDestinationImage.ClientRect; FDestinationImage.Canvas.FrameRect(R, dxLayoutDestinationBorderColor); InflateRect(R, -1, -1); FDestinationImage.Canvas.FillRect(R, dxLayoutDestinationColor); R := TdxLayoutGroupViewInfoAccess(DestinationGroup.ViewInfo).GetAreaPartBounds(AreaPart, DestinationItem.ViewInfo); with DestinationGroup.ViewInfo.SelectionArea do OffsetRect(R, -Left, -Top); FDestinationImage.Canvas.FrameRect(R, dxLayoutDestinationBorderColor); finally FDestinationImage.Canvas.RestoreState; end; FNeedRepaintDestinationImage := False; end; procedure TdxLayoutControlDragAndDropObject.PaintDragImage; var AViewInfo: TdxCustomLayoutItemViewInfo; function GetItemDragBounds: TRect; begin Result := AViewInfo.SelectionBorderRect; end; procedure CalculateDragImageOffset; begin if not SourceItem.ActuallyVisible then FDragImageOffset := cxNullPoint else begin case Source of dsControl: FDragImageOffset := cxPointOffset(GetItemDragBounds.TopLeft, SourceControl.ScreenToClient(FStartDragPoint), False); dsCustomizeFormTreeViewItems: FDragImageOffset := cxPointOffset(GetItemDragBounds.TopLeft, cxRectCenter(GetItemDragBounds), False); else FDragImageOffset := cxNullPoint; end; end; end; function GetViewInfoBounds(AHasBorderChanged: Boolean): TRect; begin if TdxCustomLayoutItemAccess(SourceItem).IsAvailable or not TdxCustomLayoutItemViewInfoAccess(AViewInfo).ActuallyVisible then Result := Rect(-TdxCustomLayoutItemViewInfoAccess(AViewInfo).CalculateMinWidth, -TdxCustomLayoutItemViewInfoAccess(AViewInfo).CalculateMinHeight, 0, 0) else begin Result := TdxCustomLayoutItemViewInfoAccess(AViewInfo).OriginalBounds; if AHasBorderChanged then Result := TdxLayoutGroupViewInfoAccess(AViewInfo).GetItemAreaBounds(Result); end; end; var ANeedDestroyViewInfo: Boolean; APrevHasBorder: Boolean; APrevBounds: TRect; begin if TdxCustomLayoutItemAccess(SourceItem).IsAvailable then begin AViewInfo := TdxCustomLayoutItemAccess(SourceItem).GetViewInfoClass.Create(SourceControl.ViewInfo, nil, SourceItem); ANeedDestroyViewInfo := True; end else begin AViewInfo := SourceItem.ViewInfo; ANeedDestroyViewInfo := False; APrevBounds := AViewInfo.Bounds; end; try APrevHasBorder := TdxCustomLayoutItemViewInfoAccess(SourceItem.ViewInfo).HasBorder; TdxLayoutControlViewInfoAccess(SourceControl.ViewInfo).IsDragImagePainted := True; try AViewInfo.Calculate(GetViewInfoBounds(TdxCustomLayoutItemViewInfoAccess(SourceItem.ViewInfo).HasBorder <> APrevHasBorder)); FDragImage.SetBounds(0, 0, cxRectWidth(GetItemDragBounds), cxRectHeight(GetItemDragBounds)); FDragImage.Canvas.WindowOrg := GetItemDragBounds.TopLeft; try with TdxCustomLayoutItemPainterAccess(TdxCustomLayoutItemViewInfoAccess(AViewInfo).GetPainterClass.Create(FDragImage.Canvas, AViewInfo)) do try Paint; DoDrawSelectionFrame; PaintSpecificPart; finally Free; end; finally FDragImage.Canvas.WindowOrg := cxNullPoint; end; CalculateDragImageOffset; finally TdxLayoutControlViewInfoAccess(SourceControl.ViewInfo).IsDragImagePainted := False; end; finally if ANeedDestroyViewInfo then AViewInfo.Destroy else AViewInfo.Calculate(APrevBounds); end; end; procedure TdxLayoutControlDragAndDropObject.RefreshDestinationImage; const CustomizeFormHitTestCodes = [htCustomizeForm, htAvailableItems, htTreeViewItems]; var P: TPoint; begin if (DestinationGroup = nil) or (DestinationItem = nil) or (AreaPart = apNone) or (FHitTest.HitTestCode in CustomizeFormHitTestCodes) then Exit; PaintDestinationImage; if FDestinationImage.Visible then FDestinationImage.Invalidate else begin P := DestinationControl.ClientToScreen(DestinationGroup.ViewInfo.SelectionArea.TopLeft); FDestinationImage.MoveTo(P); FDestinationImage.Show; end; end; procedure TdxLayoutControlDragAndDropObject.ShowDragImage(const P: TPoint); begin if FDragImage <> nil then begin FDragImage.MoveTo(P); FDragImage.Show; end; end; procedure TdxLayoutControlDragAndDropObject.BeginDragAndDrop; begin inherited BeginDragAndDrop; //#DG SourceItem.SelectComponent([]); TdxCustomLayoutControlAccess(SourceControl).DragAndDropBegan; CreateDragImage; end; procedure TdxLayoutControlDragAndDropObject.DragAndDrop(const P: TPoint; var Accepted: Boolean); function FindDestLayoutControl(const P: TPoint): TdxCustomLayoutControl; var AControl: TWinControl; AWnd: THandle; begin Result := nil; AWnd := cxWindowFromPoint(P); while (Result = nil) and (AWnd <> 0) do begin AControl := FindControl(AWnd); if (AControl is TdxCustomLayoutControl) and (AControl.Owner = SourceControl.Owner) and TdxCustomLayoutControlAccess(AControl).AllowDrop then Result := TdxLayoutControl(AControl); AWnd := GetAncestor(AWnd, GA_PARENT); end; if Result = nil then Result := SourceControl; end; procedure DetermineDestinationControl(const P: TPoint); begin DestinationControl := FindDestLayoutControl(P); end; procedure DetermineDestinationItem; begin // DetermineDestinationItems(ADestItems); // UpdatedestinationPlaces(AScreenPoint, ADestItems, ADestinationItem); DestinationItem := FHitTest.GetDesinationItem; if (DestinationItem = SourceItem) or ((DestinationItem is TdxLayoutItem) and TdxCustomLayoutItemAccess(DestinationItem).IsAvailable and (DestinationItem.Parent = nil)) or ((DestinationItem is TdxLayoutGroup) and not SourceItem.CanMoveTo(DestinationItem)) then DestinationItem := nil; end; procedure DetermineAreaPart(const P: TPoint); const CustomizeFormTreeViewItemAreaPart: array [TdxLayoutDirection] of TdxLayoutAreaPart = (apRight, apBottom, apBottom); var AAreaPart: TdxLayoutAreaPart; begin AAreaPart := apNone; if FHitTest.IsDeterminedAreaPart then AAreaPart := FHitTest.GetAreaPart else if (DestinationItem <> nil) then AAreaPart := TdxCustomLayoutItemViewInfoAccess(DestinationItem.ViewInfo).GetAreaPart(P); AreaPart := AAreaPart; end; procedure DetermineDestinationGroup; begin if DestinationItem = nil then DestinationGroup := nil else begin if DestinationItem.IsRoot or (AreaPart in [apCenter, apFirstChild, apLastChild]) then DestinationGroup := TdxLayoutGroup(DestinationItem) else DestinationGroup := DestinationItem.Parent; end; end; procedure DetermineHitTest(const P: TPoint); begin if IsWindowVisible(DestinationControl.Handle) then FHitTest := DestinationControl.ViewInfo.GetHitTest(P) else FHitTest := SourceControl.ViewInfo.GetHitTest(P); end; procedure UpdateDestinationImageState; begin if FNeedRepaintDestinationImage then RefreshDestinationImage; end; var AScreenPoint, ADestPoint, ADragImagePoint: TPoint; begin AScreenPoint := Control.ClientToScreen(P); DetermineDestinationControl(AScreenPoint); ADestPoint := DestinationControl.ScreenToClient(AScreenPoint); ADragImagePoint := cxPointOffset(AScreenPoint, FDragImageOffset); ShowDragImage(ADragImagePoint); DetermineHitTest(ADestPoint); DetermineDestinationItem; DetermineAreaPart(ADestPoint); DetermineDestinationGroup; Accepted := AreaPart <> apNone; UpdateDestinationImageState; inherited; end; procedure TdxLayoutControlDragAndDropObject.EndDragAndDrop(Accepted: Boolean); function GetDestPosition: Integer; begin case AreaPart of apRight, apBottom, apAfter: if DestinationItem = DestinationGroup then Result := DestinationGroup.VisibleCount else Result := DestinationItem.VisibleIndex + 1; apLeft, apTop, apBefore: if DestinationItem = DestinationGroup then Result := 0 else Result := DestinationItem.VisibleIndex; apLastChild: Result := DestinationGroup.VisibleCount; else Result := 0; end; if (SourceItem.Parent = DestinationGroup) and (SourceItem.VisibleIndex < Result) then Dec(Result); end; function GetLayoutDirection: TdxLayoutDirection; begin Result := DestinationGroup.LayoutDirection; end; function GetOrthogonalDirection: TdxLayoutDirection; begin Result := TdxLayoutGroupAccess(DestinationItem.Parent).GetHelperClass.GetOrthogonalDirection; end; function IsHorizontalAreaPart: Boolean; begin Result := AreaPart in [apLeft, apRight, apAfter, apBefore]; end; function IsVerticalAreaPart: Boolean; begin Result := AreaPart in [apTop, apBottom, apAfter, apBefore]; end; procedure DoInsert; begin SourceItem.MoveTo(DestinationGroup, GetDestPosition, True); end; procedure DoCreateGroup; const LayoutDirections: array[Boolean] of TdxLayoutDirection = (ldVertical, ldHorizontal); var AGroup: TdxLayoutGroup; begin if DestinationItem.IsRoot then begin DestinationGroup.PutChildrenIntoHiddenGroup; DestinationGroup.LayoutDirection := LayoutDirections[IsHorizontalAreaPart]; SourceItem.MoveTo(DestinationGroup, GetDestPosition, True); end else begin AGroup := DestinationItem.PutIntoHiddenGroup(GetOrthogonalDirection); SourceItem.MoveTo(AGroup, GetDestPosition, True); end; end; procedure DoContentInsert; begin DestinationGroup.PutChildrenIntoHiddenGroup; DestinationGroup.LayoutDirection := GetOrthogonalDirection; SourceItem.MoveTo(DestinationGroup, GetDestPosition, True); end; function CanProcessDrop: Boolean; begin Result := Accepted and (DestinationControl <> nil) and ((CanDrop and (DestinationItem <> nil)) or CanRemove); end; var ANeedUpdateCustomizeForm: Boolean; ALinkSelf: TcxObjectLink; begin FreeAndNil(FDragImage); Dirty := True; if CanProcessDrop then begin ANeedUpdateCustomizeForm := False; TdxCustomLayoutControlAccess(SourceControl).CustomizeFormBeginUpdate; TdxCustomLayoutControlAccess(DestinationControl).CustomizeFormBeginUpdate; try TdxCustomLayoutControlAccess(DestinationControl).SaveToUndo; DestinationControl.BeginUpdate; TdxCustomLayoutControlAccess(SourceControl).SaveToUndo; SourceControl.BeginUpdate; try if CanDrop and (DestinationGroup <> nil) then begin ANeedUpdateCustomizeForm := True; case TdxLayoutGroupViewInfoAccess(DestinationGroup.ViewInfo).GetActionType(AreaPart) of atInsert: DoInsert; atCreateGroup: DoCreateGroup; atContentInsert: DoContentInsert; end; end else if CanRemove then begin SourceItem.Parent := nil; ANeedUpdateCustomizeForm := True; end; finally ALinkSelf := cxAddObjectLink(SourceItem); try ResetDragAndDropObjects; SourceControl.EndUpdate; DestinationControl.EndUpdate; if ALinkSelf.Ref <> nil then (ALinkSelf.Ref as TdxCustomLayoutItem).MakeVisible; finally cxRemoveObjectLink(ALinkSelf); end; end; finally TdxCustomLayoutControlAccess(SourceControl).CustomizeFormCancelUpdate; TdxCustomLayoutControlAccess(DestinationControl).CustomizeFormCancelUpdate; if ANeedUpdateCustomizeForm then begin TdxCustomLayoutControlAccess(SourceControl).CustomizeFormUpdate([cfutContent, cfutSelection]); if (DestinationControl <> SourceControl) and TdxCustomLayoutControlAccess(DestinationControl).IsCustomization then TdxCustomLayoutControlAccess(DestinationControl).CustomizeFormUpdate([cfutContent, cfutSelection]); end; end; SourceControl.Update; if DestinationControl <> SourceControl then DestinationControl.Update; TdxCustomLayoutControlAccess(SourceControl).Modified; end else ResetDragAndDrop; inherited; end; procedure TdxLayoutControlDragAndDropObject.Init(ASource: TdxLayoutDragSource; ASourceItem: TdxCustomLayoutItem); begin SourceControl.Update; Source := ASource; SourceItem := ASourceItem; FStartDragPoint := GetMouseCursorPos; end; { TdxLayoutDragAndDropHelper } constructor TdxLayoutDragAndDropHelper.Create( AControl: TdxCustomLayoutControl); begin inherited Create(AControl); Reset; end; procedure TdxLayoutDragAndDropHelper.InitializeDragItem(AItem: TdxCustomLayoutItem; X, Y: Integer); begin FMouseDownPos := Point(X, Y); FDragItem := AItem; end; procedure TdxLayoutDragAndDropHelper.Reset; begin FDragItem := nil; FMouseDownPos := cxInvalidPoint; end; procedure TdxLayoutDragAndDropHelper.TryBeginDragAndDrop(X, Y: Integer; ASource: TdxLayoutDragSource); begin if CanBeginDragAndDrop(X, Y) then BeginDragAndDrop(ASource); end; function TdxLayoutDragAndDropHelper.CanBeginDragAndDrop(X, Y: Integer): Boolean; begin Result := TdxCustomLayoutControlAccess(Control).CanDragAndDrop and (Control.DragAndDropState = ddsNone) and not cxPointIsEqual(FMouseDownPos, cxInvalidPoint) and not IsPointInDragDetectArea(FMouseDownPos, X, Y) and (DragItem <> nil) and TdxCustomLayoutItemAccess(DragItem).CanDragAndDrop; end; procedure TdxLayoutDragAndDropHelper.BeginDragAndDrop(ASource: TdxLayoutDragSource); begin DragAndDropObject.Init(ASource, DragItem); Control.BeginDragAndDrop; end; function TdxLayoutDragAndDropHelper.GetDragAndDropObject: TdxLayoutControlDragAndDropObject; begin Result := TdxLayoutControlDragAndDropObject(Control.DragAndDropObject); end; initialization Screen.Cursors[crdxLayoutControlNoDrop] := LoadCursor(HInstance, 'DXLAYOUTCONTROLNODROP'); Screen.Cursors[crdxLayoutControlRemove] := LoadCursor(HInstance, 'DXLAYOUTCONTROLREMOVE'); end.