unit WinMgrForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.ComCtrls, ListViewEx, Generics.Collections, System.Actions, Vcl.ActnList,
  Vcl.Menus;

type
  TWndMgr = class(TForm)
    lvForms: TListViewEx;
    PopupMenu: TPopupMenu;
    ActionList: TActionList;
    aClose: TAction;
    Close1: TMenuItem;
    aGoTo: TAction;
    Goto1: TMenuItem;
    procedure FormShow(Sender: TObject);
    procedure lvFormsColumnClick(Sender: TObject; Column: TListColumn);
    procedure lvFormsCompare(Sender: TObject; Item1, Item2: TListItem;
      Data: Integer; var Compare: Integer);
    procedure aCloseUpdate(Sender: TObject);
    procedure aCloseExecute(Sender: TObject);
    procedure aGoToUpdate(Sender: TObject);
    procedure aGoToExecute(Sender: TObject);
    procedure lvFormsDblClick(Sender: TObject);
    procedure lvFormsKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  strict private
    FSortColumn: Integer;
    FSortDesc: Boolean;
    class var FInstances: TList<TWndMgr>;
    class constructor ClassCreate;
    class destructor ClassDestroy;
    procedure LvAddForm(AForm: TCustomForm);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure UpdateList;
    procedure UpdateFormCaption(AForm: TCustomForm);
    procedure RemoveForm(AForm: TCustomForm);
    procedure AddForm(AForm: TCustomForm);
    procedure UpdateActiveFormIndicator;
    function GetSelectedForms: TArray<TCustomForm>;
    function GetSelectedForm: TCustomForm;
    class procedure FormListChanged;
    class procedure CaptionChanged(AForm: TCustomForm);
    class procedure FormDestroyed(AForm: TCustomForm);
    class procedure FormAdded(AForm: TCustomForm);
    class procedure ActiveFormChanged;
  end;

  TWindowWatcher = class(TComponent)
  strict private
    FForm: TCustomForm;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

var
  WndMgr: TWndMgr;

implementation

uses
  Math, StrUtils, FormFader, FrontEndProps, ScreenDispatch;

{$R *.dfm}

{ TWndMgr }

procedure TWndMgr.aCloseExecute(Sender: TObject);
var
  frm: TCustomForm;
begin
  for frm in GetSelectedForms do
    if frm.Parent = nil then
      frm.Close;
end;

procedure TWndMgr.aCloseUpdate(Sender: TObject);
begin
  aClose.Enabled := lvForms.SelCount > 0;
end;

class procedure TWndMgr.ActiveFormChanged;
begin
  if Assigned(FInstances) then
    for var WndMgr in FInstances do
      WndMgr.UpdateActiveFormIndicator;
end;

procedure TWndMgr.AddForm(AForm: TCustomForm);
begin
  RemoveForm(AForm);
  LvAddForm(AForm);
end;

procedure TWndMgr.aGoToExecute(Sender: TObject);
var
  frm: TCustomForm;
begin
  frm := GetSelectedForm;
  if Assigned(frm) then
  begin
    frm.Show;
    if frm.CanFocus then
      frm.SetFocus;
  end;
end;

procedure TWndMgr.aGoToUpdate(Sender: TObject);
begin
  aGoTo.Enabled := (lvForms.SelCount = 1) and (lvForms.ItemIndex <> -1);
end;

class procedure TWndMgr.CaptionChanged(AForm: TCustomForm);
begin
  if Assigned(FInstances) then
    for var WndMgr in FInstances do
      WndMgr.UpdateFormCaption(AForm);
end;

class constructor TWndMgr.ClassCreate;
begin
  FInstances := TList<TWndMgr>.Create;
end;

class destructor TWndMgr.ClassDestroy;
begin
  FreeAndNil(FInstances);
end;

constructor TWndMgr.Create(AOwner: TComponent);
begin
  inherited;
  if Assigned(FInstances) then
    FInstances.Add(Self);
  TFormFader.Create(Self);
  TWindowWatcher.Create(Self);
  TScreenDispatcher.RegisterFormChangeProc('Algosim.TWndMgr', ActiveFormChanged);
end;

destructor TWndMgr.Destroy;
begin
  if Assigned(FInstances) then
    FInstances.Remove(Self);
  inherited;
end;

class procedure TWndMgr.FormAdded(AForm: TCustomForm);
begin
  if Assigned(FInstances) then
    for var WndMgr in FInstances do
      WndMgr.AddForm(AForm);
end;

class procedure TWndMgr.FormDestroyed(AForm: TCustomForm);
begin
  if Assigned(FInstances) then
    for var WndMgr in FInstances do
      WndMgr.RemoveForm(AForm);
end;

class procedure TWndMgr.FormListChanged;
begin
  if Assigned(FInstances) then
    for var WndMgr in FInstances do
      WndMgr.UpdateList;
end;

procedure TWndMgr.FormShow(Sender: TObject);
begin
  UpdateList;
end;

function TWndMgr.GetSelectedForm: TCustomForm;
var
  frms: TArray<TCustomForm>;
begin
  Result := nil;
  if lvForms.SelCount <> 1 then
    Exit;
  if lvForms.ItemIndex = -1 then
    Exit;
  frms := GetSelectedForms;
  if Length(frms) <> 1 then
    Exit;
  Result := frms[0];
end;

function TWndMgr.GetSelectedForms: TArray<TCustomForm>;
var
  L: TList<TCustomForm>;
  i, j: Integer;
begin
  L := TList<TCustomForm>.Create;
  try
    for i := 0 to lvForms.Items.Count - 1 do
      if lvForms.Items[i].Selected and (lvForms.Items[i].Data <> nil) then
        for j := 0 to Screen.CustomFormCount - 1 do
          if NativeInt(Screen.CustomForms[j]) = NativeInt(lvForms.Items[i].Data) then
          begin
            L.Add(Screen.CustomForms[j]);
            Break;
          end;
    Result := L.ToArray;
  finally
    L.Free;
  end;
end;

procedure TWndMgr.LvAddForm(AForm: TCustomForm);
begin
  if Assigned(AForm) then
    with lvForms.Items.Add do
    begin
      Caption := AForm.Caption;
      SubItems.Add(FormTypeStr(AForm));
      SubItems.Add('');
      Data := AForm;
    end;
end;

procedure TWndMgr.lvFormsColumnClick(Sender: TObject; Column: TListColumn);
begin
  if FSortColumn = Column.Index then
    FSortDesc := not FSortDesc
  else
  begin
    FSortColumn := Column.Index;
    FSortDesc := False;
  end;
  lvForms.AlphaSort;
end;

procedure TWndMgr.lvFormsCompare(Sender: TObject; Item1, Item2: TListItem;
  Data: Integer; var Compare: Integer);
begin
  case FSortColumn of
    0:
      Compare := CompareText(Item1.Caption, Item2.Caption);
    1:
      Compare := CompareText(Item1.SubItems[0], Item2.SubItems[0]);
  end;
  if FSortDesc then
    Compare := -Compare;
end;

procedure TWndMgr.lvFormsDblClick(Sender: TObject);
begin
  if lvForms.SelCount = 1 then
    aGoTo.Execute;
end;

procedure TWndMgr.lvFormsKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  case Key of
    VK_RETURN:
      if lvForms.SelCount = 1 then
        aGoTo.Execute;
  end;
end;

procedure TWndMgr.RemoveForm(AForm: TCustomForm);
var
  i: Integer;
begin
  if Assigned(AForm) then
    for i := lvForms.Items.Count - 1 downto 0 do
      if NativeInt(lvForms.Items[i].Data) = NativeInt(AForm) then
        lvForms.Items.Delete(i);
end;

procedure TWndMgr.UpdateActiveFormIndicator;
begin
  for var i := 0 to lvForms.Items.Count - 1 do
    if NativeInt(lvForms.Items[i].Data) = NativeInt(Screen.ActiveCustomForm) then
      lvForms.Items[i].SubItems[1] := '◀'
    else
      lvForms.Items[i].SubItems[1] := ''
end;

procedure TWndMgr.UpdateFormCaption(AForm: TCustomForm);
var
  i: Integer;
begin
  if Assigned(AForm) then
    for i := 0 to lvForms.Items.Count - 1 do
      if NativeInt(lvForms.Items[i].Data) = NativeInt(AForm) then
        lvForms.Items[i].Caption := AForm.Caption;
end;

procedure TWndMgr.UpdateList;
var
  i: Integer;
  frm: TForm;
begin
  lvForms.Items.BeginUpdate;
  try
    lvForms.Clear;
    for i := 0 to Screen.FormCount - 1 do
    begin
      frm := Screen.Forms[i];
      if frm is TShadowForm then
        Continue;
      LvAddForm(frm);
    end;
  finally
    lvForms.Items.EndUpdate;
  end;
  lvForms.AlphaSort;
end;

{ TWindowWatcher }

constructor TWindowWatcher.Create(AOwner: TComponent);
begin
  if not (AOwner is TCustomForm) then
    raise Exception.Create('Owner must be a form.');
  inherited;
  FForm := TCustomForm(AOwner);
  TWndMgr.FormAdded(FForm);
end;

destructor TWindowWatcher.Destroy;
begin
  TWndMgr.FormDestroyed(FForm);
  inherited;
end;

end.
