unit U_PreviewEngineUIManager;

interface

uses
  System.Classes, System.Generics.Collections,
  Vcl.Controls, Vcl.ActnMan, Vcl.ActnList,
  U_PreviewEngine;

type
  TPreviewEngineUIManager = class
  private
    FActionMgr: TActionManager;
    FContainer: TWinControl;
    FActionPreviewEngine: TAction;

    FEngineForms: TObjectList<TCustomPreviewEngineForm>;
    FEngineSelectors: TDictionary<TCustomPreviewEngineForm,TAction>;
    FRendererSelectors: TDictionary<TRenderer,TAction>;
    FEngineEnablers: TDictionary<TCustomPreviewEngineForm,TAction>;

    FFileName: string;
    FSelectedRenderer: TRenderer;

    FOnRendererSelected: TNotifyEvent;

    procedure InitializeEngines;
    procedure InitializeUI;

    procedure actSelectEngineExecute(Sender: TObject);
    procedure actSelectRendererExecute(Sender: TObject);
    procedure actSelectRendererUpdate(Sender: TObject);
    procedure actEnableEngineExecute(Sender: TObject);

    procedure SetSelectedRenderer(const Value: TRenderer);
    procedure CreateRendererSelector(const Renderer: TRenderer; const ParentItem: TActionClientItem);

  protected
    procedure EngineRenderersNotify(Sender: TObject; const Renderer: TRenderer;
                                    Action: TCollectionNotification); virtual;
    procedure DoRendererSelected;
  public
    constructor Create(const ActionManager: TActionManager;
                       const PreviewContainer: TWinControl;
                       const ActionPreviewEngine: TAction);
    destructor  Destroy; override;

    function ShowFile(const FileName: string): TRenderer;
    function NextRenderer: TRenderer;

    property ActionManager: TActionManager    read FActionMgr;
    property Container: TWinControl           read FContainer;

    property Filename: string                 read FFileName;
    property SelectedRenderer: TRenderer      read FSelectedRenderer;

    property OnRendererSelected: TNotifyEvent read FOnRendererSelected  write FOnRendererSelected;
  end;

implementation

uses
  System.SysUtils, System.Generics.Defaults, System.Actions,
  Winapi.Windows,
  Vcl.Forms, Vcl.Graphics;

{ ================================================================================================ }
{ TUIPreviewEngineManager }

{ ------------------------------------------------------------------------------------------------ }
constructor TPreviewEngineUIManager.Create(const ActionManager: TActionManager;
                                           const PreviewContainer: TWinControl;
                                           const ActionPreviewEngine: TAction);
begin
  inherited Create;
  FEngineForms := TObjectList<TCustomPreviewEngineForm>.Create;

  FActionMgr := ActionManager;
  FActionPreviewEngine := ActionPreviewEngine;
  FContainer := PreviewContainer;

  FEngineSelectors := TDictionary<TCustomPreviewEngineForm,TAction>.Create;
  FRendererSelectors := TDictionary<TRenderer,TAction>.Create;
  FEngineEnablers := TDictionary<TCustomPreviewEngineForm,TAction>.Create;

  InitializeEngines;
  InitializeUI;
end {TUIPreviewEngineManager.Create};
{ ------------------------------------------------------------------------------------------------ }
destructor TPreviewEngineUIManager.Destroy;
begin
  FEngineEnablers.Free;
  FRendererSelectors.Free;
  FEngineSelectors.Free;
  FEngineForms.Free;

  inherited;
end {TUIPreviewEngineManager.Destroy};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.InitializeEngines;
var
  FormClass: TCustomPreviewEngineFormClass;
  Form: TCustomPreviewEngineForm;
begin
//  FEngineForms := TObjectList<TCustomPreviewEngineForm>.Create(TComparer<TCustomPreviewEngineForm>.Construct(
//                      function(const Left, Right: TCustomPreviewEngineForm): integer
//                      begin
//                        {$MESSAGE HINT 'TODO: sort engine forms -- MCO 2015-02-09'}
//                        // TODO: look 'em up in some list, then base their order on that list.
//                        // TODO: if they don't occur in the list, move them to the end
//                        Result := 0;
//                      end));
  for FormClass in TCustomPreviewEngineFormClass.RegisteredForms do begin
    Form := FormClass.Create(nil);
    Form.Parent := FContainer;
    Form.Align := alClient;
    Form.BorderStyle := bsNone;
    Form.Enabled := True; // TODO: read from ini or something
    Form.Color := clWindow;
    FEngineForms.Add(Form);
  end;
end {TUIPreviewEngineManager.InitializeEngines};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.InitializeUI;
var
  ItemPreviewEngine: TActionClientItem;
  Form: TCustomPreviewEngineForm;
  actSelectEngine, actEnableEngine: TAction;
  EngineIndex, ImageIndex: integer;
  Item: TActionClientItem;
  Renderer: TRenderer;
  ItemOffset: integer;
  ActionList: TActionList;
  HasMultipleRenderers: Boolean;
  HasActions: Boolean;
  EngineAction: TContainedAction;
begin
  if FEngineForms.Count <= 0 then
    Exit;

  ItemPreviewEngine := ActionManager.FindItemByAction(FActionPreviewEngine);
  ItemOffset := 1;

  EngineIndex := 0;
  for Form in FEngineForms do begin
    ActionList := Form.ActionList;

    if (Form.Icon = nil) or Form.Icon.Empty then
      ImageIndex := -1
    else
      ImageIndex := ActionManager.Images.AddIcon(Form.Icon);

    actSelectEngine := TAction.Create(ActionManager);
    actSelectEngine.Caption := Form.Caption;
    actSelectEngine.Enabled := Form.Enabled; // TODO: dynamically follow when the form is enabled
    actSelectEngine.Hint := Form.Caption;
    actSelectEngine.ImageIndex := ImageIndex;
    if EngineIndex < 10 then
      actSelectEngine.ShortCut := scCtrl or scShift or Ord(IntToStr((EngineIndex + 1) mod 10)[1]);
    actSelectEngine.Tag := NativeInt(Form);
    actSelectEngine.Visible := True;
    actSelectEngine.OnExecute := actSelectEngineExecute;
    FEngineSelectors.Add(Form, actSelectEngine);

    Item := ItemPreviewEngine.Items.Insert(ItemOffset + EngineIndex) as TActionClientItem;
    Item.Action := actSelectEngine;

    // Fill the list of renderers
    HasMultipleRenderers := Form.Renderers.Count > 1;
    HasActions := Assigned(ActionList) and (ActionList.ActionCount > 0);
    if HasMultipleRenderers or HasActions then begin
      if HasActions then begin
        for EngineAction in ActionList do begin
          Item.Items.Add.Action := EngineAction;
        end;
        with Item.Items.Add do begin
          Caption := '-';
          CommandStyle := csSeparator;
        end;
      end;
      with Item.Items.Add do begin
        Action := actSelectEngine;
        Caption := '(autoselect)';
        ImageIndex := -1;
        Visible := HasMultipleRenderers;
      end;
      for Renderer in Form.Renderers do begin
        CreateRendererSelector(Renderer, Item);
      end;
    end else if Form.Renderers.Count > 0 then begin
      FRendererSelectors.Add(Form.Renderers[0], actSelectEngine);
    end {for};

    Form.Renderers.OnNotify := EngineRenderersNotify;

    {--- MCO 10-02-2015: Create a toolbar button to enable/disable this engine ---}
    actEnableEngine := TAction.Create(ActionManager);
    actEnableEngine.AutoCheck := True;
    actEnableEngine.Caption := Form.Caption;
    actEnableEngine.Checked := Form.Enabled;
    actEnableEngine.Hint := Format('Use %0:s|Toggle the use of %0:s for previewing files', [Form.Caption]);
    actEnableEngine.ImageIndex := ImageIndex;
    if EngineIndex < 10 then
      actEnableEngine.ShortCut := scCtrl or Ord(IntToStr((EngineIndex + 1) mod 10)[1]);
    actEnableEngine.Tag := NativeInt(Form);
    actEnableEngine.Visible := True;
    actEnableEngine.OnExecute := actEnableEngineExecute;

    Item := ActionManager.ActionBars[0].Items.Add;
    Item.Action := actEnableEngine;
    Item.ImageIndex := ImageIndex;
    Item.ShowGlyph := True;
    Item.ShowCaption := False;
    Item.Visible := True;

    Inc(EngineIndex);
  end {for};
end {TUIPreviewEngineManager.InitializeUI};

{ ------------------------------------------------------------------------------------------------ }
function TPreviewEngineUIManager.ShowFile(const FileName: string): TRenderer;
var
  Form: TCustomPreviewEngineForm;
begin
  FFileName := FileName;

  FActionPreviewEngine.ImageIndex := FActionPreviewEngine.Tag;
  FActionPreviewEngine.Hint := '';

  Result := nil;
  for Form in FEngineForms do begin
    {$MESSAGE WARN 'TODO: improve error handling'}
    if (Result = nil) and Form.Enabled and Form.TryShowFile(Filename, Result) then begin
      Form.Show;
      SetSelectedRenderer(Result);
    end;
  end {for};
  // Only hide the forms after we've found a good one, to prevent unnecessary flickering
  for Form in FEngineForms do begin
    if (Result = nil) or (Form <> Result.Form) then begin
      Form.Hide;
      Form.Clear;
    end;
  end;
end {TUIPreviewEngineManager.TryShowFile};

{ ------------------------------------------------------------------------------------------------ }
function TPreviewEngineUIManager.NextRenderer: TRenderer;
var
  SelectedForm, Form: TCustomPreviewEngineForm;
  FoundForm, FoundRenderer: boolean;
  Renderer: TRenderer;
begin
  if SelectedRenderer = nil then
    Exit;
  SelectedForm := SelectedRenderer.Form;
  Result := nil;
  FoundForm := False;
  for Form in FEngineForms do begin
    if not FoundForm then begin
      FoundForm := (Form = SelectedForm);
      if FoundForm then begin
        FoundRenderer := False;
        for Renderer in Form.Renderers do begin
          if not FoundRenderer then begin
            FoundRenderer := (Renderer = SelectedRenderer);
          end else if Renderer.CouldShowFile(FFileName) and Renderer.TryShowFile(FFileName) then begin
            Result := Renderer;
            Break;
          end;
        end {for};
        if Result <> nil then
          Break;
      end;
    end else if Form.Enabled and Form.TryShowFile(FFileName, Result) then begin
      Form.Show;
      Break;
    end;
  end {for};
  if Result <> nil then begin
    SetSelectedRenderer(Result);
    if Result.Form <> SelectedForm then begin
      SelectedForm.Hide;
      SelectedForm.Clear;
    end;
  end else begin
    Result := ShowFile(FFileName);
  end;
end {TPreviewEngineUIManager.NextRenderer};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.CreateRendererSelector(const Renderer: TRenderer;
                                                         const ParentItem: TActionClientItem);
var
  actSelectRenderer: TAction;
begin
  actSelectRenderer := TAction.Create(ActionManager);
  actSelectRenderer.Caption := Renderer.DisplayName;
  actSelectRenderer.Enabled := Renderer.Form.Enabled; // TODO: dynamically follow when the form is enabled
  actSelectRenderer.Tag := NativeInt(Renderer);
  actSelectRenderer.OnExecute := actSelectRendererExecute;
  actSelectRenderer.OnUpdate := actSelectRendererUpdate;
  FRendererSelectors.Add(Renderer, actSelectRenderer);
  with ParentItem.Items.Add do begin
    Action := actSelectRenderer;
  end;
end {TPreviewEngineUIManager.CreateRendererSelector};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.SetSelectedRenderer(const Value: TRenderer);
var
  actSelectEngine, actSelectRenderer: TAction;
begin
  if Assigned(FSelectedRenderer) and (FSelectedRenderer.Form <> Value.Form) then begin
    ActionManager.FindItemByAction(FEngineSelectors[FSelectedRenderer.Form]).Default := False;
    ActionManager.FindItemByAction(FRendererSelectors[FSelectedRenderer]).Default := False;
  end;

  FSelectedRenderer := Value;

  // Update the menu item for the engine
  actSelectEngine := FEngineSelectors.Items[Value.Form];
  ActionManager.FindItemByAction(actSelectEngine).Default := True;
  // Update the menu item for the renderer
  actSelectRenderer := FRendererSelectors.Items[Value];
  ActionManager.FindItemByAction(actSelectRenderer).Default := True;

  FActionPreviewEngine.Hint := Format('%1:s (%0:s)', [Value.Form.Caption, Value.DisplayName]);
  FActionPreviewEngine.ImageIndex := actSelectEngine.ImageIndex;

  DoRendererSelected;
end {TUIPreviewEngineManager.SetSelectedRenderer};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.DoRendererSelected;
begin
  if Assigned(FOnRendererSelected) then
    FOnRendererSelected(Self);
end {TPreviewEngineUIManager.DoRendererSelected};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.EngineRenderersNotify(Sender: TObject; const Renderer: TRenderer;
  Action: TCollectionNotification);
  { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
  function FindAutoselectItem(const ParentItem: TActionClientItem): TActionClientItem;
  var
    CAI: TCollectionItem;
    Item: TActionClientItem;
  begin
    for CAI in ParentItem.Items do begin
      Item := TActionClientItem(CAI);
      if Assigned(Item.Action)
          and TEqualityComparer<TNotifyEvent>.Default.Equals(Item.Action.OnExecute, actSelectEngineExecute)
      then begin
        Result := Item;
        Exit;
      end;
    end;
    Result := nil;
  end {FindAutoselectItem};
  { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - }
var
  actSelectEngine: TAction;
  actSelectRenderer: TAction;
  ParentItem, AutoselectItem: TActionClientItem;
begin
  case Action of
    cnAdded: begin
      actSelectEngine := FEngineSelectors[Renderer.Form];
      ParentItem := ActionManager.FindItemByAction(actSelectEngine);
      CreateRendererSelector(Renderer, ParentItem);
      // Enable the autoselect item if necessary
      if Renderer.Form.Renderers.Count = 2 then begin
        AutoselectItem := FindAutoselectItem(ParentItem);
        if AutoselectItem <> nil then begin
          AutoselectItem.Action.Visible := True;
          AutoselectItem.Visible := True;
        end;
      end;
    end {cnAdded};
    cnRemoved, cnExtracted: begin
      actSelectRenderer := FRendererSelectors.Items[Renderer];
      ActionManager.DeleteActionItems([actSelectRenderer]);
      FRendererSelectors.Remove(Renderer);
      actSelectRenderer.Free;
      // Hide the autoselect item if necessary
      if Renderer.Form.Renderers.Count = 2 then begin
        actSelectEngine := FEngineSelectors[Renderer.Form];
        ParentItem := ActionManager.FindItemByAction(actSelectEngine);
        AutoselectItem := FindAutoselectItem(ParentItem);
        if AutoselectItem <> nil then begin
          AutoselectItem.Action.Visible := True;
          AutoselectItem.Visible := True;
        end;
      end;
    end {cnRemoved, cnExtracted};
  end;
end {TPreviewEngineUIManager.EngineRenderersNotify};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.actEnableEngineExecute(Sender: TObject);
var
  actEnableEngine: TAction;
  Form: TCustomPreviewEngineForm;
  Renderer: TRenderer;
begin
  actEnableEngine := Sender as TAction;
  Form := TObject(actEnableEngine.Tag) as TCustomPreviewEngineForm;

  Form.Enabled := actEnableEngine.Checked;
  if Form.Enabled
      and (Length(Form.CouldShowFile(FFileName)) > 0)
      and Form.TryShowFile(FFileName, Renderer)
  then begin
    Form.Show;
    SetSelectedRenderer(Renderer);
    {$MESSAGE HINT 'TODO: hide the previously shown form'}
  end else if not Form.Enabled then begin
    // automatically select the (next) engine to display the current file
    Renderer := ShowFile(FFileName);
    if Renderer.Form <> Form then begin
      Form.Hide;
      Form.Clear;
    end;
  end;
end {TUIPreviewEngineManager.actEnableEngineExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.actSelectEngineExecute(Sender: TObject);
var
  actSelectEngine: TAction;
  Form: TCustomPreviewEngineForm;
  Renderer: TRenderer;
begin
  actSelectEngine := Sender as TAction;
  Form := TObject(actSelectEngine.Tag) as TCustomPreviewEngineForm;

  if Form.Enabled and Form.TryShowFile(FFileName, Renderer) then begin
    Form.Show;
    SetSelectedRenderer(Renderer);
  end else begin
    MessageBeep(MB_ICONWARNING);
    Renderer := ShowFile(FFileName);
    if Renderer.Form <> Form then begin
      Form.Hide;
      Form.Clear;
    end;
  end;
end {TUIPreviewEngineManager.actSelectEngineExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.actSelectRendererExecute(Sender: TObject);
var
  actSelectRenderer: TAction;
  Renderer: TRenderer;
  Form: TCustomPreviewEngineForm;
begin
  actSelectRenderer := Sender as TAction;
  Renderer := TObject(actSelectRenderer.Tag) as TRenderer;
  Form := Renderer.Form;

  if Form.Enabled and Renderer.TryShowFile(FFileName) then begin
    Form.Show;
    SetSelectedRenderer(Renderer);
  end else begin
    {$MESSAGE HINT 'TODO: What to do when the renderer couldn’t render? — MCO 10-02-2015'}
  end;
end {TUIPreviewEngineManager.actSelectRendererExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TPreviewEngineUIManager.actSelectRendererUpdate(Sender: TObject);
var
  actSelectRenderer: TAction;
  Renderer: TRenderer;
  Form: TCustomPreviewEngineForm;
begin
  actSelectRenderer := Sender as TAction;
  Renderer := TObject(actSelectRenderer.Tag) as TRenderer;
  Form := Renderer.Form;

  actSelectRenderer.Enabled := (FFileName <> '') and Form.Enabled;
  if actSelectRenderer.Enabled then begin
    actSelectRenderer.Checked := Renderer.CouldShowFile(FFileName);
  end;
end {TUIPreviewEngineManager.actSelectRendererUpdate};



end.
