1 unit sparta_BasicResizeFrame;
2 
3 {$mode delphi}{$H+}
4 
5 interface
6 
7 uses
8   Classes, contnrs, SysUtils, FileUtil, Forms, Controls, ExtCtrls, StdCtrls,
9   Graphics, LCLType, lclintf, Menus, LMessages, Math, Types, sparta_InterfacesMDI;
10 
11 type
12   TPositioningCode = (pcPositioning, pcPositioningEnd);
13   TPositioningKind = set of (pkBottom, pkRight);
14   TPositioningEvent = procedure(Sender: TObject; PositioningKind: TPositioningKind; PositioningCode: TPositioningCode) of object;
15 
16   { TBasicResizeFrame }
17 
18   TResizerFrameClass = class of TBasicResizeFrame;
19   TBasicResizeFrame = class(TFrame, IResizeFrame)
20     iResizerLineImg: TImage;
21     pFormHandler: TPanel;
22     pFakeMenu: TPanel;
23     pBG: TPanel;
24     pB: TPanel;
25     pClient: TPanel;
26     pL: TPanel;
27     pMarginB: TPanel;
28     pMarginL: TPanel;
29     pMarginR: TPanel;
30     pMarginT: TPanel;
31     pR: TPanel;
32     pT: TPanel;
33     procedure pBGPaint(Sender: TObject);
34     procedure pFakeMenuPaint(Sender: TObject);
35     procedure sbVerticalScroll(Sender: TObject; ScrollCode: TScrollCode;
36       var ScrollPos: Integer);
37     procedure sbHorizontalScroll(Sender: TObject; ScrollCode: TScrollCode;
38       var ScrollPos: Integer);
39   public const
40     SIZER_RECT_SIZE = 8;
41     SIZER_LINE_WIDTH = 8;
42   private
43     FVerticalScrollPos: Integer;
44     FHorizontalScrollPos: Integer;
45     FDesignedForm: IDesignedForm;
46     FBackground: IDesignedFormBackground;
47     FFakeFocusControl: TWinControl;
48 
49     procedure FakeExitEnter(Sender: TObject);
50     procedure FakeKeyDown(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
51     procedure FakeKeyUp(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
52     procedure FakeUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);
53   private
54     { private declarations }
55     FOnNodePositioning: TPositioningEvent;
56     FOnHorizontalScroll, FOnVerticalScroll: TScrollEvent;
57     FLastRightMarign: Integer;
58     FLastBottomMarign: Integer;
59     FNodePositioning: Boolean;
60     FOldPos, FDelta: TPoint;
61     FPositioningKind: TPositioningKind;
62     FMaxWidth, FMaxHeight: Integer;
63     FLastClientWidth, FLastClientHeight: Integer;
64     FLastDesignedWidthToScroll, FLastDesignedHeightToScroll: Integer;
65     FOldHasMainMenu: Boolean;
66     FDesignerModified: Boolean;
67     FSizerLineWidth: Integer;
68     FSizerRectSize: Integer;
69 
HasMainMenunull70     function HasMainMenu: Boolean;
71     procedure AppOnIdle(Sender: TObject; var {%H-}Done: Boolean);
72 
73     procedure PanelPaint(Sender: TObject);
74     procedure BGChangeBounds(Sender: TObject);
75 
76     procedure CreateNodes;
77     procedure NodeMouseDown(Sender: TObject; {%H-}Button: TMouseButton; {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);
78     procedure NodeMouseMove(Sender: TObject; {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);
79     procedure NodeMouseUp(Sender: TObject; {%H-}Button: TMouseButton; {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);
80 
GetRightMarginnull81     function GetRightMargin: Integer;
GetBottomMarginnull82     function GetBottomMargin: Integer;
83 
84     // dependent on scroll position
85     // for Vertical
BottomSizerRectHeightnull86     function BottomSizerRectHeight: Integer;
BottomSizerLineWidthnull87     function BottomSizerLineWidth: Integer;
TopSizerRectTopnull88     function TopSizerRectTop: Integer;
TopSizerLineWidthnull89     function TopSizerLineWidth: Integer;
VerticalSizerLineLengthnull90     function VerticalSizerLineLength: Integer;
91     // for Horizontal
RightSizerRectWidthnull92     function RightSizerRectWidth: Integer;
RightSizerLineWidthnull93     function RightSizerLineWidth: Integer;
LeftSizerRectLeftnull94     function LeftSizerRectLeft: Integer;
LeftSizerLineWidthnull95     function LeftSizerLineWidth: Integer;
HorizontalSizerLineLengthnull96     function HorizontalSizerLineLength: Integer;
97 
98     procedure AdjustFormHandler;
99 
GetMenuHeightnull100     function GetMenuHeight: Integer;
101   protected
102     FNodes: TObjectList;
103   protected
104     procedure TryBoundDesignedForm; virtual;
105     procedure BeginFormSizeUpdate(Sender: TObject); virtual;
106     procedure EndFormSizeUpdate(Sender: TObject); virtual;
107   protected { IResizeFrame }
108     procedure HideSizeRects;
109     procedure ShowSizeRects;
110     procedure PositionNodes; overload;
DesignedWidthToScrollnull111     function DesignedWidthToScroll: Integer;
DesignedHeightToScrollnull112     function DesignedHeightToScroll: Integer;
113     procedure ClientChangeBounds; overload;
114 
GetFramenull115     function GetFrame: TCustomFrame;
GetVerticalScrollPosnull116     function GetVerticalScrollPos: Integer;
117     procedure SetVerticalScrollPos(AValue: Integer);
GetHorizontalScrollPosnull118     function GetHorizontalScrollPos: Integer;
119     procedure SetHorizontalScrollPos(AValue: Integer);
120 
GetBackgroundPanelnull121     function GetBackgroundPanel: TPanel;
GetBackgroundMarginnull122     function GetBackgroundMargin(const AIndex: Integer): Integer;
123 
GetNewSizenull124     function GetNewSize: TPoint;
GetFormHandlernull125     function GetFormHandler: TPanel;
GetNodePositioningnull126     function GetNodePositioning: Boolean;
GetDesignedFormnull127     function GetDesignedForm: IDesignedForm;
128     procedure SetDesignedForm(const AValue: IDesignedForm);
129 
GetSizerRectSizenull130     function GetSizerRectSize: Integer;
GetSizerLineWidthnull131     function GetSizerLineWidth: Integer;
132   public { IResizeFrame }
133     procedure DesignerSetFocus;
134   public
135     constructor Create(TheOwner: TComponent); override;
136     destructor Destroy; override;
137 
138     property DesignedForm: IDesignedForm read GetDesignedForm write SetDesignedForm;
139 
140     procedure PositionNodes(AroundControl: TWinControl); overload;
141     property NodePositioning: Boolean read GetNodePositioning;
142     procedure ClientChangeBounds(Sender: TObject); overload;
143 
144     property RightMargin: Integer read GetRightMargin;
145     property BottomMargin: Integer read GetBottomMargin;
146     property OnNodePositioning: TPositioningEvent read FOnNodePositioning write FOnNodePositioning;
147 
148     property BgLeftMargin: Integer index 0 read GetBackgroundMargin;
149     property BgTopMargin: Integer index 1 read GetBackgroundMargin;
150     property BgRightMargin: Integer index 2 read GetBackgroundMargin;
151     property BgBottomMargin: Integer index 3 read GetBackgroundMargin;
152     property SizerRectSize: Integer read FSizerRectSize;
153     property SizerLineWidth: Integer read FSizerLineWidth;
154 
155     procedure HideSizeControls;
156     procedure ShowSizeControls;
157 
158     procedure OnModified;
159 
160     property VerticalScrollPos: Integer read GetVerticalScrollPos write SetVerticalScrollPos;
161     property HorizontalScrollPos: Integer read GetHorizontalScrollPos write SetHorizontalScrollPos;
162   end;
163 
164 implementation
165 
166 {$R *.lfm}
167 
168 { Node grip indices are as follows:
169 
170        1
171 0 +----+----+ 2
172   |         |
173 7 +         + 3
174   |         |
175 6 +----+----+ 4
176        5
177 
178 Only grips 3, 4, and 5 are sizeable }
179 
180 procedure TBasicResizeFrame.pFakeMenuPaint(Sender: TObject);
181 var
182   MenuRect: Types.TRect;
183   Menu: TMainMenu;
184   X, Y, I: Integer;
185   LCanvas: TCanvas;
186 begin
187   //fake paint menu
188 
189   MenuRect := pFakeMenu.ClientRect;
190   LCanvas := pFakeMenu.Canvas;
191   LCanvas.Brush.Color := clMenuBar;
192   LCanvas.FillRect(MenuRect);
193 
194   // pFakeMenu is visible only when HasMainMenu is true
195   // but FDesignedForm can be nil if the designer is painted before it has been assigned
196   if not HasMainMenu then
197     Exit;
198 
199   Menu := FDesignedForm.Form.Menu;
200   LCanvas.Font.Color := clMenuText;
201 
202   X := 5;
203   Y := (MenuRect.Top+MenuRect.Bottom-LCanvas.TextHeight('Hg')) div 2;
204   for I := 0 to Menu.Items.Count-1 do
205     if Menu.Items[I].Visible then
206     begin
207       LCanvas.TextOut(X, Y, Menu.Items[I].Caption);
208       Inc(X, LCanvas.TextWidth(Menu.Items[I].Caption) + 10);
209     end;
210 end;
211 
212 procedure TBasicResizeFrame.pBGPaint(Sender: TObject);
213 begin
214   pBG.SendToBack;
215 end;
216 
217 procedure TBasicResizeFrame.sbVerticalScroll(Sender: TObject;
218   ScrollCode: TScrollCode; var ScrollPos: Integer);
219 begin
220   if ScrollCode <> scEndScroll then
221     HideSizeRects
222   else
223     ShowSizeRects;
224 
225   FVerticalScrollPos := ScrollPos;
226 
227   PositionNodes(Self);
228 
229   if Assigned(FOnVerticalScroll)
230     // for refresh from this class, pass sender as nil.
231     // In other case program will go into infinity loop
232     and (Sender <> nil) then
233     FOnVerticalScroll(Sender, ScrollCode, ScrollPos);
234 end;
235 
236 procedure TBasicResizeFrame.sbHorizontalScroll(Sender: TObject;
237   ScrollCode: TScrollCode; var ScrollPos: Integer);
238 begin
239   if ScrollCode <> scEndScroll then
240     HideSizeRects
241   else
242     ShowSizeRects;
243 
244   FHorizontalScrollPos := ScrollPos;
245 
246   PositionNodes(Self);
247 
248   if Assigned(FOnHorizontalScroll)
249     // for refresh from this class, pass sender as nil.
250     // In other case program will go into infinity loop
251     and (Sender <> nil) then
252     FOnHorizontalScroll(Sender, ScrollCode, ScrollPos);
253 end;
254 
255 { TResizerFrame }
256 
257 // Tiles the source image over the given target canvas
258 procedure TileImage(const ASource: TImage; ATarget: TCanvas; AX, AY,
259   AWidth, AHeight: Integer);
260 var
261   LX, LY, LDeltaX, LDeltaY: Integer;
262 begin
263   LDeltaX := ASource.Width;
264   LDeltaY := ASource.Height;
265   LY := 0;
266   while LY < AHeight do
267   begin
268     LX := 0;
269     while LX < AWidth do
270     begin
271       ATarget.Draw(AX + LX, AY + LY, ASource.Picture.graphic);
272       Inc(LX, LDeltaX);
273     end;
274     Inc(LY, LDeltaY);
275   end;
276 end;
277 
278 procedure TBasicResizeFrame.PanelPaint(Sender: TObject);
279 var
280   LWidth, LHeight: Integer;
281   LOldColor: TColor;
282   LCanvas: TCanvas;
283 begin
284   if FNodePositioning then
285     Exit;
286   if (Sender = pR) or (Sender = pL) then
287   begin
288     LWidth := SizerLineWidth;
289     LHeight := Height;
290   end else
291   begin
292     LWidth := Width;
293     LHeight := SizerLineWidth;
294   end;
295   LCanvas := (Sender as TPanel).Canvas;
296   if FFakeFocusControl.Focused then
297   begin
298     LOldColor := LCanvas.Brush.Color;
299     LCanvas.Brush.Color := $FFEEDD;
300     LCanvas.FillRect(0, 0, LWidth, LHeight);
301     LCanvas.Brush.Color := LOldColor;
302   end;
303   TileImage(iResizerLineImg, LCanvas, 0, 0, LWidth, LHeight);
304 end;
305 
306 procedure TBasicResizeFrame.ClientChangeBounds(Sender: TObject);
307 begin
308   if (DesignedForm = nil) or FNodePositioning then
309     Exit;
310 
311   FLastClientWidth  := pClient.Width;
312   FLastClientHeight := pClient.Height;
313 
314 (*
315   DesignedForm.BeginUpdate;
316 
317   DesignedForm.RealLeft := 0;
318   DesignedForm.RealTop := 0;
319   DesignedForm.RealWidth  := pClient.Width;
320   DesignedForm.RealHeight := pClient.Height;
321   DesignedForm.EndUpdate;
322 *)
323 end;
324 
325 procedure TBasicResizeFrame.BGChangeBounds(Sender: TObject);
326 begin
327   PositionNodes(Self);
328 end;
329 
330 procedure TBasicResizeFrame.HideSizeRects;
331 var
332   p: TObject;
333   wc: TWinControl absolute p;
334 begin
335   for p in FNodes do
336     if not (wc is TPanel) then
337       wc.Visible := False;
338 end;
339 
340 procedure TBasicResizeFrame.HideSizeControls;
341 begin
342   pL.Repaint;
343   pT.Repaint;
344   pR.Repaint;
345   pB.Repaint;
346 
347   HideSizeRects;
348   pBG.SendToBack;
349 end;
350 
351 procedure TBasicResizeFrame.ShowSizeRects;
352 var
353   p: TObject;
354   wc: TWinControl absolute p;
355 begin
356   for p in FNodes do
357     wc.Visible := True;
358 end;
359 
360 procedure TBasicResizeFrame.PositionNodes;
361 begin
362   PositionNodes(Self);
363 end;
364 
365 procedure TBasicResizeFrame.ShowSizeControls;
366 begin
367   pL.Repaint;
368   pT.Repaint;
369   pR.Repaint;
370   pB.Repaint;
371 
372   ShowSizeRects;
373   //pBG.Visible := True;
374 end;
375 
376 procedure TBasicResizeFrame.CreateNodes;
377 var
378   Node: Integer;
379   Panel: TPanel;
380 begin
381   for Node := 0 to 7 do
382   begin
383     Panel := TPanel.Create(self);
384     with Panel do
385     begin
386       BevelOuter := bvNone;
387       Color := clBlack;
388 
389       Name := 'Node' + IntToStr(Node);
390       Caption:='';
391       Width := SIZER_RECT_SIZE;  // scaled dynamically by LCL
392       Height := SIZER_RECT_SIZE; // scaled dynamically by LCL
393       Parent := Self;
394       Visible := True;
395       FNodes.Add(Panel);
396 
397       case Node of
398         // on mac there is no cursor for crNWSE ( https://bugs.freepascal.org/view.php?id=32194#c101876 )
399         {0,}4: Cursor := {$IFDEF MACOS}crSizeAll{$ELSE}crSizeNWSE{$ENDIF};
400         {1,}5: Cursor := crSizeNS;
401         //{2,}6: Cursor := $IFDEF MACOS}crSizeAll{$ELSE}crSizeNESW{$ENDIF};
402         3{,7}: Cursor := crSizeWE;
403       end;
404       if Node in [3,4,5] then
405       begin
406         OnMouseDown := NodeMouseDown;
407         OnMouseMove := NodeMouseMove;
408         OnMouseUp := NodeMouseUp;
409       end;
410 
411       with TShape.Create(Panel) do
412       begin
413         Parent := Panel;
414         Align:= alClient;
415         Enabled := False;
416 
417         if Node in [3,4,5] then
418           Brush.Color:=clBtnFace
419         else
420           Brush.Color:=clGray;
421       end;
422     end;
423   end;
424   // extra resizers
425   pB.OnMouseDown := NodeMouseDown;
426   pB.OnMouseMove := NodeMouseMove;
427   pB.OnMouseUp := NodeMouseUp;
428 
429   pR.OnMouseDown := NodeMouseDown;
430   pR.OnMouseMove := NodeMouseMove;
431   pR.OnMouseUp := NodeMouseUp;
432 
433   FNodes.Add(pL);
434   FNodes.Add(pT);
435   FNodes.Add(pR);
436   FNodes.Add(pB);
437 end;
438 
439 procedure TBasicResizeFrame.NodeMouseDown(Sender: TObject; Button: TMouseButton;
440   Shift: TShiftState; X, Y: Integer);
441 var
442   LCtrlPoint: TPoint;
443 begin
444   { TShape in TBasicResizeFrame.CreateNodes is disabled, anyway in future can be usefull
445   if Sender is TGraphicControl then
446     Sender := TGraphicControl(Sender).Parent;}
447 
448   if (Enabled) and (Sender is TWinControl) then
449   begin
450     FNodePositioning:=True;
451     BeginFormSizeUpdate(Sender);
452 
453     // when we start resizing the rules do not apply to us :)
454     FMaxWidth := Constraints.MaxWidth;
455     FMaxHeight := Constraints.MaxHeight;
456     Constraints.MaxWidth := 0;
457     Constraints.MaxHeight := 0;
458     with pClient do
459     begin
460       Align := alClient;
461       if pBG.Left + BgLeftMargin <= 0 then
462         BorderSpacing.Left := Max(-pBG.Left - (FHorizontalScrollPos - SizerRectSize), 0)
463       else
464         BorderSpacing.Left := Max(pBG.Left + BgLeftMargin, 0);
465 
466       if pBG.Top + BgTopMargin <= 0 then
467         BorderSpacing.Top := Max(-pBG.Top - (FVerticalScrollPos - SizerRectSize), 0)
468       else
469         BorderSpacing.Top := Max(pBG.Top + BgTopMargin, 0);
470 
471       BorderSpacing.Right := Max(Self.Width - (pR.Left - BgRightMargin), 0);
472       BorderSpacing.Bottom := Max(Self.Height - (pB.Top - BgBottomMargin), 0);
473     end;
474 
475 
476     {$IF Defined(LCLWin32) or Defined(LCLWin64)}
477     SetCapture(TWinControl(Sender).Handle);
478     {$ENDIF}
479     GetCursorPos(FOldPos);
480     // perform first "click delta" to reduce leap
481     // + calculate delta created by scrollbars and theirs position...
482     FillChar(FDelta, SizeOf(FDelta), #0);
483     LCtrlPoint := (Sender as TWinControl).ScreenToClient(Mouse.CursorPos);
484     if Sender = pR then
485     begin
486       FDelta.X := -(LCtrlPoint.x - RightSizerLineWidth) + RightMargin + Left;
487       FPositioningKind := [pkRight];
488     end
489     else if Sender = pB then
490     begin
491       FDelta.Y := -(LCtrlPoint.y - BottomSizerLineWidth) + BottomMargin + Top;
492       FPositioningKind := [pkBottom];
493     end
494     else
495       case FNodes.IndexOf(Sender) of
496         3: // middle right
497           begin
498             FDelta.X := -(LCtrlPoint.x - RightSizerRectWidth) + RightMargin + Left;
499             FPositioningKind := [pkRight];
500           end;
501         4: // right bottom
502           begin
503             FDelta.X := -(LCtrlPoint.x - RightSizerRectWidth) + RightMargin + Left;
504             FDelta.Y := -(LCtrlPoint.y - BottomSizerRectHeight) + BottomMargin + Top;
505             FPositioningKind := [pkRight, pkBottom];
506           end;
507         5: // middle bottom
508           begin
509             FDelta.Y := -(LCtrlPoint.y - BottomSizerRectHeight) + BottomMargin + Top;
510             FPositioningKind := [pkBottom];
511           end;
512       end;
513   end;
514 end;
515 
516 procedure TBasicResizeFrame.NodeMouseMove(Sender: TObject; Shift: TShiftState; X,
517   Y: Integer);
518 var
519   newPos: TPoint;
520   frmPoint : TPoint;
521   OldRect: TRect;
522   AdjL,AdjR,AdjT,AdjB: Boolean;
523 begin
524   { TShape in TBasicResizeFrame.CreateNodes is disabled, anyway in future can be usefull
525   // handle TPanel for resizing rectangles
526   if Sender is TGraphicControl then
527     Sender := TGraphicControl(Sender).Parent;}
528 
529   if FNodePositioning then
530   begin
531     with TWinControl(Sender) do
532     begin
533       newPos := Point(0, 0);
534       GetCursorPos(newPos);
535 
536       if (newPos.x = FOldPos.x) and (newPos.y = FOldPos.y) then
537         Exit;
538 
539       HideSizeControls;
540       UpdateWindow(pBG.Handle);
541       UpdateWindow(Self.Handle);
542       UpdateWindow(Self.Parent.Handle);
543       with Self do
544       begin //resize
545         frmPoint := Self.ScreenToClient(Mouse.CursorPos);
546         frmPoint.x:= frmPoint.x + FDelta.x;
547         frmPoint.y:= frmPoint.y + FDelta.y;
548 
549         OldRect := Self.BoundsRect;
550         AdjL := False;
551         AdjR := False;
552         AdjT := False;
553         AdjB := False;
554         case FNodes.IndexOf(TWinControl(Sender)) of
555           0: begin
556                //AdjL := True;
557                //AdjT := True;
558              end;
559           1: begin
560                //AdjT := True;
561              end;
562           2: begin
563                //AdjR := True;
564                //AdjT := True;
565              end;
566           3, 10: begin
567                AdjR := True;
568              end;
569           4: begin
570                AdjR := True;
571                AdjB := True;
572              end;
573           5, 11: begin
574                AdjB := True;
575              end;
576           6: begin
577                //AdjL := True;
578                //AdjB := True;
579              end;
580           7: begin
581                //AdjL := True;
582              end;
583         end;
584 
585         if AdjL then
586           OldRect.Left := frmPoint.X;
587         if AdjR then
588           OldRect.Right := frmPoint.X;
589         if AdjT then
590           OldRect.Top := frmPoint.Y;
591         if AdjB then
592           OldRect.Bottom := frmPoint.Y;
593 
594         SetBounds(OldRect.Left,OldRect.Top,OldRect.Right - OldRect.Left,OldRect.Bottom - OldRect.Top);
595       end;
596       //move node
597       Left := Left - FOldPos.X + newPos.X;
598       Top := Top - FOldPos.Y + newPos.Y;
599       FOldPos := newPos;
600     end;
601     PositionNodes(Self);
602     if Assigned(OnNodePositioning) then
603       OnNodePositioning(Self, FPositioningKind, pcPositioning);
604 
605     // the same operation as belowe exist in ClientChangeBounds but it is
606     // disabled for FNodePositioning = true
607     // we need to refresh this values after OnNodePositioning
608     FLastClientWidth := pClient.Width;
609     FLastClientHeight:= pClient.Height;
610   end;
611 end;
612 
613 procedure TBasicResizeFrame.NodeMouseUp(Sender: TObject; Button: TMouseButton;
614   Shift: TShiftState; X, Y: Integer);
615 begin
616   { TShape in TBasicResizeFrame.CreateNodes is disabled, anyway in future can be usefull
617   if Sender is TGraphicControl then
618     Sender := TGraphicControl(Sender).Parent;}
619 
620   if FNodePositioning then
621   begin
622     Screen.Cursor := crDefault;
623     {$IF Defined(LCLWin32) or Defined(LCLWin64)}
624     ReleaseCapture;
625     {$ENDIF}
626 
627     Constraints.MaxWidth := FMaxWidth;
628     Constraints.MaxHeight := FMaxHeight;
629     FNodePositioning := False;
630     ShowSizeControls;
631     if Assigned(OnNodePositioning) then
632       OnNodePositioning(Sender, FPositioningKind, pcPositioningEnd);
633     FPositioningKind := [];
634     FNodePositioning := False;
635 
636     pClient.Align := alNone;
637     BorderSpacing.Left := 0;
638     BorderSpacing.Top := 0;
639     BorderSpacing.Right := 0;
640     BorderSpacing.Bottom := 0;
641     PositionNodes(Self);
642 
643     EndFormSizeUpdate(Sender);
644 
645     // after resizing, TFrame is frozen in Windows OS
646     // this is trick to workaraund IDE bug. Also for proper size for normal form
647     TryBoundDesignedForm;
648     // for small resizes, designed form is moved on the top and on the bottom
649     // is showed white block - to stop this we need to move pClient to right position
650     PositionNodes;
651     ShowSizeControls;
652   end;
653 end;
654 
655 procedure TBasicResizeFrame.OnModified;
656 begin
657   FDesignerModified := True;
658 end;
659 
TBasicResizeFrame.GetRightMarginnull660 function TBasicResizeFrame.GetRightMargin: Integer;
661 begin
662   if not FNodePositioning then
663     FLastRightMarign := Width - (pR.Left + pR.Width);
664   Result := FLastRightMarign;
665 end;
666 
HasMainMenunull667 function TBasicResizeFrame.HasMainMenu: Boolean;
668 var
669   I: Integer;
670 begin
671   Result := False;
672   if  (FDesignedForm<>nil) and (FDesignedForm.Form.Menu<>nil)
673   and not (csDestroying in FDesignedForm.Form.Menu.ComponentState)
674   and (FDesignedForm.Form.Menu.Items.Count>0)
675   then
676     for I := 0 to FDesignedForm.Form.Menu.Items.Count-1 do
677       if FDesignedForm.Form.Menu.Items[I].Visible then
678         Exit(True);
679 end;
680 
TBasicResizeFrame.GetBottomMarginnull681 function TBasicResizeFrame.GetBottomMargin: Integer;
682 begin
683   if not FNodePositioning then
684     FLastBottomMarign := Height - (pB.Top + pB.Height);
685   Result := FLastBottomMarign;
686 end;
687 
688 {-----------------------------------------------------------------------------------------------------------------------
689   for Vertical scroll
690 {----------------------------------------------------------------------------------------------------------------------}
691 
BottomSizerRectHeightnull692 function TBasicResizeFrame.BottomSizerRectHeight: Integer;
693 begin
694   Result := SizerRectSize;
695 end;
696 
TBasicResizeFrame.BottomSizerLineWidthnull697 function TBasicResizeFrame.BottomSizerLineWidth: Integer;
698 begin
699   Result := SizerLineWidth;
700 end;
701 
TBasicResizeFrame.TopSizerRectTopnull702 function TBasicResizeFrame.TopSizerRectTop: Integer;
703 begin
704   Result := -FVerticalScrollPos;
705 end;
706 
TopSizerLineWidthnull707 function TBasicResizeFrame.TopSizerLineWidth: Integer;
708 begin
709   Result := SizerLineWidth;
710 end;
711 
VerticalSizerLineLengthnull712 function TBasicResizeFrame.VerticalSizerLineLength: Integer;
713 begin
714   Result := Height - BottomMargin;
715 end;
716 
717 {-----------------------------------------------------------------------------------------------------------------------
718   for Horizontal scroll
719 {----------------------------------------------------------------------------------------------------------------------}
720 
TBasicResizeFrame.RightSizerRectWidthnull721 function TBasicResizeFrame.RightSizerRectWidth: Integer;
722 begin
723   Result := SizerRectSize;
724 end;
725 
RightSizerLineWidthnull726 function TBasicResizeFrame.RightSizerLineWidth: Integer;
727 begin
728   Result := SizerLineWidth;
729 end;
730 
TBasicResizeFrame.LeftSizerRectLeftnull731 function TBasicResizeFrame.LeftSizerRectLeft: Integer;
732 begin
733   Result := -FHorizontalScrollPos;
734 end;
735 
LeftSizerLineWidthnull736 function TBasicResizeFrame.LeftSizerLineWidth: Integer;
737 begin
738   Result := SizerLineWidth;
739 end;
740 
HorizontalSizerLineLengthnull741 function TBasicResizeFrame.HorizontalSizerLineLength: Integer;
742 begin
743   Result := Width - RightMargin;
744 end;
745 
746 procedure TBasicResizeFrame.AdjustFormHandler;
747 begin
748   pFormHandler.Left:=(-FDesignedForm.Form.Left)-(FDesignedForm.PositionDelta.x+ifthen(FHorizontalScrollPos-SizerLineWidth>0,FHorizontalScrollPos-SizerLineWidth,0));
749   pFormHandler.Top:=(-FDesignedForm.Form.Top)-(FDesignedForm.PositionDelta.y+ifthen(FVerticalScrollPos-SizerLineWidth>0,FVerticalScrollPos-SizerLineWidth,0));
750   pFormHandler.Width:=(FDesignedForm.Form.Width+abs(FDesignedForm.Form.Left)+FDesignedForm.PositionDelta.x);;
751   pFormHandler.Height:=(FDesignedForm.Form.Height+abs(FDesignedForm.Form.Top)+FDesignedForm.PositionDelta.y);
752 end;
753 
GetBackgroundMarginnull754 function TBasicResizeFrame.GetBackgroundMargin(const AIndex: Integer): Integer;
755 begin
756   if FBackground = nil then
757     Result := 0
758   else
759     Result := FBackground.GetMargin(AIndex);
760 
761   if (AIndex = 1) and HasMainMenu then
762     Result := Result + GetMenuHeight;
763 end;
764 
GetNewSizenull765 function TBasicResizeFrame.GetNewSize: TPoint;
766 begin
767   Result := TPoint.Create(FLastClientWidth,FLastClientHeight);
768 end;
769 
GetFormHandlernull770 function TBasicResizeFrame.GetFormHandler: TPanel;
771 begin
772   Result := pFormHandler;
773 end;
774 
GetNodePositioningnull775 function TBasicResizeFrame.GetNodePositioning: Boolean;
776 begin
777   Result := FNodePositioning;
778 end;
779 
GetDesignedFormnull780 function TBasicResizeFrame.GetDesignedForm: IDesignedForm;
781 begin
782   Result := FDesignedForm;
783 end;
784 
785 procedure TBasicResizeFrame.SetDesignedForm(const AValue: IDesignedForm);
786 begin
787   FDesignedForm := AValue;
788   if FDesignedForm = nil then
789   begin
790     if Assigned(FBackground) then
791       FBackground.ResizeFrame := nil;
792     FBackground := nil;
793   end
794   else
795     if Supports(FDesignedForm, IDesignedFormBackground, FBackground) then
796     begin
797       FBackground.Parent := pBG;
798       FBackground.ResizeFrame := Self;
799     end;
800   // special for QT (at start "design form" has wrong position)
801   TryBoundDesignedForm;
802 end;
803 
GetMenuHeightnull804 function TBasicResizeFrame.GetMenuHeight: Integer;
805 begin
806   // some WS (Gtk2) return too big SM_CYMENU, just set it according to font height
807   // no problem, it is used only for the fake main menu
808 
809   {$IFDEF LCLWin32}
810   Result := lclintf.GetSystemMetrics(SM_CYMENU);
811   {$ELSE}
812   if pBG.HandleAllocated then
813     Result := pBG.Canvas.TextHeight('Hg') * 4 div 3
814   else
815     Result := 20;
816   {$ENDIF}
817 end;
818 
819 procedure TBasicResizeFrame.TryBoundDesignedForm;
820 begin
821   if DesignedForm = nil then
822     Exit;
823 
824   HideSizeControls;
825   ShowSizeControls;
826 
827   // for GTK2 resizing form (pClient is hidden under pBG)
828   {$IF DEFINED(LCLGtk2) OR DEFINED(LCLQt) OR DEFINED(LCLQt5)}
829   pFormHandler.SendToBack; // <--- this is a must.
830   {$ENDIF}
831   pFormHandler.BringToFront;
832 
833   pFakeMenu.Visible := HasMainMenu;
834   if pFakeMenu.Visible then
835   begin
836     pFakeMenu.Height := GetMenuHeight;
837     pFakeMenu.BorderSpacing.Left := BgLeftMargin;
838     pFakeMenu.BorderSpacing.Top := BgTopMargin - pFakeMenu.Height;
839     pFakeMenu.BorderSpacing.Right := BgRightMargin;
840     pFakeMenu.BringToFront;
841   end;
842 end;
843 
844 procedure TBasicResizeFrame.BeginFormSizeUpdate(Sender: TObject);
845 begin
846   FLastDesignedWidthToScroll:=DesignedWidthToScroll;
847   FLastDesignedHeightToScroll:=DesignedHeightToScroll;
848   pBG.OnPaint := nil;
849   pBG.SendToBack;
850 end;
851 
852 procedure TBasicResizeFrame.EndFormSizeUpdate(Sender: TObject);
853 begin
854   pBG.OnPaint := pBGPaint;
855 end;
856 
GetFramenull857 function TBasicResizeFrame.GetFrame: TCustomFrame;
858 begin
859   Result := Self;
860 end;
861 
TBasicResizeFrame.GetVerticalScrollPosnull862 function TBasicResizeFrame.GetVerticalScrollPos: Integer;
863 begin
864   Result := FVerticalScrollPos;
865 end;
866 
867 procedure TBasicResizeFrame.SetVerticalScrollPos(AValue: Integer);
868 begin
869   FVerticalScrollPos := AValue;
870 end;
871 
GetHorizontalScrollPosnull872 function TBasicResizeFrame.GetHorizontalScrollPos: Integer;
873 begin
874   Result := FHorizontalScrollPos;
875 end;
876 
877 procedure TBasicResizeFrame.SetHorizontalScrollPos(AValue: Integer);
878 begin
879   FHorizontalScrollPos := AValue;
880 end;
881 
TBasicResizeFrame.GetSizerRectSizenull882 function TBasicResizeFrame.GetSizerRectSize: Integer;
883 begin
884   Result := SizerRectSize;
885 end;
886 
GetSizerLineWidthnull887 function TBasicResizeFrame.GetSizerLineWidth: Integer;
888 begin
889   Result := SizerLineWidth;
890 end;
891 
GetBackgroundPanelnull892 function TBasicResizeFrame.GetBackgroundPanel: TPanel;
893 begin
894   Result := pBG;
895 end;
896 
DesignedWidthToScrollnull897 function TBasicResizeFrame.DesignedWidthToScroll: Integer;
898 begin
899   if DesignedForm = nil then
900     Exit(0);
901   if FNodePositioning then
902     Result := FLastDesignedWidthToScroll
903   else
904     Result := abs(DesignedForm.Width - FLastClientWidth);
905   //Result := DesignedForm.Width - DesignedForm.RealWidth;
906 end;
907 
908 procedure TBasicResizeFrame.DesignerSetFocus;
909 begin
910   if FFakeFocusControl.CanSetFocus then
911     FFakeFocusControl.SetFocus;
912 end;
913 
TBasicResizeFrame.DesignedHeightToScrollnull914 function TBasicResizeFrame.DesignedHeightToScroll: Integer;
915 begin
916   if DesignedForm = nil then
917     Exit(0);
918 
919   if FNodePositioning then
920     Result := FLastDesignedHeightToScroll
921   else
922     Result := abs(DesignedForm.Height - FLastClientHeight);
923   //Result := DesignedForm.Height - DesignedForm.RealHeight;
924 end;
925 
926 procedure TBasicResizeFrame.ClientChangeBounds;
927 begin
928   ClientChangeBounds(nil);
929 end;
930 
931 {}
932 
933 constructor TBasicResizeFrame.Create(TheOwner: TComponent);
934 begin
935   inherited Create(TheOwner);
936 
937 // Michl: Don't change DesignTimePPI of BasicResizeFrame (sparta_basicresizeframe.lfm).
938 //        There always has to be the default (none entry = 96 PPI) value!
939   FSizerRectSize := ScaleX(SIZER_RECT_SIZE, 96);
940   FSizerLineWidth := ScaleX(SIZER_LINE_WIDTH, 96);
941 
942   FFakeFocusControl := TEdit.Create(Self);
943   FFakeFocusControl.Parent := Self;
944   FFakeFocusControl.Top := -100;
945   FFakeFocusControl.OnKeyDown := FakeKeyDown;
946   FFakeFocusControl.OnKeyUp := FakeKeyUp;
947   FFakeFocusControl.OnUTF8KeyPress := FakeUTF8KeyPress;
948   FFakeFocusControl.OnEnter := FakeExitEnter;
949   FFakeFocusControl.OnExit := FakeExitEnter;
950 
951   FNodes := TObjectList.Create(False);
952   CreateNodes;
953 
954   pL.OnPaint := PanelPaint;
955   pT.OnPaint := PanelPaint;
956   pR.OnPaint := PanelPaint;
957   pB.OnPaint := PanelPaint;
958 
959   pClient.OnChangeBounds := ClientChangeBounds;
960   pBG.OnChangeBounds     := BGChangeBounds;
961   PositionNodes(Self);
962 
963   Application.AddOnIdleHandler(AppOnIdle);
964 end;
965 
966 procedure TBasicResizeFrame.AppOnIdle(Sender: TObject; var Done: Boolean);
967 var
968   aHasMainMenu: Boolean;
969 begin
970   if FDesignerModified then
971   begin
972     aHasMainMenu := HasMainMenu;
973     if aHasMainMenu <> FOldHasMainMenu then
974     begin
975       FOldHasMainMenu := aHasMainMenu;
976       TryBoundDesignedForm;
977       if Assigned(OnNodePositioning) then
978         OnNodePositioning(Self, [pkBottom], pcPositioningEnd);
979       Application.NotifyUserInputHandler(Self, 0); // force repaint invisible components
980     end else
981     if pFakeMenu.Visible then
982       pFakeMenu.Invalidate; // always repaint menu on modification
983 
984     FDesignerModified := False;
985   end;
986 end;
987 
988 destructor TBasicResizeFrame.Destroy;
989 begin
990   Pointer(FDesignedForm) := nil;
991   Pointer(FBackground) := nil;
992   Application.RemoveOnIdleHandler(AppOnIdle);
993   FNodes.Free;
994   inherited Destroy;
995 end;
996 
997 procedure TBasicResizeFrame.FakeExitEnter(Sender: TObject);
998 begin
999   pL.Repaint;
1000   pT.Repaint;
1001   pR.Repaint;
1002   pB.Repaint;
1003 end;
1004 
1005 procedure TBasicResizeFrame.FakeKeyDown(Sender: TObject; var Key: Word;
1006   Shift: TShiftState);
1007 var
1008   LWndProc: TWndMethod;
1009   LMsg: TLMKeyUp;
1010 begin
1011   LWndProc := FDesignedForm.Form.WindowProc;
1012   FillChar(LMsg{%H-}, SizeOf(LMsg), 0);
1013   LMsg.msg := CN_KEYDOWN;
1014   LMsg.CharCode := Key;
1015   LWndProc(TLMessage(LMsg));
1016   Key := LMsg.CharCode;
1017 end;
1018 
1019 procedure TBasicResizeFrame.FakeKeyUp(Sender: TObject; var Key: Word;
1020   Shift: TShiftState);
1021 var
1022   LWndProc: TWndMethod;
1023   LMsg: TLMKeyUp;
1024 begin
1025   LWndProc := FDesignedForm.Form.WindowProc;
1026   FillChar(LMsg{%H-}, SizeOf(LMsg), 0);
1027   LMsg.msg := CN_KEYUP;
1028   LMsg.CharCode := Key;
1029   LWndProc(TLMessage(LMsg));
1030   Key := LMsg.CharCode;
1031 end;
1032 
1033 procedure TBasicResizeFrame.FakeUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char
1034   );
1035 begin
1036   FDesignedForm.Form.IntfUTF8KeyPress(UTF8Key, 1, False);
1037 end;
1038 
1039 procedure TBasicResizeFrame.PositionNodes(AroundControl: TWinControl);
1040 var
1041   Node,T,L,CT,CL,FR,FB,FT,FL: Integer;
1042   TopLeft: TPoint;
1043 begin
1044   if FDesignedForm = nil then
1045     Exit;
1046 
1047   // positions of bars
1048   if not FNodePositioning then
1049   begin
1050     pL.Left := -FHorizontalScrollPos;
1051     pR.Left := FDesignedForm.Width - FHorizontalScrollPos + pL.Width + BgRightMargin + BgLeftMargin;
1052     pT.Top := -FVerticalScrollPos;
1053     pB.Top := FDesignedForm.Height - FVerticalScrollPos + pT.Height + BgBottomMargin + BgTopMargin;
1054 
1055     // width and height
1056     pL.Top:=0;
1057     pL.Height := FDesignedForm.Height + 2*SizerRectSize + BgTopMargin + BgBottomMargin;
1058     pR.Top:=0;
1059     pR.Height := FDesignedForm.Height + 2*SizerRectSize + BgTopMargin + BgBottomMargin;
1060     pT.Left:=0;
1061     pT.Width := FDesignedForm.Width + 2*SizerRectSize + BgLeftMargin + BgRightMargin;
1062     pB.Left:=0;
1063     pB.Width := FDesignedForm.Width + 2*SizerRectSize + BgLeftMargin + BgRightMargin;
1064 
1065     // client
1066     if pBG.Left + BgLeftMargin <= 0 then
1067       pClient.Left := -(pBG.Left) - (FHorizontalScrollPos - SizerRectSize)
1068     else
1069       pClient.Left := pBG.Left + BgLeftMargin;
1070     if pBG.Top + BgTopMargin <= 0 then
1071       pClient.Top := -(pBG.Top) - (FVerticalScrollPos - SizerRectSize)
1072     else
1073       pClient.Top := pBG.Top + BgTopMargin;
1074 
1075     pClient.Height := Height - pClient.Top - Max(Height - (pB.Top - BgBottomMargin), 0);
1076     pClient.Width := Width - pClient.Left - Max(Width - (pR.Left - BgRightMargin), 0);
1077   end;
1078 
1079   AdjustFormHandler;
1080 
1081   for Node := 0 to 7 do
1082   begin
1083     with AroundControl do
1084     begin
1085       FR := Width - RightSizerRectWidth - RightMargin;
1086       FB := Height - BottomSizerRectHeight - BottomMargin;
1087 
1088       FT := TopSizerRectTop;
1089       FL := LeftSizerRectLeft;
1090 
1091       CL := (FR - FL) div 2 + FL;
1092       CT := (FB - FT) div 2 + FT;
1093 
1094       case Node of
1095         0: begin
1096              T := FT;
1097              L := FL;
1098            end;
1099         1: begin
1100              T := FT;
1101              L := CL;
1102            end;
1103         2: begin
1104              T := FT;
1105              L := FR;
1106            end;
1107         3: begin
1108              T := CT;
1109              L := FR;
1110            end;
1111         4: begin
1112              T := FB;
1113              L := FR;
1114            end;
1115         5: begin
1116              T := FB;
1117              L := CL;
1118            end;
1119         6: begin
1120              T := FB;
1121              L := FL;
1122            end;
1123         7: begin
1124              T := CT;
1125              L := FL;
1126            end;
1127         else
1128           T := 0;
1129           L := 0;
1130       end;
1131 
1132       TopLeft := (Classes.Point(L,T));
1133     end;
1134     with TPanel(FNodes[Node]) do
1135     begin
1136       Top := TopLeft.Y;
1137       Left := TopLeft.X;
1138       Repaint;
1139     end;
1140   end;
1141 end;
1142 
1143 end.
1144 
1145