
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, Vcl.AppEvnts;

type
  TWndMgr = class(TForm)
    lvForms: TListViewEx;
    PopupMenu: TPopupMenu;
    ActionList: TActionList;
    aClose: TAction;
    Close1: TMenuItem;
    aGoTo: TAction;
    Goto1: TMenuItem;
    sbForms: TStatusBar;
    aDiscard: TAction;
    DiscardDel1: TMenuItem;
    ApplicationEvents: TApplicationEvents;
    procedure FormShow(Sender: TObject);
    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);
    procedure lvFormsSelCntChange(Sender: TObject);
    procedure aDiscardUpdate(Sender: TObject);
    procedure aDiscardExecute(Sender: TObject);
    procedure ApplicationEventsHint(Sender: TObject);
  strict private
    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, ClientVisuals;

{$R *.dfm}

{ TWndMgr }

procedure TWndMgr.aCloseExecute(Sender: TObject);
begin
  for var 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);
  lvForms.Sort(lvForms.SortColumn, lvForms.SortAscending);
end;

procedure TWndMgr.aDiscardExecute(Sender: TObject);
begin
  var LSelForms := GetSelectedForms;
  for var frm in LSelForms do
    if frm.Parent = nil then
      if frm is TVisFrm then
        frm.Free
      else
        frm.Close
end;

procedure TWndMgr.aDiscardUpdate(Sender: TObject);
begin
  aDiscard.Enabled := lvForms.SelCount > 0;
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;

procedure TWndMgr.ApplicationEventsHint(Sender: TObject);
begin
  if Application.Hint.IsEmpty then
    lvFormsSelCntChange(Self)
  else
    sbForms.SimpleText := Application.Hint;
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;
begin
  Result := nil;
  if lvForms.SelCount <> 1 then
    Exit;
  if lvForms.ItemIndex = -1 then
    Exit;
  var frms := GetSelectedForms;
  if Length(frms) <> 1 then
    Exit;
  Result := frms[0];
end;

function TWndMgr.GetSelectedForms: TArray<TCustomForm>;
begin
  var L := TList<TCustomForm>.Create;
  try
    for var i := 0 to lvForms.Items.Count - 1 do
      if lvForms.Items[i].Selected and (lvForms.Items[i].Data <> nil) then
        for var 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.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;
    VK_DELETE:
      if lvForms.SelCount > 0 then
        aDiscard.Execute;
  end;
end;

procedure TWndMgr.lvFormsSelCntChange(Sender: TObject);
begin
  sbForms.SimpleText := Format('%d window(s), %d selected',
    [lvForms.Items.count, lvForms.SelCount]);
end;

procedure TWndMgr.RemoveForm(AForm: TCustomForm);
begin
  if Assigned(AForm) then
    for var 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);
begin
  if Assigned(AForm) then
    for var i := 0 to lvForms.Items.Count - 1 do
      if NativeInt(lvForms.Items[i].Data) = NativeInt(AForm) then
        lvForms.Items[i].Caption := AForm.Caption;
  lvForms.Sort(lvForms.SortColumn, lvForms.SortAscending);
end;

procedure TWndMgr.UpdateList;
begin
  lvForms.Items.BeginUpdate;
  try
    lvForms.Clear;
    for var i := 0 to Screen.FormCount - 1 do
    begin
      var frm := Screen.Forms[i];
      if frm is TShadowForm then
        Continue;
      LvAddForm(frm);
    end;
  finally
    lvForms.Items.EndUpdate;
  end;
  lvForms.EnableSorting([]);
  if lvForms.SortColumn = -1 then
    lvForms.SortColumn := 0;
  lvForms.Sort(lvForms.SortColumn, lvforms.SortAscending);
  lvFormsSelCntChange(Self);
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.
