File: ffloatingsite.pas

package info (click to toggle)
lazarus 2.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 214,460 kB
  • sloc: pascal: 1,862,622; xml: 265,709; cpp: 56,595; sh: 3,008; java: 609; makefile: 535; perl: 297; sql: 222; ansic: 137
file content (195 lines) | stat: -rw-r--r-- 5,406 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
unit fFloatingSite;
(* Floating dock host.
  Host one or more docked clients.
  Destroy the site on the last undock.

  To allow for un/docking forms without widgetset support, use dock headers.
  To distinguish multiple clients, use the form header style (named caption).
    Default are unnamed headers, override app specifc with hsForm.

  Handle flaws of the Delphi docking model (improper undock).
  - Disallow TControls to float (else nothing but trouble).
  - For the IDE, floating client forms must wrap themselves into a new
    host site, to allow for continued docking of other clients.

Problems:
As with DockBook, closing docked forms may result in Exceptions :-(

*)

{$mode objfpc}{$H+}

{$DEFINE appdock} //using DockMaster/AppDockManager?

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  ExtCtrls;

type
  TFloatingSite = class(TForm)
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormDockDrop(Sender: TObject; Source: TDragDockObject;
      X, Y: Integer);
    procedure FormUnDock(Sender: TObject; Client: TControl;
      NewTarget: TWinControl; var Allow: Boolean);
  protected
    destructor Destroy; override;
    procedure Loaded; override;
  {$IFDEF appdock}
  {$ELSE}
    procedure ReloadDockedControl(const AControlName: string;
                                  var AControl: TControl); override;
  {$ENDIF}
  public
    procedure UpdateCaption(without: TControl);
  end;

implementation

{$R *.lfm}

uses
  LCLproc,  //debugging only
  EasyDockSite, //EasyTree DockManager
  uMakeSite;    //AppDockManager

// ----------- config --------------
const
  HideSingleHeader = False; //always show dockheader, for undocking forms
  HeaderStyle = hsMinimal;  //default to small headers (no caption bar)

type
{$IFDEF appdock}
  TOurDockManager = TAppDockManager;
{$ELSE}
  TOurDockManager = TEasyTree;
{$ENDIF}

{ TFloatingSite }

procedure TFloatingSite.UpdateCaption(without: TControl);
var
  i: integer;
  s: string;
  ctl: TControl;
begin
(* Show the combined captions of all clients.
  Exclude client to be undocked.
*)
  s := '';
  for i := 0 to DockClientCount - 1 do begin
    ctl := DockClients[i];
    if ctl <> without then
      s := s + GetDockCaption(ctl) + ', ';
  end;
  SetLength(s, Length(s) - 2); //strip trailing ", "
  Caption := s;
end;

destructor TFloatingSite.Destroy;
begin
  DebugLn('destroying ', Name);
  inherited Destroy;
end;

procedure TFloatingSite.FormClose(Sender: TObject;
  var CloseAction: TCloseAction);
var
  i: integer;
  ctl: TControl;
  //frm: TCustomForm absolute ctl;
begin
(* When an empty site is closed, it shall be freed.
  Otherwise the clients must be handled (close forms?).
*)
  for i := DockClientCount - 1 downto 0 do begin
    ctl := DockClients[i];
    ctl.Visible := False; //handle rest invisibly
    ctl.ManualDock(nil);
    //if ctl.Owner = nil then ctl.Destroy; //seems to work, but is this okay???
{
//Application.ReleaseComponent(ctl); --- Exception!
    if ctl <> nil then begin
    //verify that both Parent and HostDockSite are cleared
      DebugLn('Undocked %s P=%p H=%p', [ctl.Name,
        pointer(ctl.Parent), pointer(ctl.HostDockSite)]);
    end;
    if ctl is TCustomForm then begin
      frm.Close; //--- Exception!
      //frm.Release; --- also Exception!
      //frm.Hide;
    end;
}
  end;
  CloseAction := caFree;
end;

procedure TFloatingSite.FormDockDrop(Sender: TObject; Source: TDragDockObject;
  X, Y: Integer);
begin
(* Update the caption.
*)
  UpdateCaption(nil);
end;

procedure TFloatingSite.FormUnDock(Sender: TObject; Client: TControl;
  NewTarget: TWinControl; var Allow: Boolean);
begin
(* Check for undock last client, if allowed kill empty docksite.
  Refresh caption after undock.

Shit: in both cases the docking management does the opposite of what it should do:
  When the last control is dragged away, it's hosted in a *new* site.
  When a second control is dragged away, the entire site is moved.
:-(

Fix: disallow TControls to become floating.
*)
//try to distinguish between TControl and TWinControl (TCustomForm?)
  Allow := (NewTarget <> nil) or (Client is TWinControl); //seems to be safe
  DebugLn('TFloatingSite undodock, allow ', DbgS(Allow));
  if not Allow then
    exit; //all done

  if DockClientCount <= 1 then begin
    DebugLn('release ', Name);
    Release; //destroy empty site
  end else begin
    UpdateCaption(Client); //update caption, excluding removed client
    DockManager.ResetBounds(True); //required with gtk2!?
  end;
end;

procedure TFloatingSite.Loaded;
begin
(* select and configure the docking manager.
*)
  inherited Loaded;
  if DockManager = nil then
    DockManager := TOurDockManager.Create(self);
  if DockManager is TEasyTree then begin
  //adjust as desired, in config section above (order required!?)
    TEasyTree(DockManager).HideSingleCaption := HideSingleHeader;  // True; //only show headers for multiple clients
    TEasyTree(DockManager).SetStyle(HeaderStyle);  //show client name in the header
  end;
end;

{$IFDEF appdock}
{$ELSE}
procedure TFloatingSite.ReloadDockedControl(const AControlName: string;
  var AControl: TControl);
begin
  inherited ReloadDockedControl(AControlName, AControl);
  if AControl = nil then begin
    AControl := TForm.Create(Application);
    //make dock client
    //if uMakeSite...
  end;
end;
{$ENDIF}

end.