unit F_Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  System.Generics.Collections,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.ComCtrls,
  Vcl.StdCtrls, Vcl.ToolWin, Vcl.ImgList, System.Actions, Vcl.ActnList,
  Vcl.ActnMan, Vcl.ActnCtrls, Vcl.PlatformDefaultStyleActnCtrls, Vcl.StdActns,
  U_ZaapSentinel2, U_PreviewEngine, U_PreviewEngineUIManager,
  M_Main;

type
  TfrmMain = class(TForm)
    tbcPreview: TTabControl;
    acttbMain: TActionToolBar;
    ActionManager: TActionManager;
    actRefresh: TAction;
    actAutorefresh: TAction;
    actViewSentinel: TAction;
    actViewImageInfo: TAction;
    actActivateZTreeWin: TAction;
    actViewFullScreen: TAction;
    pnlError: TPanel;
    actViewTopmost: TAction;
    lblError: TLabel;
    actFileExit: TFileExit;
    actWindowMinimize: TAction;
    pnlRight: TPanel;
    actPreviewEngine: TAction;
    actEngineManage: TAction;
    actEngineAutoselect: TAction;
    actFileProperties: TAction;
    sbrMain: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure ApplicationException(Sender: TObject; E: Exception);
    procedure actAutoRefreshExecute(Sender: TObject);
    procedure actRefreshExecute(Sender: TObject);
    procedure actViewSentinelExecute(Sender: TObject);
    procedure actViewImageInfoExecute(Sender: TObject);
    procedure actActivateZTreeWinExecute(Sender: TObject);
    procedure actActivateZTreeWinUpdate(Sender: TObject);
    procedure actViewFullScreenExecute(Sender: TObject);
    procedure pnlErrorDblClick(Sender: TObject);
    procedure actViewTopmostUpdate(Sender: TObject);
    procedure actViewTopmostExecute(Sender: TObject);
    procedure actWindowMinimizeExecute(Sender: TObject);
    procedure actPreviewEngineExecute(Sender: TObject);
    procedure actEngineAutoselectExecute(Sender: TObject);
    procedure actFilePropertiesExecute(Sender: TObject);
    procedure EngineManagerRendererSelected(Sender: TObject);
    procedure sbrMainDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; const Rect: TRect);
  private
    { Private declarations }
//    FWatcher: TThread;
    FInitialized: Boolean;
    FEngineManager: TPreviewEngineUIManager;

    FFileIconBitmap: TBitmap;

    procedure LoadSentinel;
    procedure ProcessPamphlet;
  public
    { Public declarations }
    FSentinel: TZaapSentinelFile;
  end;

var
  frmMain: TfrmMain;

implementation
uses
  System.IOUtils, System.Types, System.Masks, System.Generics.Defaults,
  Winapi.PsApi, Winapi.ShellAPI,
  Vcl.Imaging.GIFImg, Vcl.Imaging.jpeg, Vcl.Imaging.pngimage,
  U_ShellItemImage,
  F_Sentinel, F_Info;

{$R *.dfm}


{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  Application.OnException := ApplicationException;

  FEngineManager := TPreviewEngineUIManager.Create(ActionManager,
                                                   tbcPreview,
                                                   actPreviewEngine);
  FEngineManager.OnRendererSelected := EngineManagerRendererSelected;
end {TfrmMain.FormCreate};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  FFileIconBitmap.Free;
  FSentinel.Free;
  FEngineManager.Free;
end {TfrmMain.FormDestroy};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  actAutorefresh.Checked := False;
  Application.OnException := nil;
end {TfrmMain.FormCloseQuery};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.ApplicationException(Sender: TObject; E: Exception);
begin
  MessageBeep(MB_ICONERROR);
  lblError.Caption := E.Message;
  lblError.Font.Color := clRed;
  pnlError.Show;
end {TfrmMain.ApplicationException};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.EngineManagerRendererSelected(Sender: TObject);
var
  Renderer: TRenderer;
  Summary: string;
begin
  Renderer := FEngineManager.SelectedRenderer;
  sbrMain.Panels.BeginUpdate;
  try
    if Assigned(Renderer) then begin
      sbrMain.Panels[1].Text := Renderer.DisplayName + ' (' + Renderer.Form.Caption + ')';
      Summary := Renderer.SummarizeInfo;
      if Summary = #0 then begin
        sbrMain.Panels[2].Text := '';
        sbrMain.Panels[2].Bevel := pbNone;
      end else begin
        sbrMain.Panels[2].Text := Summary;
        sbrMain.Panels[2].Bevel := pbLowered;
      end;
    end else begin
      sbrMain.Panels[1].Text := '(no preview engine)';
      sbrMain.Panels[2].Bevel := pbNone;
    end;

    if frmInfo.Visible then begin
      frmInfo.StartPopulatingLines(FEngineManager.Filename, True);
      try
        if Assigned(Renderer) then
          Renderer.PopulateInfo(frmInfo);
      finally
        frmInfo.StopPopulatingLines;
      end;
    end;
  finally
    sbrMain.Panels.EndUpdate;
  end;
end {TfrmMain.EngineManagerRendererSelected};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.LoadSentinel;
var
  Sentinels: TStringDynArray;
  SentinelFile: string;
begin
  if Assigned(FSentinel) then
    Exit;

  Sentinels := TZaapSentinelFile.FindAllSentinels;
  if Length(Sentinels) > 0 then begin
    {$MESSAGE HINT 'TODO: what if there are multiple sentinels available?'}
    SentinelFile := Sentinels[0];
  end else begin
    SentinelFile := TPath.Combine(GetEnvironmentVariable('APPDATA'), 'ZTreeWin') + '\zbar.dat';
    if not PromptForFileName(SentinelFile,
                            'ZTreeWin Sentinel File (zbar.dat)|zbar.dat',
                            '.dat',
                            'Please select zbar.dat') then
      raise Exception.Create('No zbar.dat sentinel file found! Are any ZTreeWin instances running with /ZB?');
  end;

  FSentinel := TZaapSentinelFile.Create(SentinelFile);
  actActivateZTreeWin.Update;
end {TfrmMain.LoadSentinel};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actRefreshExecute(Sender: TObject);
begin
  LoadSentinel;

  if FSentinel.Refresh then begin
    ProcessPamphlet;
  end else if FSentinel.HasProblem then begin
    frmInfoSentinel.PopulateProblems(FSentinel, True);
    frmInfoSentinel.Show;
  end;
end {TfrmMain.actRefreshExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actViewFullScreenExecute(Sender: TObject);
var
  Index: Integer;
begin
  Index := actViewFullScreen.SecondaryShortCuts.IndexOfShortCut('Esc');
  if BorderStyle <> bsNone then begin
    BorderStyle := bsNone;
    Tag := Ord(WindowState);
    WindowState := wsMaximized;
    acttbMain.Visible := False;
    FormStyle := fsStayOnTop;
    if Index = -1 then
      actViewFullScreen.SecondaryShortCuts.Add('Esc');
  end else begin
    FormStyle := fsNormal;
    BorderStyle := bsSizeable;
    acttbMain.Visible := True;
    WindowState := TWindowState(Tag);
    if Index > -1 then
      actViewFullScreen.SecondaryShortCuts.Delete(Index);
  end;
end {TfrmMain.actViewFullScreenExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actViewImageInfoExecute(Sender: TObject);
begin
  frmInfo.Visible := actViewImageInfo.Checked;
  if frmInfo.Visible and Assigned(FEngineManager.SelectedRenderer) then begin
    frmInfo.StartPopulatingLines(FEngineManager.Filename, True);
    try
      FEngineManager.SelectedRenderer.PopulateInfo(frmInfo);
    finally
      frmInfo.StopPopulatingLines;
    end;
  end;
end {TfrmMain.actViewImageInfoExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actViewSentinelExecute(Sender: TObject);
begin
  frmInfoSentinel.Visible := actViewSentinel.Checked;
  if frmInfoSentinel.Visible and Assigned(FSentinel) then
    frmInfoSentinel.Populate(FSentinel);
end {TfrmMain.actViewSentinelExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actViewTopmostExecute(Sender: TObject);
begin
  if actViewTopmost.Checked then
    Self.FormStyle := fsStayOnTop
  else
    Self.FormStyle := fsNormal;
end {TfrmMain.actViewTopmostExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actViewTopmostUpdate(Sender: TObject);
begin
  actViewTopmost.Checked := (Self.FormStyle = fsStayOnTop);
end {TfrmMain.actViewTopmostUpdate};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actWindowMinimizeExecute(Sender: TObject);
begin
  if WindowState = wsMinimized then begin
    WindowState := TWindowState(Tag);
  end else begin
    Tag := Ord(WindowState);
    WindowState := wsMinimized;
  end;
end {TfrmMain.actWindowMinimizeExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actActivateZTreeWinExecute(Sender: TObject);
begin
  if Assigned(FSentinel) and IsWindow(FSentinel.Pamphlet.HWnd) then begin
    if GetForegroundWindow <> FSentinel.Pamphlet.HWnd then begin
      ShowWindow(FSentinel.Pamphlet.HWnd, SW_RESTORE);
      SetForegroundWindow(FSentinel.Pamphlet.HWnd);
    end else begin
      SetForegroundWindow(Handle);
      Activate;
      SetFocus;
    end;
  end;
end {TfrmMain.actActivateZTreeWinExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actActivateZTreeWinUpdate(Sender: TObject);
begin
  actActivateZTreeWin.Enabled := Assigned(FSentinel);
end {TfrmMain.actActivateZTreeWinUpdate};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actFilePropertiesExecute(Sender: TObject);
var
  sei: TShellExecuteInfo;
  ThisMon: TMonitor;
  i: Integer;
begin
//  CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
  ZeroMemory(@sei, SizeOf(sei));
  sei.cbSize := SizeOf(sei);
  sei.fMask  := SEE_MASK_INVOKEIDLIST;
  sei.Wnd    := Self.Handle;
  sei.lpVerb := 'properties';
  sei.lpFile := PChar(FSentinel.ThisPath);
  sei.nShow  := SW_SHOW;
  {$MESSAGE HINT 'TODO: Make these choices optional? — MCO 13-02-2015'}
  // on multimon setups, show the properties on the other monitor
  if (Self.FormStyle = fsStayOnTop) and (Screen.MonitorCount > 1) then begin
    ThisMon := Screen.MonitorFromWindow(Self.Handle);
    if ThisMon <> Screen.PrimaryMonitor then begin
      sei.hMonitor := Screen.PrimaryMonitor.Handle;
      sei.fMask := sei.fMask or SEE_MASK_HMONITOR;
    end else begin
      for i := 0 to Screen.MonitorCount do begin
        if Screen.Monitors[i] <> ThisMon then begin
          sei.hMonitor := Screen.PrimaryMonitor.Handle;
          sei.fMask := sei.fMask or SEE_MASK_HMONITOR;
          Break;
        end;
      end {for};
    end;
  end;
  if not ShellExecuteEx(@sei) then begin
    RaiseLastOSError
  end else if (Self.FormStyle = fsStayOnTop) and (Screen.MonitorCount = 1) then begin
    {$MESSAGE HINT 'TODO: Make this behaviour optional? — MCO 13-02-2015'}
    Self.WindowState := wsMinimized;
  end;
end {TfrmMain.actFilePropertiesExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actPreviewEngineExecute(Sender: TObject);
begin
  FEngineManager.NextRenderer;
end {TfrmMain.actPreviewEngineExecute};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.ProcessPamphlet;
var
  Buffer: array[0..1023] of Char;
  BufferSize: integer;
  i: Integer;
  Shortcut: TShortcut;
begin
  Caption := FSentinel.ThisPath + ' - ' + Application.Title;

  Screen.Cursor := crHourGlass;
  try
    Application.ProcessMessages;

    if frmInfoSentinel.Visible then
      frmInfoSentinel.Populate(FSentinel);

    // process shortcut keys
    if not FInitialized then begin
      FInitialized := True;
    end else begin
      if FSentinel.Pamphlet.Command = '^' then begin
        case FSentinel.Pamphlet.CommandChar of
          Char(VK_F4): begin
            Close;
          end;
          'R': begin
            FSentinel.Reset;
            FInitialized := False;
          end;
          else begin
            Shortcut := scCtrl or scShift or Ord(FSentinel.Pamphlet.CommandChar);
            for i := 0 to ActionManager.ActionCount - 1 do begin
              OutputDebugString(PChar(Format('%s %d', [ActionManager.Actions[i].Name, ActionManager.Actions[i].ShortCut and not (scCtrl or scShift)])));
              if (ActionManager.Actions[i].ShortCut = Shortcut) or (ActionManager.Actions[i].SecondaryShortCuts.IndexOfShortCut(Shortcut) > -1) then begin
                ActionManager.Actions[i].Execute;
                // DIRTY HACK: a HelpContext of -1 means we shouldn't process the pamphlet after having processed this action
//                if ActionManager.Actions[i].HelpContext = -1 then
                  Exit
//                else
//                  Break;
              end;
            end {for};
          end {else};
        end {case CommandChar};
      end {if};
    end {if};

    tbcPreview.Tabs[tbcPreview.TabIndex] := ExtractFileName(FSentinel.ThisPath);

    pnlError.Hide;
    try
      if FSentinel.ThisPath <> FEngineManager.Filename then begin
        FreeAndNil(FFileIconBitmap);
        sbrMain.Invalidate;
      end;

      {$MESSAGE HINT 'TODO: also only when the file name changes?'}
      FEngineManager.ShowFile(FSentinel.ThisPath);
    except
      MessageBeep(MB_ICONERROR);
      BufferSize := ExceptionErrorMessage(ExceptObject, ExceptAddr, Buffer, Length(Buffer));
      lblError.Caption := Copy(Buffer, 1, BufferSize);
      lblError.Font.Color := clRed;
      pnlError.Show;
    end;
  finally
    Screen.Cursor := crDefault;
  end;
end {TfrmMain.DoRefresh};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.sbrMainDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; const Rect: TRect);
var
  W, H: integer;
begin
  if Panel.Text = '(icon)' then begin
    if (FFileIconBitmap = nil) and (FSentinel <> nil) then begin
      W := Rect.Width;
      H := Rect.Height;
      if W > H then
        W := H
      else
        H := W;
      FFileIconBitmap := U_ShellItemImage.GetFileIcon(FSentinel.ThisPath, W, H);
      FFileIconBitmap.Transparent := False;
    end;
    StatusBar.Canvas.Lock;
    try
      StatusBar.Canvas.FillRect(Rect);
      if FFileIconBitmap <> nil then
        StatusBar.Canvas.Draw(Rect.Left, Rect.Top, FFileIconBitmap);
    finally
      StatusBar.Canvas.Unlock;
    end;
  end;
end {TfrmMain.sbrMainDrawPanel};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.pnlErrorDblClick(Sender: TObject);
begin
  pnlError.Hide;
end;

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actAutoRefreshExecute(Sender: TObject);
begin
  try
    if actAutorefresh.Checked then
      LoadSentinel;

//    actRefresh.Enabled := not actAutoRefresh.Checked;
    while actAutorefresh.Checked do begin
      if FSentinel.WaitForUpdate(100) then
        ProcessPamphlet;
      Application.ProcessMessages;
    end {while};

//    if not actAutoRefresh.Checked then begin
//      if Assigned(FWatcher) then begin
//        FWatcher.Terminate;
//        FWatcher := nil;
//      end;
//    end else begin
//      LoadSentinel;
//
//      FWatcher := TThread.CreateAnonymousThread(procedure
//      var
//        ErrClass, ErrMsg: string;
//      begin
//        try
//          try
//            while not TThread.CheckTerminated do begin
//              if FSentinel.WaitForUpdate(1000) then begin
//                TThread.Synchronize(TThread.CurrentThread, procedure
//                begin
//                  ProcessPamphlet;
//                end);
//              end;
//            end;
//          except
//            on E: Exception do begin
//              ErrClass := '' + E.ClassName;
//              ErrMsg := '' + E.Message;
//              TThread.Synchronize(TThread.CurrentThread, procedure
//              begin
//                MessageBox(Handle, PChar(ErrMsg), PChar(ErrClass), MB_ICONERROR);
//                FSentinel.Reset;
//              end);
//            end;
//          end;
//        finally
//          TThread.Synchronize(TThread.CurrentThread, procedure
//          begin
//            FWatcher := nil;
//            actAutoRefresh.Checked := Assigned(FWatcher);
//            actRefresh.Enabled := not actAutoRefresh.Checked;
//          end);
//        end;
//      end);
//      FWatcher.Start;
//    end;
  finally
//    actAutoRefresh.Checked := Assigned(FWatcher);
//    actRefresh.Enabled := not actAutoRefresh.Checked;
  end;
end {TfrmMain.chkAutoRefreshClick};

{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.actEngineAutoselectExecute(Sender: TObject);
begin
  FEngineManager.ShowFile(FSentinel.ThisPath);
end {TfrmMain.actEngineAutoselectExecute};

end.
