unit mandocking; {$mode objfpc}{$H+} interface uses Types, Forms, SysUtils, Controls, ExtCtrls, ProjectIntf, LazIDEIntf, MenuIntf, IDEMsgIntf, SrcEditorIntf, XMLConf; procedure Register; resourcestring mnuDockMsgWindow = 'Dock "Messages" window'; implementation type TDockState = record Docked : Boolean; FloatRect : TRect; FloatBrd : TFormBorderStyle; DockSize : TSize; EverDocked : Boolean; // if "docking" has even been since start of IDE. end; { TManualDocker } TManualDocker = class(TObject) private FCurrentSrcWin : TWinControl; protected procedure AdjustControlsOrder; function DoChangeDocking(DockingEnabled: Boolean): Boolean; procedure LoadState(cfg: TXMLConfig; var Astate: TDockState; const StateName: string); procedure SaveState(cfg: TXMLConfig; const Astate: TDockState; const StateName: string); procedure LoadStates; procedure SaveStates; procedure AllocControls(AParent: TWinControl); procedure DeallocControls; procedure ReallocControls; procedure UpdateDockState(var astate: TDockState; wnd: TWinControl); procedure DoPanelResize; procedure PanelResize(Sender: TObject); procedure SourceWindowCreated(Sender: TObject); procedure SourceWindowDestroyed(Sender: TObject); public ConfigPath : AnsiString; split : TSplitter; panel : TPanel; MsgWnd : TDockState; constructor Create; destructor Destroy; override; procedure OnCmdClick(Sender: TObject); function OnProjectOpen(Sender: TObject; AProject: TLazProject): TModalResult; end; var cmd : TIDEMenuCommand = nil; docker : TManualDocker = nil; const DockCfgRoot = 'ManualDockConfig'; DockCfgXML = 'manualdockconfig.xml'; MsgDockedName = 'Messages'; { TManualDocker } function SafeRect(const c: TREct; MinWidth, MinHeight: Integer): TRect; begin Result := c; if Result.Top < 0 then Result.Top := 0; if Result.Left < 0 then Result.Left := 0; if c.Right - c.Left < MinWidth then Result.Right := Result.Left + MinWidth; if c.Bottom - c.Top < MinHeight then Result.Bottom := Result.Top + MinHeight; end; function Max(a, b: Integer): Integer; begin if a > b then Result := a else Result := b; end; procedure TManualDocker.AdjustControlsOrder; var bar : TControl; i:Integer; begin if not Assigned(FCurrentSrcWin) then Exit; //HACK: the actual "bottom" controls are unknown! bar:=nil; for i:=0 to FCurrentSrcWin.ControlCount-1 do if ((FCurrentSrcWin.Controls[i].Name='StatusBar') or (FCurrentSrcWin.Controls[i].ClassName='TStatusBar')) then begin bar:=FCurrentSrcWin.Controls[i]; Break; end; split.Top:=bar.Top; panel.Top:=split.Height+split.Top; if Assigned(bar) then bar.Top:=panel.Top+panel.Height; end; function TManualDocker.DoChangeDocking(DockingEnabled:Boolean):Boolean; begin if DockingEnabled then begin Result:=False; if not (Assigned(SourceEditorManagerIntf) and Assigned(SourceEditorManagerIntf.ActiveSourceWindow)) or not Assigned(IDEMessagesWindow) then Exit; if not Assigned(panel) then AllocControls(SourceEditorManagerIntf.ActiveSourceWindow); if IDEMessagesWindow.Parent = nil then begin MsgWnd.FloatRect := IDEMessagesWindow.BoundsRect; MsgWnd.FloatBrd := IDEMessagesWindow.BorderStyle; end; panel.visible:=true; split.visible:=true; panel.Height:=MsgWnd.DockSize.cy; AdjustControlsOrder; IDEMessagesWindow.BorderStyle := bsNone; IDEMessagesWindow.Parent := panel; // LCL fails with "infinite resize loop", using manual size adjustement instead panel.OnResize:=@PanelResize; DoPanelResize; IDEMessagesWindow.TabStop := False; { // this code has been used to keep the cursor back to the source code // whenever IDE is focused back, instead of focusing a compiler message for i := 0 to IDEMessagesWindow.ControlCount - 1 do if IDEMessagesWindow.Controls[i] is TWinControl then TWinControl(IDEMessagesWindow.Controls[i]).TabStop := False; } Result:=True; MsgWnd.EverDocked:=True; end else if MsgWnd.EverDocked then begin if Assigned(panel) then begin panel.visible := False; UpdateDockState(MsgWnd, panel); end; if Assigned(split) then split.visible := False; IDEMessagesWindow.Parent := nil; IDEMessagesWindow.BoundsRect := SafeRect(MsgWnd.FloatRect, Max(30, IDEMessagesWindow.ClientWidth), Max(30, IDEMessagesWindow.ClientHeight)); IDEMessagesWindow.BorderStyle := MsgWnd.FloatBrd; IDEMessagesWindow.TabStop := true; IDEMessagesWindow.Show; Result:=True; end else Result:=true; end; constructor TManualDocker.Create; var pths : array [0..1] of String; i : Integer; begin if SourceEditorManagerIntf <> nil then begin SourceEditorManagerIntf.RegisterChangeEvent(semWindowCreate, @SourceWindowCreated); SourceEditorManagerIntf.RegisterChangeEvent(semWindowDestroy, @SourceWindowDestroyed); end; pths[0]:= LazarusIDE.GetPrimaryConfigPath; pths[1]:= LazarusIDE.GetSecondaryConfigPath; for i := 0 to length(pths)-1 do begin try ConfigPath := IncludeTrailingPathDelimiter(pths[i])+DockCfgXML; LoadStates; Break; except end; end; MsgWnd.FloatBrd := bsToolWindow; end; destructor TManualDocker.Destroy; begin if Assigned(panel) then UpdateDockState(MsgWnd, panel); SaveStates; DeallocControls; inherited Destroy; end; procedure TManualDocker.OnCmdClick(Sender: TObject); var NeedDocking: Boolean; begin NeedDocking:=not Cmd.Checked; DoChangeDocking(NeedDocking); MsgWnd.docked:=NeedDocking; cmd.Checked:=NeedDocking; end; function TManualDocker.OnProjectOpen(Sender: TObject; AProject: TLazProject): TModalResult; begin DoChangeDocking(MsgWnd.Docked); Result := mrOK; end; function CreateXMLConfig(const xmlfile: string) : TXMLConfig; begin Result := TXMLConfig.Create(nil); Result.RootName := DockCfgRoot; Result.Filename := xmlfile; end; procedure TManualDocker.AllocControls(AParent: TWinControl); begin FCurrentSrcWin := AParent; panel := TPanel.Create(FCurrentSrcWin); panel.BorderStyle := bsNone; panel.Align:=alBottom; FCurrentSrcWin.InsertControl(panel); split := TSplitter.Create(AParent); split.Align:=alBottom; FCurrentSrcWin.InsertControl(split); end; procedure TManualDocker.DeallocControls; begin split:=nil; panel:=nil; end; procedure TManualDocker.ReallocControls; begin end; procedure TManualDocker.UpdateDockState(var astate: TDockState; wnd: TWinControl); begin astate.DockSize.cx := wnd.ClientWidth; astate.DockSize.cy := wnd.ClientHeight; end; procedure TManualDocker.DoPanelResize; begin IDEMessagesWindow.BoundsRect:=panel.ClientRect; end; procedure TManualDocker.PanelResize(Sender: TObject); begin if not Assigned(panel) or not Assigned(IDEMessagesWindow) or not MsgWnd.Docked then Exit; DoPanelResize; end; procedure TManualDocker.SourceWindowCreated(Sender: TObject); begin if Assigned(FCurrentSrcWin) or (SourceEditorManagerIntf.SourceWindowCount > 1) then Exit; if MsgWnd.Docked then DoChangeDocking(true); end; procedure TManualDocker.SourceWindowDestroyed(Sender: TObject); begin if FCurrentSrcWin <> Sender then Exit; DoChangeDocking(False); DeallocControls; FCurrentSrcWin := nil; // avoid re-docking to the window being destroyed if MsgWnd.Docked and (SourceEditorManagerIntf.ActiveSourceWindow<>Sender) then DoChangeDocking(True); end; procedure TManualDocker.LoadState(cfg: TXMLConfig; var Astate: TDockState; const StateName: string); var nm : UnicodeString; begin nm := UTF8Decode(StateName); AState.Docked := cfg.GetValue(nm+'/docked', False); AState.FloatRect.Left := cfg.GetValue(nm+'/float/left', -1); AState.FloatRect.Top := cfg.GetValue(nm+'/float/top', -1); AState.FloatRect.Right := cfg.GetValue(nm+'/float/right', -1); AState.FloatRect.Bottom := cfg.GetValue(nm+'/float/bottom', -1); AState.DockSize.cx := cfg.GetValue(nm+'/docked/cx', 30); AState.DockSize.cy := cfg.GetValue(nm+'/docked/cy', 50); end; procedure TManualDocker.SaveState(cfg: TXMLConfig; const Astate: TDockState; const StateName: string); var nm : UnicodeString; begin nm:=UTF8Decode(StateName); cfg.SetValue(nm+'/docked', AState.Docked); cfg.SetValue(nm+'/float/left', AState.FloatRect.Left); cfg.SetValue(nm+'/float/top', AState.FloatRect.Top); cfg.SetValue(nm+'/float/right', AState.FloatRect.Right); cfg.SetValue(nm+'/float/bottom', AState.FloatRect.Bottom); cfg.SetValue(nm+'/docked/cx', AState.DockSize.cx); cfg.SetValue(nm+'/docked/cy', AState.DockSize.cy) end; procedure TManualDocker.LoadStates; var cfg : TXMLConfig; begin cfg := CreateXMLConfig(ConfigPath); try LoadState(cfg, MsgWnd, MsgDockedName) finally cfg.Free; end; end; procedure TManualDocker.SaveStates; var cfg : TXMLConfig; begin cfg := CreateXMLConfig(ConfigPath); try try SaveState(cfg, MsgWnd, MsgDockedName) finally cfg.Free; end; except end; end; procedure Register; begin docker := TManualDocker.Create; cmd := RegisterIDEMenuCommand(itmViewMainWindows, 'makeMessagesDocked', mnuDockMsgWindow, @docker.OnCmdClick, nil, nil, ''); cmd.Checked:=docker.MsgWnd.Docked; LazarusIDE.AddHandlerOnProjectOpened(@docker.OnProjectOpen, False); end; initialization finalization docker.Free; end.