{

This unit is part of MKVExtractGUI2 project
(C) Eugene Pashin, 2010-2011
pashin@rambler.ru

}
unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, Buttons, StdCtrls, ExtCtrls, CheckLst, ShellApi, XPMan,
  ActnList;

type
  TfrmMain = class(TForm)
    StatusBar1: TStatusBar;
    edInput: TLabeledEdit;
    edOutput: TLabeledEdit;
    Label1: TLabel;
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    btnExtract: TButton;
    cbSource: TCheckBox;
    OpenDialog1: TOpenDialog;
    CheckListBox1: TCheckListBox;
    Timer1: TTimer;
    Button1: TButton;
    btnPause: TButton;
    btnAbort: TButton;
    SpeedButton3: TSpeedButton;
    SpeedButton4: TSpeedButton;
    XPManifest1: TXPManifest;
    Label2: TLabel;
    btnBatch: TButton;
    Label3: TLabel;
    cmbChapFormat: TComboBox;
    Bevel1: TBevel;
    ActionList1: TActionList;
    atnInputFile: TAction;
    atnOutputDir: TAction;
    atnCheckAll: TAction;
    atnUncheckAll: TAction;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure cbSourceClick(Sender: TObject);
    procedure btnExtractClick(Sender: TObject);
    procedure edInputChange(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure btnPauseClick(Sender: TObject);
    procedure btnAbortClick(Sender: TObject);
    procedure Label2Click(Sender: TObject);
    procedure btnBatchClick(Sender: TObject);
    procedure atnInputFileExecute(Sender: TObject);
    procedure atnOutputDirExecute(Sender: TObject);
    procedure atnUncheckAllExecute(Sender: TObject);
    procedure atnCheckAllExecute(Sender: TObject);
    procedure CheckListBox1ClickCheck(Sender: TObject);
  private
    ExtList: TStringList;
    TrackCaptionList: TStringList;
    procedure SaveSettings();
    procedure LoadSettings();
    procedure SetButtons();
    procedure SetPauseButton();
    procedure EnableControls(AEnable: Boolean);
    function GetCheckedCount(): integer;
    procedure CheckAll(Checked: Boolean);
    procedure UpdateListCaptions(); // just for screen readers
  protected
    procedure RegisterHotKeys();
    procedure UnRegisterHotKeys();
    procedure WMHotkey(var Msg: TMessage); message WM_HOTKEY;
    procedure WMDropFiles(var Msg: TMessage); message WM_DROPFILES;
  public
    ScreenReader: Boolean;
    procedure OpenFile(FileName: TFileName);
    procedure DoExtract(Simulate: Boolean = False);
    procedure Status(S: string = '');
  end;

var
  frmMain: TfrmMain;

implementation

uses
  utils, filectrl, IniFiles, ConsoleRedirect, siparser, StrUtils,
  egg, textview, W7TaskBar;

var
  Extractor: TMkvExtractor;
  ShowEgg: Boolean;
{$R *.dfm}
  { TForm1 }

procedure TfrmMain.Button1Click(Sender: TObject);
const
  ABOUT =
    'The next little step from MKVExtractGUI 1.6.4.1:'#13 +
    ''#13 +
    '+ Added .ogm chapter extraction'#13 +
    '+ Added command-line support'#13 +
    '+ Added progress indicator'#13 +
    '+ Added process control'#13 +
    '+ Added chapters extraction'#13 +
    '+ Added language-id display'#13 +
    ''#13 +
    '(C) Eugene "Jekil" Pashin, 2010-2011'#13 +
    'pashin@rambler.ru'#13#13 +
    'Regards to Bunkus! :)'#13;
begin
  if ShowEgg then
    frmEgg.Execute()
  else
    Application.MessageBox(ABOUT, 'Just about...', MB_OK + MB_ICONINFORMATION);

  ShowEgg := True;
end;

procedure TfrmMain.cbSourceClick(Sender: TObject);
begin
  SetButtons();
end;

procedure TfrmMain.CheckAll(Checked: Boolean);
var
  i: integer;
begin
  for i := 0 to CheckListBox1.Count - 1 do
    CheckListBox1.Checked[i] := Checked;
  UpdateListCaptions();
end;

procedure TfrmMain.CheckListBox1ClickCheck(Sender: TObject);
begin
  UpdateListCaptions();
end;

procedure TfrmMain.edInputChange(Sender: TObject);
begin
  SetButtons();
end;

procedure TfrmMain.EnableControls(AEnable: Boolean);
begin
  SpeedButton1.Enabled := AEnable;
  SpeedButton2.Enabled := AEnable;
  CheckListBox1.Enabled := AEnable;
  cbSource.Enabled := AEnable;
  btnExtract.Enabled := AEnable;
  edInput.Enabled := AEnable;
  edOutput.Enabled := AEnable;

  SpeedButton3.Enabled := AEnable;
  SpeedButton4.Enabled := AEnable;

  btnPause.Enabled := not AEnable;
  btnAbort.Enabled := not AEnable;
  btnBatch.Enabled := AEnable;
  Label3.Enabled := AEnable;

  cmbChapFormat.Enabled := AEnable;
end;

procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  UnRegisterHotKeys();
  SaveSettings();
  Extractor.Free();
  ExtList.Free();
  TrackCaptionList.Free();
end;

procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if Timer1.Enabled then
  begin
    CanClose := Application.MessageBox(
      'Extraction process will be aborted. Continue with close?', 'Waring',
      MB_OKCANCEL + MB_ICONEXCLAMATION) = IDOK;
    if CanClose then
    begin
      Extractor.Abort();
      Sleep(200);
    end;
  end;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
const
  TOOLNIX_MSG = 'Put me in mkvtoolnix dir, please!'#13#13 +
    'In case if you don''t have it - just press "OK" to get it from'#13;
  TOOLNIX_URI = 'http://www.bunkus.org/videotools/mkvtoolnix/win32/';

  CODENAME = 'Resurrection';
var
  ver: string;
begin
  RegisterHotKeys();
  // SpeedButton1.Height := edInput.Height+4;
  // SpeedButton2.Height := edOutput.Height+4;

  ShowEgg := False;
  ver := CardinalToVersionString(GetFileVersion(Application.ExeName), False);
  Caption := Format('%s-%s ("%s")', [Caption, ver, CODENAME]);

  SetProgressState(tbpsNone);

  if not Extractor.IsMkvtoolnixHere() then
  begin
    if Application.MessageBox(TOOLNIX_MSG + TOOLNIX_URI, PChar(Caption),
      MB_OKCANCEL + MB_ICONSTOP) = IDOK then
      ShellExecute(Handle, 'open', TOOLNIX_URI, nil, nil, SW_SHOWNORMAL);
    Application.Terminate;
    Exit;
  end;

  DragAcceptFiles(Handle, True);
  Extractor := TMkvExtractor.Create;
  ExtList := TStringList.Create;
  TrackCaptionList := TStringList.Create;
  LoadSettings();

  if ParamStr(1) <> '' then
    OpenFile(ParamStr(1));

  EnableControls(True);
  SetButtons();
end;

procedure TfrmMain.Label2Click(Sender: TObject);
begin
  ShellExecute(0, 'open', PChar(Label2.Caption), nil, nil, SW_SHOWNORMAL);
end;

procedure TfrmMain.LoadSettings;
var
  ini: TIniFile;
begin
  ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  Left := ini.ReadInteger('General', 'Left', Left);
  Top := ini.ReadInteger('General', 'Top', Top);
  Width := ini.ReadInteger('General', 'Width', Width);
  Height := ini.ReadInteger('General', 'Height', Height);
  cbSource.Checked := ini.ReadBool('General', 'Source', cbSource.Checked);
  edOutput.Text := ini.ReadString('General', 'Output', edOutput.Text);
  cmbChapFormat.ItemIndex := ini.ReadInteger('General', 'ChapterFormat', 0);
  ScreenReader := ini.ReadBool('General', 'ScreenReader', False);
  ini.Free;
end;

procedure TfrmMain.OpenFile(FileName: TFileName);
var
  S, desc: string;
  _lang: string;
  i, parcnt: integer;
  seg, seg0: TSegment;
begin
  edInput.Text := FileName;
  if edOutput.Text = '' then
    edOutput.Text := ExtractFilePath(edInput.Text);

  TrackCaptionList.Clear();
  CheckListBox1.Clear(); // to clear selection
  ExtList.Clear();

  if not FileExists(edInput.Text) then
  begin
    Status('Error opening file.');
    Application.MessageBox('File does not exists!', 'Error',
      MB_OK + MB_ICONERROR);
    Exit;
  end;

  Status('Executing mkvinfo...');

  SetProgressState(tbpsIndeterminate);
  Application.ProcessMessages();

  if not Extractor.LoadFromFile(edInput.Text) then
  begin
    Status('Error opening file.');
    SetProgressState(tbpsError);
    Application.MessageBox('mkvinfo does not recognized the file!', 'Error',
      MB_OK + MB_ICONERROR);
    Exit;
  end;

  SetProgressState(tbpsNone);

  seg0 := Extractor.info.Segments['Segment tracks'];
  if Assigned(seg0) then
    for i := 0 to seg0.Count - 1 do
    begin
      seg := seg0[i];
      _lang := IfThen(seg.Values['Language'] <> '', seg.Values['Language'], 'eng');
      S := Format('Track %s: %s, %s [%s] %s ', [seg.Values['Track number'],
        seg.Values['Track type'], seg.Values['Codec ID'], _lang,
        seg.Values['Name']]);
      if seg.Values['Track type'] = 'audio' then
        S := S + Format('(%sch, %shz)', [seg['Audio track'].Values['Channels'],
          seg['Audio track'].Values['Sampling frequency']]);
      // CheckListBox1.Items.Add(s);
      TrackCaptionList.Add(S);
      ExtList.Add(Format('tracks=%d', [i + 1]));
    end;

  seg0 := Extractor.info.Segments['Attachments'];
  if Assigned(seg0) then
    for i := 0 to seg0.Count - 1 do
    begin
      seg := seg0[i];
      desc := seg.Values['File description'];
      desc := IfThen(desc = '', '', Format('(%s)', [desc]));
      S := Format('Attachment: %s, %s, %s bytes %s', [seg.Values['File name'],
        seg.Values['Mime type'], seg.Values['File data, size'], desc]);
      // CheckListBox1.Items.Add(s);
      TrackCaptionList.Add(S);
      ExtList.Add(Format('attachments=%d', [i + 1]));
    end;

  seg0 := Extractor.info.Segments['Chapters'];
  if Assigned(seg0) then
  begin
    // count "ChapterAtom" items!

    //
    seg := seg0[0];
    if seg0.Count > 1 then
      S := Format('Chapters: %d', [seg0.Count])
    else
    begin
      // вычитаем количество левых параметров EditionEntry
      parcnt := 0;
      for i := 0 to seg.Count - 1 do
        if seg[i].Name <> 'ChapterAtom' then
          Inc(parcnt);

      S := Format('Chapters: %d', [seg.Count - parcnt]);
    end;
    // CheckListBox1.Items.Add(s);
    TrackCaptionList.Add(S);
    ExtList.Add('chapters');
  end;

  Status();
  UpdateListCaptions();
  ActiveControl := CheckListBox1;

end;

procedure TfrmMain.WMHotkey(var Msg: TMessage);
var
  i: integer;
begin
  i := Lo(Msg.WParam) - 154;
  if i < CheckListBox1.Count then
    CheckListBox1.Checked[i] := not CheckListBox1.Checked[i]
  else
    Beep();
  UpdateListCaptions();
end;

procedure TfrmMain.RegisterHotKeys;
var
  i, id: integer;
begin
  for i := 1 to 9 do
  begin
    id := GlobalAddAtom(PChar(Format('hotkey%d', [i])));
    RegisterHotKey(Handle, id, MOD_CONTROL, Ord(IntToStr(i)[1]));
  end;
end;

procedure TfrmMain.UnRegisterHotKeys;
var
  i, id: integer;
begin
  for i := 1 to 9 do
  begin
    id := GlobalFindAtom(PChar(Format('hotkey%d', [i])));
    if id <> 0 then
      UnRegisterHotKey(Handle, id);
  end;
end;

procedure TfrmMain.SaveSettings;
var
  ini: TIniFile;
begin
  try
    ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
    ini.WriteInteger('General', 'Left', Left);
    ini.WriteInteger('General', 'Top', Top);
    ini.WriteInteger('General', 'Width', Width);
    ini.WriteInteger('General', 'Height', Height);
    ini.WriteBool('General', 'Source', cbSource.Checked);
    ini.WriteString('General', 'Output', edOutput.Text);
    ini.WriteInteger('General', 'ChapterFormat', cmbChapFormat.ItemIndex);
    ini.Free;
  except
    Application.MessageBox('Can''t save my settings ini-file, sorry.',
      'Permission problems', MB_OK + MB_ICONWARNING);
  end;
end;

procedure TfrmMain.SetButtons;
begin
  edOutput.Enabled := not cbSource.Checked;
  SpeedButton2.Enabled := not cbSource.Checked;
  btnExtract.Enabled := Trim(edInput.Text) <> '';
  SetPauseButton();
end;

procedure TfrmMain.SetPauseButton;
// var
// ps: TBPF;
begin
  btnPause.Caption := IfThen(Extractor.IsPaused, 'Resume', 'Pause');
  if Extractor.IsPaused then
    // ps:=TBPF_PAUSED
  else
    ;
  // ps:=TBPF_NORMAL;
  // SetTaskBarState(Handle, ps);
end;

procedure TfrmMain.Status(S: string = '');
begin
  StatusBar1.SimpleText := S;
  Application.ProcessMessages;
end;

procedure TfrmMain.Timer1Timer(Sender: TObject);
var
  prog_cur: integer;
  ps, curMode: string;
begin
  if Extractor.IsProcessActive() then
  begin
    ps := IfThen(Extractor.IsPaused, '[Paused]', '');
    prog_cur := Extractor.Progress();
    curMode := Extractor.Console.CurrentQueueItemName();
    Status(Format('Extracting %s: %d%% %s', [curMode, prog_cur, ps]));
    SetProgressValue(prog_cur, 100);
  end
  else
  begin
    EnableControls(True);
    SetButtons();
    Timer1.Enabled := False;
    SetProgressState(tbpsNone);
    if Extractor.Console.Aborted then
    begin
      Status('Aborted.');
    end
    else
    begin
      MessageBeep(0);
      case Extractor.Console.ErrorCode() of
        1:
          Status('Pipe Error.');
        2:
          Status('Process Error.');
        3:
          Status('Extracted OK.');
      else
        Status('Error.');
      end;
    end;
  end;
end;

procedure TfrmMain.UpdateListCaptions;
var
  i, Sel: integer;
  Checked: array of Boolean;
  S: string;
begin
  SetLength(Checked, TrackCaptionList.Count);
  for i := 0 to TrackCaptionList.Count - 1 do
  begin
    if CheckListBox1.Count > 0 then
      Checked[i] := CheckListBox1.Checked[i]
    else
      Checked[i] := False;
  end;

  Sel := CheckListBox1.ItemIndex;
  CheckListBox1.Clear();
  for i := 0 to TrackCaptionList.Count - 1 do
  begin
    S := TrackCaptionList[i];
    if ScreenReader then
      S := IfThen(Checked[i], '[check] ', '') + S;
    CheckListBox1.Items.Add(S);
  end;

  CheckListBox1.ItemIndex := Sel;
  for i := 0 to TrackCaptionList.Count - 1 do
    CheckListBox1.Checked[i] := Checked[i];

  SetLength(Checked, 0);
end;

procedure TfrmMain.WMDropFiles(var Msg: TMessage);
var
  pFileName: array [0 .. 1024] of char;
  FileName: string;
const
  VALID_EXT = '.MKV|.MKA';
begin
  DragQueryFile(THandle(Msg.WParam), 0, pFileName, SizeOf(pFileName));
  FileName := string(pFileName);
  DragFinish(THandle(Msg.WParam));
  if Pos(UpperCase(ExtractFileExt(FileName)), VALID_EXT) > 0 then
    OpenFile(FileName)
  else
    Application.MessageBox('Only matroska files can be dropped here!', 'Error',
      MB_OK + MB_ICONERROR);
end;

procedure TfrmMain.btnPauseClick(Sender: TObject);
begin
  if Extractor.IsPaused then
  begin
    Extractor.Resume();
    SetProgressState(tbpsNormal);
  end
  else
  begin
    Extractor.Pause();
    SetProgressState(tbpsPaused);
  end;
  SetPauseButton();
  Timer1.OnTimer(Self);
end;

procedure TfrmMain.atnCheckAllExecute(Sender: TObject);
begin
  CheckAll(True);
end;

procedure TfrmMain.atnInputFileExecute(Sender: TObject);
begin
  OpenDialog1.FileName := edInput.Text;
  if OpenDialog1.Execute then
  begin
    OpenFile(OpenDialog1.FileName);
  end;
end;

procedure TfrmMain.atnOutputDirExecute(Sender: TObject);
var
  S: string;
begin
  S := edOutput.Text;
  if SelectDirectory('Select output path:', '', S) then
    edOutput.Text := S;
end;

procedure TfrmMain.atnUncheckAllExecute(Sender: TObject);
begin
  CheckAll(False);
end;

procedure TfrmMain.btnAbortClick(Sender: TObject);
begin
  Extractor.Abort();
end;

procedure TfrmMain.btnExtractClick(Sender: TObject);
begin
  if GetCheckedCount() = 0 then
  begin
    Application.MessageBox('Nothing to do!', 'Check''em', MB_OK + MB_ICONHAND);
    Exit;
  end;
  DoExtract();
end;

procedure TfrmMain.btnBatchClick(Sender: TObject);
const
  BATCH_HINT = 'rem Nothing to do!';
begin
  Extractor.CommandsPassedToConsole.Clear();
  if GetCheckedCount() = 0 then
  begin
    Extractor.CommandsPassedToConsole.Add(BATCH_HINT);
  end
  else
    DoExtract(True);
  frmTextView.Execute(Extractor.CommandsPassedToConsole.Text);
end;

procedure TfrmMain.DoExtract(Simulate: Boolean = False);
var
  i, k: integer;
  mode, trk, path, trklist: string;
  TaskList: TStringList;
const
  MODE_LIST: array [0 .. 2] of string = ('tracks', 'attachments', 'chapters');

begin
  Status();

  Extractor.OGM_Chapters := cmbChapFormat.ItemIndex = 1;

  if cbSource.Checked then
    path := ExtractFilePath(edInput.Text)
  else
    path := edOutput.Text + '\';

  TaskList := TStringList.Create;

  for i := 0 to CheckListBox1.Count - 1 do
    if CheckListBox1.Checked[i] then
    begin
      mode := ExtList.Names[i];
      if mode = '' then
      begin
        mode := ExtList[i];
        trk := '0'; // need no-empty value to make Extract work
      end
      else
        trk := ExtList.ValueFromIndex[i];
      // Extractor.Extract(mode, StrToIntDef(trk, 1), path);
      TaskList.Add(mode + '=' + trk);
    end;

  TaskList.Sort;

  // перебор типов по списку, накапливаем в строке IDы и передаём в Extract
  for i := 0 to Length(MODE_LIST) - 1 do
  begin
    trklist := '';
    for k := 0 to TaskList.Count - 1 do
      if TaskList.Names[k] = MODE_LIST[i] then
        trklist := trklist + TaskList.ValueFromIndex[k] + ',';

    if trklist <> '' then
      Extractor.Extract(MODE_LIST[i], trklist, path, Simulate);
  end;

  TaskList.Free;

  if not Simulate then
  begin
    EnableControls(False);
    Timer1.Enabled := True;
    Timer1.OnTimer(Self);
    SetProgressState(tbpsNormal);
    SetProgressValue(0, 100);
    Status('Extraction queued...');
  end;
end;

function TfrmMain.GetCheckedCount: integer;
var
  i: integer;
begin
  Result := 0;
  for i := 0 to CheckListBox1.Items.Count - 1 do
    if CheckListBox1.Checked[i] then
      Inc(Result);
end;

end.
