Updated windows installer script updated for latest Gtk2Hs version use a build for only a single ghc version (ghc 6.6.1) we previously included builds for both ghc 6.4.2 and 6.6 include the sourceview component

; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

[Setup]
AppName=Gtk2Hs
AppId=Gtk2Hs
AppVerName=Gtk2Hs 0.9.11.3
AppVersion=0.9.11.3
AppPublisher=The Gtk2Hs Team
AppPublisherURL=http://haskell.org/gtk2hs/
AppSupportURL=http://haskell.org/gtk2hs/
AppUpdatesURL=http://haskell.org/gtk2hs/

DefaultDirName={pf}\Gtk2Hs
OutputBaseFilename=gtk2hs-0.9.11.3

VersionInfoVersion=0.9.11.3
VersionInfoCopyright=Copyright (C) 2001-2007 The Gtk2Hs Team

Compression=lzma/max
SolidCompression=yes

ChangesEnvironment=yes

[Components]
Name: "gtk";     Description: "Gtk+ libraries"; Types: full compact custom; Flags: fixed
;Name: "gtk2hs1"; Description: "Gtk2Hs libraries for GHC 6.4.2"; Check: UseWithGhcVersion('6.4.2'); Types: full compact custom; Flags: fixed
Name: "gtk2hs2"; Description: "Gtk2Hs libraries for GHC 6.6.1"; Check: UseWithGhcVersion('6.6.1'); Types: full compact custom; Flags: fixed
Name: "docs";    Description: "API reference documentation"; Types: full
Name: "demos";   Description: "Source files for the Gtk2Hs demo programs"; Types: full

[Files]
Source: "gtk+-2.10.13\*";                        DestDir: "{app}";       Components: gtk;     Flags: ignoreversion recursesubdirs createallsubdirs;
;Source: "gtk2hs-0.9.11.3-ghc-6.4.2-gtk-2.10\*"; DestDir: "{app}";       Components: gtk2hs1; Flags: ignoreversion recursesubdirs createallsubdirs; AfterInstall: AfterPkgInstall;
Source: "gtk2hs-0.9.11.3-ghc-6.6.1-gtk-2.10\*"; DestDir: "{app}";       Components: gtk2hs2; Flags: ignoreversion recursesubdirs createallsubdirs; AfterInstall: AfterPkgInstall;
Source: "gtk2hs-0.9.11.3-demo\*";               DestDir: "{app}\demos"; Components: demos;   Flags: ignoreversion recursesubdirs createallsubdirs;
Source: "gtk2hs-0.9.11.3-docs\*";               DestDir: "{app}\docs";  Components: docs;    Flags: ignoreversion recursesubdirs createallsubdirs;
Source: "COPYING.txt";                          DestDir: "{app}";                            Flags: ignoreversion;
Source: "AUTHORS.txt";                          DestDir: "{app}";                            Flags: ignoreversion;

[Registry]
Root: HKCU; Subkey: "Environment"; ValueName: "Path"; ValueType: "string"; ValueData: "{app}\bin;{olddata}"; Check: NotOnPathAlready(); Flags: preservestringtype;

[Run]
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\glib.package.conf""";     StatusMsg: "Registering glib package...";     Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\cairo.package.conf""";    StatusMsg: "Registering cairo package...";    Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\svgcairo.package.conf"""; StatusMsg: "Registering svgcairo package..."; Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\gtk.package.conf""";      StatusMsg: "Registering gtk package...";      Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\glade.package.conf""";    StatusMsg: "Registering glade package...";    Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\soegtk.package.conf""";   StatusMsg: "Registering soegtk package...";   Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\gtkglext.package.conf"""; StatusMsg: "Registering gtkglext package..."; Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\sourceview.package.conf"""; StatusMsg: "Registering sourceview package..."; Flags: runhidden
[UninstallRun]
Filename: "{code:ghcpkg}"; Parameters: "unregister sourceview-0.9.11.3"; RunOnceId: "gtkglext"; Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister gtkglext-0.9.11.3";   RunOnceId: "gtkglext"; Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister soegtk-0.9.11.3";     RunOnceId: "soegtk";   Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister glade-0.9.11.3";      RunOnceId: "glade";    Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister gtk-0.9.11.3";        RunOnceId: "gtk";      Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister svgcairo-0.9.11.3";   RunOnceId: "svgcairo"; Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister cairo-0.9.11.3";      RunOnceId: "cairo";    Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister glib-0.9.11.3";       RunOnceId: "glib";     Flags: runhidden

[Code]
var
  GhcInstallDir: String;
  GhcInstallVersion: String;

  CheckingPage: TOutputProgressWizardPage;
  ErrorReportPage: TOutputMsgMemoWizardPage;
  InstallationErrorCaption: String;
  InstallationErrorMessage: String;
  InstallationErrorDetail: String;

function ghcpkg(Param: String): String;
begin
  Result := AddBackslash(GhcInstallDir) + 'bin\ghc-pkg.exe';
end;

function NotOnPathAlready(): Boolean;
var
  BinDir, Path: String;
begin
  Log('Checking if Gtk2Hs\bin dir is already on the %PATH%');
  if RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', Path) then
  begin // Successfully read the value
    Log('HKCU\Environment\PATH = ' + Path);
    BinDir := ExpandConstant('{app}\bin');
    Log('Looking for Gtk2Hs\bin dir in %PATH%: ' + BinDir + ' in ' + Path);
    if Pos(LowerCase(BinDir), Lowercase(Path)) = 0 then
    begin
      Log('Did not find Gtk2Hs\bin dir in %PATH% so will add it');
      Result := True;
    end
    else
    begin
      Log('Found Gtk2Hs bin dir in %PATH% so will not add it again');
      Result := False;
    end
  end
  else // The key probably doesn't exist
  begin
    Log('Could not access HKCU\Environment\PATH so assume it is ok to add it');
    Result := True;
  end;
end;

{ exec a program and return its output }
function ExecOutput(const Filename, Params: String): String;
var
  TmpFile: String;
  ResultCode: Integer;
begin
  Result := '';
  TmpFile := GenerateUniqueName(ExpandConstant('{tmp}'), 'tmp');
  if Exec(ExpandConstant('{cmd}'), '/C ' + Filename + ' ' + Params + ' > ' + TmpFile,
     '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
    if ResultCode = 0 then
      if LoadStringFromFile(TmpFile, Result) then
      begin
        Result := Trim(Result);
        Log('ExecOutput: running ' + Filename + ' succeded with output: ' + Result);
      end
      else
        Log('ExecOutput: running ' + Filename + ' succeded, but the temp file was not created')
    else
      Log('ExecOutput: running ' + Filename + ' failed, code: ' + IntToStr(ResultCode))
  else
    { That's really odd, we should always be able to exec the command interpreter! }
    Log('ExecOutput: cannot exec command interpreter, code: ' + IntToStr(ResultCode) + ', message: ' + SysErrorMessage(ResultCode));
end;

function CheckGhcVersionIsOk(const Path: String; var Version: String):Boolean;
begin
  Version := ExecOutput(AddBackslash(Path) + 'bin\ghc.exe', '--numeric-version');
  GhcInstallVersion := Version;

  Result := {-(Version = '6.4.2')
           or-} (Version = '6.6.1');
end;

var
  DetectValidGhcInstallationCached: Boolean;
  DetectValidGhcInstallationResult: Boolean;

function DetectValidGhcInstallation(): Boolean;
var
  HaveSomeGHCInstalled: Boolean;
  GHCVersion: String;

begin
if DetectValidGhcInstallationCached then
  Result := DetectValidGhcInstallationResult
else
begin
  Result := False;

  { first check for ghc on the path, then look in the registry }

  begin
    GhcInstallDir := ExecOutput('ghc.exe', '--print-libdir');
    if GhcInstallDir <> '' then
    begin
      StringChange(GhcInstallDir, '/', '\');
      Log('DetectValidGhcInstallation: found ghc on %PATH% with libdir: ' + GhcInstallDir);
      HaveSomeGHCInstalled := True;
      Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
    end;
  end;

  if (Result = False) and RegKeyExists(HKEY_CURRENT_USER, 'Software\Haskell\GHC') then
  begin
    Log('DetectValidGhcInstallation: found HKCU\Software\Haskell\GHC');
    HaveSomeGHCInstalled := True;
    RegQueryStringValue(HKEY_CURRENT_USER, 'Software\Haskell\GHC\ghc-6.6.1', 'InstallDir', GhcInstallDir);
    Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
  end;
{-
  if (Result = False) and RegKeyExists(HKEY_CURRENT_USER, 'Software\Haskell\GHC') then
  begin
    Log('DetectValidGhcInstallation: found HKCU\Software\Haskell\GHC');
    HaveSomeGHCInstalled := True;
    RegQueryStringValue(HKEY_CURRENT_USER, 'Software\Haskell\GHC\ghc-6.4.2', 'InstallDir', GhcInstallDir);
    Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
  end;
-}
  if (Result = False) and RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\Haskell\GHC') then
  begin
    Log('DetectValidGhcInstallation: found HKLM\SOFTWARE\Haskell\GHC');
    HaveSomeGHCInstalled := True;
    RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Haskell\GHC\ghc-6.6.1', 'InstallDir', GhcInstallDir);
    Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
  end;
{-
  if (Result = False) and RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\Haskell\GHC') then
  begin
    Log('DetectValidGhcInstallation: found HKLM\SOFTWARE\Haskell\GHC');
    HaveSomeGHCInstalled := True;
    RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Haskell\GHC\ghc-6.4.2', 'InstallDir', GhcInstallDir);
    Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
  end;
-}
  if Result then
    Log('DetectValidGhcInstallation: correct version of ghc found at: ' + GhcInstallDir)
  else if HaveSomeGHCInstalled and (GHCVersion <> '') then
  begin
    Log('DetectValidGhcInstallation: incorrect ghc version installed: ' + GHCVersion);
    InstallationErrorCaption := 'The version of GHC currently installed is not suitable.';
    InstallationErrorMessage := 'This version of Gtk2Hs requires GHC version 6.6.1.' #13#10 #13#10
                                'Setup found GHC version ' + GHCVersion + ' installed in the folder:' #13#10
                              + GhcInstallDir
  end
  else if HaveSomeGHCInstalled and (GhcInstallDir <> '') then
  begin
    Log('DetectValidGhcInstallation: some non-working version of ghc appears to be installed at: ' + GhcInstallDir);
    InstallationErrorCaption := 'GHC does not seem to be working.';
    InstallationErrorMessage := 'GHC does not appear to be installed correctly, try reinstalling GHC version 6.6.1' #13#10 #13#10
                                'Setup found what appears to be a non-working installation of GHC in the folder:' #13#10
                              + GhcInstallDir
  end
  else if HaveSomeGHCInstalled then
  begin
    Log('DetectValidGhcInstallation: corrupted ghc installation detected, probably messed up registry keys');
    InstallationErrorCaption := 'Setup did not find GHC on your computer.';
    InstallationErrorMessage := 'GHC does not appear to be installed (or the installation is corrupted), please install GHC version 6.6.1';
  end
  else
  begin
    Log('DetectValidGhcInstallation: no installation of ghc detected');
    InstallationErrorCaption := 'Setup did not find GHC on your computer.';
    InstallationErrorMessage := 'Gtk2Hs requires GHC to be installed first, please install GHC version 6.6.1' #13#10 #13#10
                                'If you installed GHC manually then make sure ghc.exe is on the path.';
  end;

  If not Result then
  begin
    Log(InstallationErrorCaption);
    Log(InstallationErrorMessage);
  end;

  DetectValidGhcInstallationResult := Result;
  DetectValidGhcInstallationCached := True;
end;
end;

function UseWithGhcVersion(const GhcVersion: String): Boolean;
begin
  DetectValidGhcInstallation();
  Result := (GhcVersion = GhcInstallVersion);
end;

var
  ChecksOk : Boolean;

function ShouldSkipPage(CurPageID: Integer): Boolean;
begin
  Log('ShouldSkipPage: CurPageID = ' + IntToStr(CurPageID));
  if CurPageID = ErrorReportPage.ID then
  begin
    if ChecksOk then
      Log('ChecksOk = True')
    else
      Log('ChecksOk = False');
    Result := ChecksOk
  end
  else
    Result := False;
end;

function NextButtonClick(CurPageID: Integer): Boolean;
var
  DllFiles, Offenders: array of String;
  Path, Offender: String;
  N, M, NumFiles: Integer;
begin
  Log('NextButtonClick: CurPageID = ' + IntToStr(CurPageID));
  if CurPageID = wpWelcome then
  begin
    if not DetectValidGhcInstallation() then
      ChecksOk := False
    else
    begin
      DllFiles := ['charset.dll', 'gspawn-win32-helper-console.exe',
        'gspawn-win32-helper.exe', 'iconv.dll', 'intl.dll',
        'jpeg62.dll', 'libatk-1.0-0.dll', 'libcairo-2.dll',
        'libgdk-win32-2.0-0.dll', 'libgdk_pixbuf-2.0-0.dll',
        'libglade-2.0-0.dll', 'libglib-2.0-0.dll',
        'libgmodule-2.0-0.dll', 'libgobject-2.0-0.dll',
        'libgthread-2.0-0.dll', 'libgtk-win32-2.0-0.dll',
        'libpango-1.0-0.dll', 'libpangocairo-1.0-0.dll',
        'libpangoft2-1.0-0.dll', 'libpangowin32-1.0-0.dll',
        'libpng13.dll', 'libxml2.dll', 'zlib1.dll'];
      NumFiles := GetArrayLength(DllFiles);
      SetArrayLength(Offenders, NumFiles);

      RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', Path);
      Path := GetSystemDir() + ';' + GetWinDir() + ';' + Path;

      Log('Checking DLL path: ' + Path);

      CheckingPage.SetText('Checking Gtk+ DLL search path...', '');
      CheckingPage.SetProgress(0, NumFiles);
      CheckingPage.Show;
      try
        M := 0;
        for N := 0 to NumFiles - 1 do
        begin
          Offender := FileSearch(DllFiles[N], Path);
          if Offender <> '' then
          begin
            Log('Found clashing dll/exe file at ' + Offender);
            Offenders[M] := Offender;
            M := M + 1;
          end
          CheckingPage.SetProgress(N+1, NumFiles);
          Sleep(10);
        end;
        if M > 0 then
        begin
          ChecksOk := False;
          InstallationErrorCaption := 'Setup found clashing DLL files on the search path.';
          InstallationErrorMessage := 'Please read the following description of the problem and how to fix it:';

          InstallationErrorDetail :=
              'For Haskell Gtk programs to work properly, the Gtk+ DLL files need to be in the DLL '
            + 'search path. However, the following DLLs files that Gtk+ uses are already on the '
            + 'system-wide search path:' #13#10

          for N := 0 to M - 1 do
            InstallationErrorDetail := InstallationErrorDetail + #13#10 + Offenders[N];

          InstallationErrorDetail := InstallationErrorDetail + #13#10 #13#10
              'The problem is that when you run a Haskell Gtk program these DLLs would get used '
            + 'rather than the proper versions of the Gtk+ DLLs installed by this setup program.'
            + 'This would almost certainly cause all your Haskell Gtk programs to not work.' #13#10 #13#10
              'You can fix this problem in a couple of ways:' #13#10 #13#10
              '1) You could uninstall or delete the program that installed the offending files '
            + '(eg it might be that you have an old version of Gtk+ installed).' #13#10 #13#10
              '2) You could modify the system-wide "Path" envrionment variable and remove the '
            + 'offending directory from it.'
        end
        else
          ChecksOk := True;
      finally
        CheckingPage.Hide;
      end;
    end;
  end
  Result := True;
end;

procedure PackageFileVarSubstitute(const PackageFile: String);
var
  PackageFileContent: String;
  PackageLibDir: String;
begin
  PackageLibDir := ExpandConstant('{app}');
  StringChange(PackageLibDir, '\', '/');

  LoadStringFromFile(PackageFile, PackageFileContent);

  StringChange(PackageFileContent, '${pkglibdir}', PackageLibDir);
  StringChange(PackageFileContent, '${GTK_BASEPATH}', PackageLibDir);

  SaveStringToFile(PackageFile, PackageFileContent, False);
  Log('Expanding variables in ' + PackageFile);
end;

procedure AfterPkgInstall;
begin
  if (ExtractFileExt(CurrentFileName) = '.conf') then
    PackageFileVarSubstitute(ExpandConstant(CurrentFileName));
end;

// Detect when the ready info page gets displayed
function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo,
                         MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo,
                         MemoTasksInfo: String): String;
begin
		Result := MemoDirInfo + NewLine
		        + NewLine
		        +	'Using GHC ' + GhcInstallVersion + ' installed at:' + NewLine
		        + Space + GhcInstallDir + NewLine
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
  BinDir, Path: String;
begin
  if (CurUninstallStep = usPostUninstall)
     and (RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'PATH', Path)) then
  begin
    BinDir := ExpandConstant('{app}\bin');
    if Pos(LowerCase(BinDir) + ';', Lowercase(Path)) <> 0 then
    begin
      StringChange(Path, BinDir + ';', '');
      RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'PATH', Path);
    end;
  end;
end;

procedure ErrorReportPageNotify(Sender: TWizardPage);
begin
  WizardForm.NextButton.Enabled := False;
  ErrorReportPage.Description      := InstallationErrorCaption;
  ErrorReportPage.SubCaptionLabel.Caption := InstallationErrorMessage;
  if InstallationErrorDetail = '' then
    ErrorReportPage.SubCaptionLabel.Height := 50
  else
  begin
    ErrorReportPage.RichEditViewer.RTFText := InstallationErrorDetail
    ErrorReportPage.RichEditViewer.Show();
  end;
end;

procedure InitializeWizard();
begin
  CheckingPage := CreateOutputProgressPage('Checking requirements', 'Checking for GHC and Gtk+');
  ErrorReportPage := CreateOutputMsgMemoPage(wpWelcome, 'Setup cannot continue', 'GHC or Gtk+ problem', 'Your installation of GHC or Gtk+ is messed up!', 'Foo bar!');
  ErrorReportPage.OnActivate := @ErrorReportPageNotify;
  ErrorReportPage.RichEditViewer.Hide();
end;

; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

[Setup]
AppName=Gtk2Hs
AppId=Gtk2Hs
AppVerName=Gtk2Hs 0.9.10.6
AppVersion=0.9.10.6
AppPublisher=The Gtk2Hs Team
AppPublisherURL=http://haskell.org/gtk2hs/
AppSupportURL=http://haskell.org/gtk2hs/
AppUpdatesURL=http://haskell.org/gtk2hs/

DefaultDirName={pf}\Gtk2Hs
OutputBaseFilename=gtk2hs-0.9.10.6

VersionInfoVersion=0.9.10.6
VersionInfoCopyright=Copyright (C) 2001-2007 The Gtk2Hs Team

Compression=lzma/max
SolidCompression=yes

ChangesEnvironment=yes

[Components]
Name: "gtk";     Description: "Gtk+ libraries"; Types: full compact custom; Flags: fixed
Name: "gtk2hs1"; Description: "Gtk2Hs libraries for GHC 6.4.2"; Check: UseWithGhcVersion('6.4.2'); Types: full compact custom; Flags: fixed
Name: "gtk2hs2"; Description: "Gtk2Hs libraries for GHC 6.6"; Check: UseWithGhcVersion('6.6'); Types: full compact custom; Flags: fixed
Name: "docs";    Description: "API reference documentation"; Types: full
Name: "demos";   Description: "Source files for the Gtk2Hs demo programs"; Types: full

[Files]
Source: "gtk+-2.10.9\*";                        DestDir: "{app}";       Components: gtk;     Flags: ignoreversion recursesubdirs createallsubdirs;
Source: "gtk2hs-0.9.10.6-ghc-6.4.2-gtk-2.10\*"; DestDir: "{app}";       Components: gtk2hs1; Flags: ignoreversion recursesubdirs createallsubdirs; AfterInstall: AfterPkgInstall;
Source: "gtk2hs-0.9.10.6-ghc-6.6-gtk-2.10\*";   DestDir: "{app}";       Components: gtk2hs2; Flags: ignoreversion recursesubdirs createallsubdirs; AfterInstall: AfterPkgInstall;
Source: "gtk2hs-0.9.10.6-demo\*";               DestDir: "{app}\demos"; Components: demos;   Flags: ignoreversion recursesubdirs createallsubdirs;
Source: "gtk2hs-0.9.10.6-docs\*";               DestDir: "{app}\docs";  Components: docs;    Flags: ignoreversion recursesubdirs createallsubdirs;
Source: "COPYING.txt";                          DestDir: "{app}";                            Flags: ignoreversion;
Source: "AUTHORS.txt";                          DestDir: "{app}";                            Flags: ignoreversion;

[Registry]
Root: HKCU; Subkey: "Environment"; ValueName: "Path"; ValueType: "string"; ValueData: "{app}\bin;{olddata}"; Check: NotOnPathAlready(); Flags: preservestringtype;

[Run]
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\glib.package.conf""";     StatusMsg: "Registering glib package...";     Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\cairo.package.conf""";    StatusMsg: "Registering cairo package...";    Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\svgcairo.package.conf"""; StatusMsg: "Registering svgcairo package..."; Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\gtk.package.conf""";      StatusMsg: "Registering gtk package...";      Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\glade.package.conf""";    StatusMsg: "Registering glade package...";    Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\soegtk.package.conf""";   StatusMsg: "Registering soegtk package...";   Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "update ""{app}\gtkglext.package.conf"""; StatusMsg: "Registering gtkglext package..."; Flags: runhidden

[UninstallRun]
Filename: "{code:ghcpkg}"; Parameters: "unregister gtkglext-0.9.10.6"; RunOnceId: "gtkglext"; Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister soegtk-0.9.10.6";   RunOnceId: "soegtk";   Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister glade-0.9.10.6";    RunOnceId: "glade";    Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister gtk-0.9.10.6";      RunOnceId: "gtk";      Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister svgcairo-0.9.10.6"; RunOnceId: "svgcairo"; Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister cairo-0.9.10.6";    RunOnceId: "cairo";    Flags: runhidden
Filename: "{code:ghcpkg}"; Parameters: "unregister glib-0.9.10.6";     RunOnceId: "glib";     Flags: runhidden

[Code]
var
  GhcInstallDir: String;
  GhcInstallVersion: String;

  CheckingPage: TOutputProgressWizardPage;
  ErrorReportPage: TOutputMsgMemoWizardPage;
  InstallationErrorCaption: String;
  InstallationErrorMessage: String;
  InstallationErrorDetail: String;

function ghcpkg(Param: String): String;
begin
  Result := AddBackslash(GhcInstallDir) + 'bin\ghc-pkg.exe';
end;

function NotOnPathAlready(): Boolean;
var
  BinDir, Path: String;
begin
  Log('Checking if Gtk2Hs\bin dir is already on the %PATH%');
  if RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', Path) then
  begin // Successfully read the value
    Log('HKCU\Environment\PATH = ' + Path);
    BinDir := ExpandConstant('{app}\bin');
    Log('Looking for Gtk2Hs\bin dir in %PATH%: ' + BinDir + ' in ' + Path);
    if Pos(LowerCase(BinDir), Lowercase(Path)) = 0 then
    begin
      Log('Did not find Gtk2Hs\bin dir in %PATH% so will add it');
      Result := True;
    end
    else
    begin
      Log('Found Gtk2Hs bin dir in %PATH% so will not add it again');
      Result := False;
    end
  end
  else // The key probably doesn't exist
  begin
    Log('Could not access HKCU\Environment\PATH so assume it is ok to add it');
    Result := True;
  end;
end;

{ exec a program and return its output }
function ExecOutput(const Filename, Params: String): String;
var
  TmpFile: String;
  ResultCode: Integer;
begin
  Result := '';
  TmpFile := GenerateUniqueName(ExpandConstant('{tmp}'), 'tmp');
  if Exec(ExpandConstant('{cmd}'), '/C ' + Filename + ' ' + Params + ' > ' + TmpFile,
     '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
    if ResultCode = 0 then
      if LoadStringFromFile(TmpFile, Result) then
      begin
        Result := Trim(Result);
        Log('ExecOutput: running ' + Filename + ' succeded with output: ' + Result);
      end
      else
        Log('ExecOutput: running ' + Filename + ' succeded, but the temp file was not created')
    else
      Log('ExecOutput: running ' + Filename + ' failed, code: ' + IntToStr(ResultCode))
  else
    { That's really odd, we should always be able to exec the command interpreter! }
    Log('ExecOutput: cannot exec command interpreter, code: ' + IntToStr(ResultCode) + ', message: ' + SysErrorMessage(ResultCode));
end;

function CheckGhcVersionIsOk(const Path: String; var Version: String):Boolean;
begin
  Version := ExecOutput(AddBackslash(Path) + 'bin\ghc.exe', '--numeric-version');
  GhcInstallVersion := Version;

  Result := (Version = '6.4.2')
         or (Version = '6.6');
end;

var
  DetectValidGhcInstallationCached: Boolean;
  DetectValidGhcInstallationResult: Boolean;

function DetectValidGhcInstallation(): Boolean;
var
  HaveSomeGHCInstalled: Boolean;
  GHCVersion: String;

begin
if DetectValidGhcInstallationCached then
  Result := DetectValidGhcInstallationResult
else
begin
  Result := False;

  { first check for ghc on the path, then look in the registry }

  begin
    GhcInstallDir := ExecOutput('ghc.exe', '--print-libdir');
    if GhcInstallDir <> '' then
    begin
      StringChange(GhcInstallDir, '/', '\');
      Log('DetectValidGhcInstallation: found ghc on %PATH% with libdir: ' + GhcInstallDir);
      HaveSomeGHCInstalled := True;
      Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
    end;
  end;

  if (Result = False) and RegKeyExists(HKEY_CURRENT_USER, 'Software\Haskell\GHC') then
  begin
    Log('DetectValidGhcInstallation: found HKCU\Software\Haskell\GHC');
    HaveSomeGHCInstalled := True;
    RegQueryStringValue(HKEY_CURRENT_USER, 'Software\Haskell\GHC\ghc-6.6', 'InstallDir', GhcInstallDir);
    Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
  end;

  if (Result = False) and RegKeyExists(HKEY_CURRENT_USER, 'Software\Haskell\GHC') then
  begin
    Log('DetectValidGhcInstallation: found HKCU\Software\Haskell\GHC');
    HaveSomeGHCInstalled := True;
    RegQueryStringValue(HKEY_CURRENT_USER, 'Software\Haskell\GHC\ghc-6.4.2', 'InstallDir', GhcInstallDir);
    Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
  end;

  if (Result = False) and RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\Haskell\GHC') then
  begin
    Log('DetectValidGhcInstallation: found HKLM\SOFTWARE\Haskell\GHC');
    HaveSomeGHCInstalled := True;
    RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Haskell\GHC\ghc-6.6', 'InstallDir', GhcInstallDir);
    Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
  end;

  if (Result = False) and RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\Haskell\GHC') then
  begin
    Log('DetectValidGhcInstallation: found HKLM\SOFTWARE\Haskell\GHC');
    HaveSomeGHCInstalled := True;
    RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Haskell\GHC\ghc-6.4.2', 'InstallDir', GhcInstallDir);
    Result := CheckGhcVersionIsOk(GhcInstallDir, GHCVersion);
  end;

  if Result then
    Log('DetectValidGhcInstallation: correct version of ghc found at: ' + GhcInstallDir)
  else if HaveSomeGHCInstalled and (GHCVersion <> '') then
  begin
    Log('DetectValidGhcInstallation: incorrect ghc version installed: ' + GHCVersion);
    InstallationErrorCaption := 'The version of GHC currently installed is not suitable.';
    InstallationErrorMessage := 'This version of Gtk2Hs requires GHC version 6.6 or 6.4.2.' #13#10 #13#10
                                'Setup found GHC version ' + GHCVersion + ' installed in the folder:' #13#10
                              + GhcInstallDir
  end
  else if HaveSomeGHCInstalled and (GhcInstallDir <> '') then
  begin
    Log('DetectValidGhcInstallation: some non-working version of ghc appears to be installed at: ' + GhcInstallDir);
    InstallationErrorCaption := 'GHC does not seem to be working.';
    InstallationErrorMessage := 'GHC does not appear to be installed correctly, try reinstalling GHC version 6.6 or 6.4.2' #13#10 #13#10
                                'Setup found what appears to be a non-working installation of GHC in the folder:' #13#10
                              + GhcInstallDir
  end
  else if HaveSomeGHCInstalled then
  begin
    Log('DetectValidGhcInstallation: corrupted ghc installation detected, probably messed up registry keys');
    InstallationErrorCaption := 'Setup did not find GHC on your computer.';
    InstallationErrorMessage := 'GHC does not appear to be installed (or the installation is corrupted), please install GHC version 6.6 or 6.4.2';
  end
  else
  begin
    Log('DetectValidGhcInstallation: no installation of ghc detected');
    InstallationErrorCaption := 'Setup did not find GHC on your computer.';
    InstallationErrorMessage := 'Gtk2Hs requires GHC to be installed first, please install GHC version 6.6 or 6.4.2' #13#10 #13#10
                                'If you installed GHC manually then make sure ghc.exe is on the path.';
  end;

  If not Result then
  begin
    Log(InstallationErrorCaption);
    Log(InstallationErrorMessage);
  end;

  DetectValidGhcInstallationResult := Result;
  DetectValidGhcInstallationCached := True;
end;
end;

function UseWithGhcVersion(const GhcVersion: String): Boolean;
begin
  DetectValidGhcInstallation();
  Result := (GhcVersion = GhcInstallVersion);
end;

var
  ChecksOk : Boolean;

function ShouldSkipPage(CurPageID: Integer): Boolean;
begin
  Log('ShouldSkipPage: CurPageID = ' + IntToStr(CurPageID));
  if CurPageID = ErrorReportPage.ID then
  begin
    if ChecksOk then
      Log('ChecksOk = True')
    else
      Log('ChecksOk = False');
    Result := ChecksOk
  end
  else
    Result := False;
end;

function NextButtonClick(CurPageID: Integer): Boolean;
var
  DllFiles, Offenders: array of String;
  Path, Offender: String;
  N, M, NumFiles: Integer;
begin
  Log('NextButtonClick: CurPageID = ' + IntToStr(CurPageID));
  if CurPageID = wpWelcome then
  begin
    if not DetectValidGhcInstallation() then
      ChecksOk := False
    else
    begin
      DllFiles := ['charset.dll', 'gspawn-win32-helper-console.exe',
        'gspawn-win32-helper.exe', 'iconv.dll', 'intl.dll',
        'jpeg62.dll', 'libatk-1.0-0.dll', 'libcairo-2.dll',
        'libgdk-win32-2.0-0.dll', 'libgdk_pixbuf-2.0-0.dll',
        'libglade-2.0-0.dll', 'libglib-2.0-0.dll',
        'libgmodule-2.0-0.dll', 'libgobject-2.0-0.dll',
        'libgthread-2.0-0.dll', 'libgtk-win32-2.0-0.dll',
        'libpango-1.0-0.dll', 'libpangocairo-1.0-0.dll',
        'libpangoft2-1.0-0.dll', 'libpangowin32-1.0-0.dll',
        'libpng13.dll', 'libxml2.dll', 'zlib1.dll'];
      NumFiles := GetArrayLength(DllFiles);
      SetArrayLength(Offenders, NumFiles);

      RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', Path);
      Path := GetSystemDir() + ';' + GetWinDir() + ';' + Path;

      Log('Checking DLL path: ' + Path);

      CheckingPage.SetText('Checking Gtk+ DLL search path...', '');
      CheckingPage.SetProgress(0, NumFiles);
      CheckingPage.Show;
      try
        M := 0;
        for N := 0 to NumFiles - 1 do
        begin
          Offender := FileSearch(DllFiles[N], Path);
          if Offender <> '' then
          begin
            Log('Found clashing dll/exe file at ' + Offender);
            Offenders[M] := Offender;
            M := M + 1;
          end
          CheckingPage.SetProgress(N+1, NumFiles);
          Sleep(10);
        end;
        if M > 0 then
        begin
          ChecksOk := False;
          InstallationErrorCaption := 'Setup found clashing DLL files on the search path.';
          InstallationErrorMessage := 'Please read the following description of the problem and how to fix it:';

          InstallationErrorDetail :=
              'For Haskell Gtk programs to work properly, the Gtk+ DLL files need to be in the DLL '
            + 'search path. However, the following DLLs files that Gtk+ uses are already on the '
            + 'system-wide search path:' #13#10

          for N := 0 to M - 1 do
            InstallationErrorDetail := InstallationErrorDetail + #13#10 + Offenders[N];

          InstallationErrorDetail := InstallationErrorDetail + #13#10 #13#10
              'The problem is that when you run a Haskell Gtk program these DLLs would get used '
            + 'rather than the proper versions of the Gtk+ DLLs installed by this setup program.'
            + 'This would almost certainly cause all your Haskell Gtk programs to not work.' #13#10 #13#10
              'You can fix this problem in a couple of ways:' #13#10 #13#10
              '1) You could uninstall or delete the program that installed the offending files '
            + '(eg it might be that you have an old version of Gtk+ installed).' #13#10 #13#10
              '2) You could modify the system-wide "Path" envrionment variable and remove the '
            + 'offending directory from it.'
        end
        else
          ChecksOk := True;
      finally
        CheckingPage.Hide;
      end;
    end;
  end
  Result := True;
end;

procedure PackageFileVarSubstitute(const PackageFile: String);
var
  PackageFileContent: String;
  PackageLibDir: String;
begin
  PackageLibDir := ExpandConstant('{app}');
  StringChange(PackageLibDir, '\', '/');

  LoadStringFromFile(PackageFile, PackageFileContent);

  StringChange(PackageFileContent, '${pkglibdir}', PackageLibDir);
  StringChange(PackageFileContent, '${GTK_BASEPATH}', PackageLibDir);

  SaveStringToFile(PackageFile, PackageFileContent, False);
  Log('Expanding variables in ' + PackageFile);
end;

procedure AfterPkgInstall;
begin
  if (ExtractFileExt(CurrentFileName) = '.conf') then
    PackageFileVarSubstitute(ExpandConstant(CurrentFileName));
end;

// Detect when the ready info page gets displayed
function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo,
                         MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo,
                         MemoTasksInfo: String): String;
begin
		Result := MemoDirInfo + NewLine
		        + NewLine
		        +	'Using GHC ' + GhcInstallVersion + ' installed at:' + NewLine
		        + Space + GhcInstallDir + NewLine
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
  BinDir, Path: String;
begin
  if (CurUninstallStep = usPostUninstall)
     and (RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'PATH', Path)) then
  begin
    BinDir := ExpandConstant('{app}\bin');
    if Pos(LowerCase(BinDir) + ';', Lowercase(Path)) <> 0 then
    begin
      StringChange(Path, BinDir + ';', '');
      RegWriteStringValue(HKEY_CURRENT_USER, 'Environment', 'PATH', Path);
    end;
  end;
end;

procedure ErrorReportPageNotify(Sender: TWizardPage);
begin
  WizardForm.NextButton.Enabled := False;
  ErrorReportPage.Description      := InstallationErrorCaption;
  ErrorReportPage.SubCaptionLabel.Caption := InstallationErrorMessage;
  if InstallationErrorDetail = '' then
    ErrorReportPage.SubCaptionLabel.Height := 50
  else
  begin
    ErrorReportPage.RichEditViewer.RTFText := InstallationErrorDetail
    ErrorReportPage.RichEditViewer.Show();
  end;
end;

procedure InitializeWizard();
begin
  CheckingPage := CreateOutputProgressPage('Checking requirements', 'Checking for GHC and Gtk+');
  ErrorReportPage := CreateOutputMsgMemoPage(wpWelcome, 'Setup cannot continue', 'GHC or Gtk+ problem', 'Your installation of GHC or Gtk+ is messed up!', 'Foo bar!');
  ErrorReportPage.OnActivate := @ErrorReportPageNotify;
  ErrorReportPage.RichEditViewer.Hide();
end;


stop shipping "MDF Alternate.solid.backup"

[_ISTool]
EnableISX=true

#define MyAppName "Solid"
#define MyAppVersion "0.0.0"
#define MyAppPublisher "Payap Language Software"
#define MyAppURL "http://solid.palaso.org"
#define MyAppExeName "Solid.exe"

[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{4AE9E6FF-434D-4465-BB07-ADC3673461AC}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
; AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DisableDirPage=yes
DisableReadyPage=yes
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
LicenseFile=license.rtf
OutputBaseFilename=SolidInstaller
Compression=lzma
SolidCompression=yes
WizardImageFile=compiler:WIZMODERNIMAGE-IS.BMP
CreateUninstallRegKey=true
;JMC: If we do switch over to installing just for the current user, then do the following.
; PrivilegesRequired=lowest

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Dirs]
Name: {app}\mappings
Name: {app}\exporters
Name: {app}\templates
Name: {userdocs}\Solid Examples

[Files]
Source: ..\installer\isxdl.dll; Flags: dontcopy
Source: ..\output\release\Solid.exe; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\KeymanLink.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Keyman7Interop.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\GlacialList.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Palaso.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\PalasoUIWindowsForms.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Palaso.DictionaryServices.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Palaso.Lift.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\L10NSharp.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Enchant.Net.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\mappings\MappingXmlToHtml.xsl; DestDir: {app}\mappings
Source: ..\mappings\LIFT.mappingSystem; DestDir: {app}\mappings
Source: ..\mappings\FLEX.mappingSystem; DestDir: {app}\mappings
Source: ..\templates\MDF.solid; DestDir: {app}\templates
Source: ..\templates\MDF.txt; DestDir: {app}\templates
Source: ..\templates\MDF Flat.solid; DestDir: {app}\templates
Source: ..\templates\MDF Loose.solid; DestDir: {app}\templates
Source: ..\templates\No Assumptions.solid; DestDir: {app}\templates
Source: ..\templates\PLB SFM.solid; DestDir: {app}\templates
Source: ..\templates\PLB SFM.txt; DestDir: {app}\templates
Source: ..\Solid Examples\BambaraSolidDemo.db; DestDir: {userdocs}\Solid Examples
Source: ..\Solid Examples\BambaraTutorial2.db; DestDir: {userdocs}\Solid Examples
Source: ..\doc\Solid Manual\Solid Documentation.pdf; DestDir: {app}
Source: ..\license_MIT.txt; DestDir: {app}

[Messages]
WinVersionTooLowError=Solid requires Windows 2000 or later.

[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"

[Code]
var
  dotnetRedistPath: string;
  downloadNeeded: boolean;
  dotNetNeeded: boolean;
  memoDependenciesNeeded: string;

procedure isxdl_AddFile(URL, Filename: String);
external 'isxdl_AddFile@files:isxdl.dll stdcall';
function isxdl_DownloadFiles(hWnd: Integer): Integer;
external 'isxdl_DownloadFiles@files:isxdl.dll stdcall';
function isxdl_SetOption(Option, Value: String): Integer;
external 'isxdl_SetOption@files:isxdl.dll stdcall';

const
  dotnetRedistURL = 'http://palaso.org/install/dotnet2/dotnetfx.exe';
  // local system for testing...
  //dotnetRedistURL = 'http://localhost/install/dotnet2/dotnetfx.exe';

function InitializeSetup(): Boolean;

begin
  Result := true;
  dotNetNeeded := false;

  // Check for required netfx installation
  if (not RegKeyExists(HKLM, 'Software\Microsoft\.NETFramework\policy\v2.0')) then begin
    dotNetNeeded := true;
    if (not IsAdminLoggedOn()) then begin
      MsgBox('Solid needs the Microsoft .NET Framework to be installed by an Administrator', mbInformation, MB_OK);
      Result := false;
    end else begin
      memoDependenciesNeeded := memoDependenciesNeeded + '      .NET Framework' #13;
      dotnetRedistPath := ExpandConstant('{src}\dotnetfx.exe');
      if not FileExists(dotnetRedistPath) then begin
        dotnetRedistPath := ExpandConstant('{tmp}\dotnetfx.exe');
        if not FileExists(dotnetRedistPath) then begin
          isxdl_AddFile(dotnetRedistURL, dotnetRedistPath);
          downloadNeeded := true;
        end;
      end;
      SetIniString('install', 'dotnetRedist', dotnetRedistPath, ExpandConstant('{tmp}\dep.ini'));
    end;
  end;

  // For testing
  //dotNetNeeded := true;
  //downloadNeeded := true;
  //SetIniString('install', 'dotnetRedist', dotnetRedistPath, ExpandConstant('{tmp}\dep.ini'));

  // I wanted to do the following since it isn't really just "Next", but this errors because WizardForm is not yet created. -JMC July 2014
  // WizardForm.NextButton.Caption := 'Install';

end;

function NextButtonClick(CurPage: Integer): Boolean;
var
  hWnd: Integer;
  ResultCode: Integer;

begin
  Result := true;

  if CurPage = wpWelcome then begin

    hWnd := StrToInt(ExpandConstant('{wizardhwnd}'));

    // don't try to init isxdl if it's not needed because it will error on < ie 3
    if downloadNeeded then begin

      isxdl_SetOption('label', 'Downloading Microsoft .NET Framework');
      isxdl_SetOption('description', 'Solid needs to install the Microsoft .NET Framework. Please wait while Setup is downloading extra files to your computer. This may take several minutes.');
      if isxdl_DownloadFiles(hWnd) = 0 then Result := false;
    end;
    if (Result = true) and (dotNetNeeded = true) then begin
      if Exec(ExpandConstant(dotnetRedistPath), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
         // handle success if necessary; ResultCode contains the exit code
         if not (ResultCode = 0) then begin
           Result := false;
         end;
      end else begin
         // handle failure if necessary; ResultCode contains the error code
         Result := false;
      end;
    end;
  end;
end;

function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
var
  s: string;

begin
  if memoDependenciesNeeded <> '' then s := s + 'Dependencies to install:' + NewLine + memoDependenciesNeeded + NewLine;
  s := s + MemoDirInfo + NewLine + NewLine;

  Result := s
end;

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, "&", "&&")}}"; Flags: nowait postinstall skipifsilent

[InnoIDE_Settings]
LogFileOverwrite=false

[_ISTool]
EnableISX=true

#define MyAppName "Solid"
#define MyAppVersion "0.0.0"
#define MyAppPublisher "Payap Language Software"
#define MyAppURL "http://solid.palaso.org"
#define MyAppExeName "Solid.exe"

[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{4AE9E6FF-434D-4465-BB07-ADC3673461AC}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
; AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DisableDirPage=yes
DisableReadyPage=yes
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
LicenseFile=license.rtf
OutputBaseFilename=SolidInstaller
Compression=lzma
SolidCompression=yes
WizardImageFile=compiler:WIZMODERNIMAGE-IS.BMP
CreateUninstallRegKey=true
;JMC: If we do switch over to installing just for the current user, then do the following.
; PrivilegesRequired=lowest

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Dirs]
Name: {app}\mappings
Name: {app}\exporters
Name: {app}\templates
Name: {userdocs}\Solid Examples

[Files]
Source: ..\installer\isxdl.dll; Flags: dontcopy
Source: ..\output\release\Solid.exe; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\KeymanLink.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Keyman7Interop.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\GlacialList.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Palaso.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\PalasoUIWindowsForms.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Palaso.DictionaryServices.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Palaso.Lift.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\L10NSharp.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\output\release\Enchant.Net.dll; DestDir: {app}; Flags: replacesameversion
Source: ..\mappings\MappingXmlToHtml.xsl; DestDir: {app}\mappings
Source: ..\mappings\LIFT.mappingSystem; DestDir: {app}\mappings
Source: ..\mappings\FLEX.mappingSystem; DestDir: {app}\mappings
Source: ..\templates\MDF.solid; DestDir: {app}\templates
Source: ..\templates\MDF.txt; DestDir: {app}\templates
Source: ..\templates\MDF Flat.solid; DestDir: {app}\templates
Source: ..\templates\MDF Alternate.solid.backup; DestDir: {app}\templates
Source: ..\templates\MDF Loose.solid; DestDir: {app}\templates
Source: ..\templates\No Assumptions.solid; DestDir: {app}\templates
Source: ..\templates\PLB SFM.solid; DestDir: {app}\templates
Source: ..\templates\PLB SFM.txt; DestDir: {app}\templates
Source: ..\Solid Examples\BambaraSolidDemo.db; DestDir: {userdocs}\Solid Examples
Source: ..\Solid Examples\BambaraTutorial2.db; DestDir: {userdocs}\Solid Examples
Source: ..\doc\Solid Manual\Solid Documentation.pdf; DestDir: {app}
Source: ..\license_MIT.txt; DestDir: {app}

[Messages]
WinVersionTooLowError=Solid requires Windows 2000 or later.

[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"

[Code]
var
  dotnetRedistPath: string;
  downloadNeeded: boolean;
  dotNetNeeded: boolean;
  memoDependenciesNeeded: string;

procedure isxdl_AddFile(URL, Filename: String);
external 'isxdl_AddFile@files:isxdl.dll stdcall';
function isxdl_DownloadFiles(hWnd: Integer): Integer;
external 'isxdl_DownloadFiles@files:isxdl.dll stdcall';
function isxdl_SetOption(Option, Value: String): Integer;
external 'isxdl_SetOption@files:isxdl.dll stdcall';

const
  dotnetRedistURL = 'http://palaso.org/install/dotnet2/dotnetfx.exe';
  // local system for testing...
  //dotnetRedistURL = 'http://localhost/install/dotnet2/dotnetfx.exe';

function InitializeSetup(): Boolean;

begin
  Result := true;
  dotNetNeeded := false;

  // Check for required netfx installation
  if (not RegKeyExists(HKLM, 'Software\Microsoft\.NETFramework\policy\v2.0')) then begin
    dotNetNeeded := true;
    if (not IsAdminLoggedOn()) then begin
      MsgBox('Solid needs the Microsoft .NET Framework to be installed by an Administrator', mbInformation, MB_OK);
      Result := false;
    end else begin
      memoDependenciesNeeded := memoDependenciesNeeded + '      .NET Framework' #13;
      dotnetRedistPath := ExpandConstant('{src}\dotnetfx.exe');
      if not FileExists(dotnetRedistPath) then begin
        dotnetRedistPath := ExpandConstant('{tmp}\dotnetfx.exe');
        if not FileExists(dotnetRedistPath) then begin
          isxdl_AddFile(dotnetRedistURL, dotnetRedistPath);
          downloadNeeded := true;
        end;
      end;
      SetIniString('install', 'dotnetRedist', dotnetRedistPath, ExpandConstant('{tmp}\dep.ini'));
    end;
  end;

  // For testing
  //dotNetNeeded := true;
  //downloadNeeded := true;
  //SetIniString('install', 'dotnetRedist', dotnetRedistPath, ExpandConstant('{tmp}\dep.ini'));

  // I wanted to do the following since it isn't really just "Next", but this errors because WizardForm is not yet created. -JMC July 2014
  // WizardForm.NextButton.Caption := 'Install';

end;

function NextButtonClick(CurPage: Integer): Boolean;
var
  hWnd: Integer;
  ResultCode: Integer;

begin
  Result := true;

  if CurPage = wpWelcome then begin

    hWnd := StrToInt(ExpandConstant('{wizardhwnd}'));

    // don't try to init isxdl if it's not needed because it will error on < ie 3
    if downloadNeeded then begin

      isxdl_SetOption('label', 'Downloading Microsoft .NET Framework');
      isxdl_SetOption('description', 'Solid needs to install the Microsoft .NET Framework. Please wait while Setup is downloading extra files to your computer. This may take several minutes.');
      if isxdl_DownloadFiles(hWnd) = 0 then Result := false;
    end;
    if (Result = true) and (dotNetNeeded = true) then begin
      if Exec(ExpandConstant(dotnetRedistPath), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
         // handle success if necessary; ResultCode contains the exit code
         if not (ResultCode = 0) then begin
           Result := false;
         end;
      end else begin
         // handle failure if necessary; ResultCode contains the error code
         Result := false;
      end;
    end;
  end;
end;

function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
var
  s: string;

begin
  if memoDependenciesNeeded <> '' then s := s + 'Dependencies to install:' + NewLine + memoDependenciesNeeded + NewLine;
  s := s + MemoDirInfo + NewLine + NewLine;

  Result := s
end;

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, "&", "&&")}}"; Flags: nowait postinstall skipifsilent

[InnoIDE_Settings]
LogFileOverwrite=false


Install PATH Registry Key now System wide. Fix https://github.com/unknown-horizons/unknown-horizons/issues/2679

;
;          _\|/_
;          (o o)
; +-----oOO-{_}-OOo------------------------------------------------------+
; |                                                                      |
; |  Unknown-Horizons - Inno Setup Script File                           |
; |  -------------------------------------------------------             |
; |                                                                      |
; |  Author:   Jens A. Koch <jakoch@web.de>                              |
; |  Author:   Thomas Kowaliczek-Schmer <thomas.kowaliczek@posteo.de>    |
; |  License:  MIT                                                       |
; |                                                                      |
; |  For the full copyright and license information, please view         |
; |  the LICENSE file that was distributed with this source code.        |
; |                                                                      |
; |  Note for developers                                                 |
; |  -------------------                                                 |
; |  A good resource for developing and understanding                    |
; |  Inno Setup Script files is the official "Inno Setup Help".          |
; |  Website:  http://jrsoftware.org/ishelp/index.php                    |
; |                                                                      |
; +---------------------------------------------------------------------<3

; version is set here, when the version isn't passed to the compiler on invocation
#ifndef APP_VERSION
#define APP_VERSION          "2017.2"
#endif

#define APP_NAME             "Unknown-Horizons"
#define APP_COMPILER         "VC14"
#define APP_PUBLISHER        "Unknown-Horizons Team"
#define APP_URL              "http://www.unknown-horizons.org/"
#define APP_SUPPORT_URL      "https://github.com/unknown-horizons/unknown-horizons/issues"
#define COPYRIGHT_YEAR        GetDateTimeString('yyyy', '', '');

[Setup]
AppId={{797B9070-BD1B-4DD9-8434-3A02697F9984}

; set application meta-data
AppName={#APP_NAME}
AppVerName={#APP_NAME} {#APP_VERSION}
AppVersion={#APP_VERSION}
AppCopyright=© {#APP_PUBLISHER}
AppPublisher={#APP_PUBLISHER}
AppPublisherURL={#APP_URL}
AppSupportURL={#APP_SUPPORT_URL}
AppUpdatesURL={#APP_URL}
UninstallDisplayIcon={app}\unknown-horizons\content\gfx\uh.ico
UninstallDisplayName={#APP_NAME}

; default installation folder is "C:\Unknown-Horizons". users might change this via dialog.
DefaultDirName=C:\Unknown-Horizons
DefaultGroupName={#APP_NAME}
DirExistsWarning=No
EnableDirDoesntExistWarning=Yes
AllowNoIcons=yes

; compression
Compression=lzma2/ultra
LZMAUseSeparateProcess=yes
LZMANumBlockThreads=2
InternalCompressLevel=max
SolidCompression=true

; style
WizardImageFile=images\WizardImage.bmp
WizardSmallImageFile=images\WizardSmallImage.bmp
SetupIconFile=images\uh.ico

; this creates the installer executable in the build folder
; for example: "_build\Unknown-Horizons-2017.2-Setup-VC14-x86.exe"
OutputBaseFilename={#APP_NAME}-{#APP_VERSION}-Setup-{#APP_COMPILER}-x86
OutputDir=..\_build

; disable wizard pages: Welcome (by default), Ready
DisableReadyPage=yes

; enable wizard pages: Select Destination Location
DisableDirPage=no

; Tell Windows to reload the environment, because Registry change (env PATH change)
ChangesEnvironment=yes

; Logging
SetupLogging=yes

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "german";  MessagesFile: "compiler:Languages\German.isl"

; Define the types of installations
; The user gets a drop-down list to select one of these types.
; Selecting a type will selected a set of components.
[Types]
Name: "full";          Description: "Full installation (game and engine)"
Name: "custom";        Description: "Custom installation"; Flags: iscustom

; Define components to install
[Components]
Name: "unknown_horizons"; Description: "[unknown-horizons] Unknown-Horizons";              Types: full
Name: vcredist2015;       Description: "[dep libs] VCRedist2015";                          Types: full

[Files]
Source: "..\repackage\unknown-horizons\*";      DestDir: "{app}\unknown-horizons";      Flags: recursesubdirs ignoreversion; Components: unknown_horizons
Source: "..\repackage\Python27\*";              DestDir: "{app}\python";                Flags: recursesubdirs ignoreversion; Components: unknown_horizons
Source: "..\repackage\vc_redist.x86.exe";       DestDir: "{tmp}";                       Flags: deleteafterinstall;           Components: unknown_horizons

[Tasks]
Name: add_startmenu;       Description: Create Startmenu entries;                         Components: unknown_horizons
Name: add_quicklaunchicon; Description: Create a &Quick Launch icon for Unknown Horizons; GroupDescription: Additional Icons:; Components: unknown_horizons
Name: add_desktopicon;     Description: Create a &Desktop icon for Unknown Horizons;      GroupDescription: Additional Icons:; Components: unknown_horizons

[Icons]
; define a group for the startmenu
Name: {group}\Start Unknown-Horizons; Filename: {app}\unknown-horizons\run_uh.bat; IconFilename: "{app}\unknown-horizons\content\gfx\uh.ico"; Tasks: add_startmenu
Name: {group}\Start Unknown-Horizons Debug; Filename: {app}\unknown-horizons\run_uh.bat; Parameters: "--debug"; IconFilename: "{app}\unknown-horizons\content\gfx\uh.ico"; Tasks: add_startmenu
Name: {group}\Uninstall Unknown-Horizons; Filename: "{uninstallexe}"; Tasks: add_startmenu
; desktop icon
Name: {userdesktop}\Unknown-Horizons; Filename: {app}\unknown-horizons\run_uh.bat; IconFilename: "{app}\unknown-horizons\content\gfx\uh.ico"; Tasks: add_desktopicon
; quick launch icon
Name: {userappdata}\Microsoft\Internet Explorer\Quick Launch\Unknown Horizons; Filename: {app}\run_uh.bat; IconFilename: "{app}\unknown-horizons\content\gfx\uh.ico"; Tasks: add_quicklaunchicon

; Define items to run automatically on installation...
[Run]
; add the Parameters, WorkingDir and StatusMsg as you wish, just keep here
; the conditional installation Check
Filename: "{tmp}\vc_redist.x86.exe"; Parameters: "/q /norestart"; Check: VCRedistNeedsInstall; StatusMsg: "Installing VC++ redistributables..."

[UninstallDelete]
;Type: filesandordirs; Name: "{userdocs}\My Games\unknown-horizons"
Type: filesandordirs; Name: "{app}\python"
Type: filesandordirs; Name: "{app}\unknown-horizons"

[Registry]
; A registry change needs the following directive: [SETUP] ChangesEnvironment=yes
; add path to fife (because libpng16-16.dll and other dependencies needs to be found)
Root: HKLM; EnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'; ValueType:string; ValueName:"PATH"; ValueData:"{olddata};{app}\python\Lib\site-packages\fife"; Flags: preservestringtype; Check: NeedsAddPathLocalMachine(ExpandConstant('{app}\python\Lib\site-packages\fife')); Components: unknown_horizons

[Code]
// modification and path lookup helper for env PATH
#include "includes\envpath.iss"

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
  // Removing a path from the PATH variable works in 3 steps:
  // 1. get the old env var PATH
  if (CurUninstallStep =  usUninstall) then
  begin
    SaveOldPathLocalMachine();
  end;
  // 2. remove paths from the env var PATH
  if (CurUninstallStep = usPostUninstall) then
  begin
    RemovePathLocalMachine(ExpandConstant('{app}') + '\python\Lib\site-packages\fife');
    // 3. refresh environment, so that the modified PATH var is activated
    RefreshEnvironment();
  end;
end;

#IFDEF UNICODE
  #DEFINE AW "W"
#ELSE
  #DEFINE AW "A"
#ENDIF
type
  INSTALLSTATE = Longint;
const
  INSTALLSTATE_INVALIDARG = -2;  { An invalid parameter was passed to the function. }
  INSTALLSTATE_UNKNOWN = -1;     { The product is neither advertised or installed. }
  INSTALLSTATE_ADVERTISED = 1;   { The product is advertised but not installed. }
  INSTALLSTATE_ABSENT = 2;       { The product is installed for a different user. }
  INSTALLSTATE_DEFAULT = 5;      { The product is installed for the current user. }

  // Visual C++ 2015 Redistributable 14.0.24215.1
  VC_2015_REDIST_X86 = '{e2803110-78b3-4664-a479-3611a381656a}';
//  VC_2015_REDIST_X64 = '{d992c12e-cab2-426f-bde3-fb8c53950b0d}';

function MsiQueryProductState(szProduct: string): INSTALLSTATE;
  external 'MsiQueryProductState{#AW}@msi.dll stdcall';

function VCVersionInstalled(const ProductID: string): Boolean;
begin
  Result := MsiQueryProductState(ProductID) = INSTALLSTATE_DEFAULT;
end;

function VCRedistNeedsInstall: Boolean;
begin
  Result := not (VCVersionInstalled(VC_2015_REDIST_X86));
end;

;
;          _\|/_
;          (o o)
; +-----oOO-{_}-OOo------------------------------------------------------+
; |                                                                      |
; |  Unknown-Horizons - Inno Setup Script File                           |
; |  -------------------------------------------------------             |
; |                                                                      |
; |  Author:   Jens A. Koch <jakoch@web.de>                              |
; |  Author:   Thomas Kowaliczek-Schmer <thomas.kowaliczek@posteo.de>    |
; |  License:  MIT                                                       |
; |                                                                      |
; |  For the full copyright and license information, please view         |
; |  the LICENSE file that was distributed with this source code.        |
; |                                                                      |
; |  Note for developers                                                 |
; |  -------------------                                                 |
; |  A good resource for developing and understanding                    |
; |  Inno Setup Script files is the official "Inno Setup Help".          |
; |  Website:  http://jrsoftware.org/ishelp/index.php                    |
; |                                                                      |
; +---------------------------------------------------------------------<3

; version is set here, when the version isn't passed to the compiler on invocation
#ifndef APP_VERSION
#define APP_VERSION          "2017.2"
#endif

#define APP_NAME             "Unknown-Horizons"
#define APP_COMPILER         "VC14"
#define APP_PUBLISHER        "Unknown-Horizons Team"
#define APP_URL              "http://www.unknown-horizons.org/"
#define APP_SUPPORT_URL      "https://github.com/unknown-horizons/unknown-horizons/issues"
#define COPYRIGHT_YEAR        GetDateTimeString('yyyy', '', '');

[Setup]
AppId={{797B9070-BD1B-4DD9-8434-3A02697F9984}

; set application meta-data
AppName={#APP_NAME}
AppVerName={#APP_NAME} {#APP_VERSION}
AppVersion={#APP_VERSION}
AppCopyright=© {#APP_PUBLISHER}
AppPublisher={#APP_PUBLISHER}
AppPublisherURL={#APP_URL}
AppSupportURL={#APP_SUPPORT_URL}
AppUpdatesURL={#APP_URL}
UninstallDisplayIcon={app}\unknown-horizons\content\gfx\uh.ico
UninstallDisplayName={#APP_NAME}

; default installation folder is "C:\Unknown-Horizons". users might change this via dialog.
DefaultDirName=C:\Unknown-Horizons
DefaultGroupName={#APP_NAME}
DirExistsWarning=No
EnableDirDoesntExistWarning=Yes
AllowNoIcons=yes

; compression
Compression=lzma2/ultra
LZMAUseSeparateProcess=yes
LZMANumBlockThreads=2
InternalCompressLevel=max
SolidCompression=true

; style
WizardImageFile=images\WizardImage.bmp
WizardSmallImageFile=images\WizardSmallImage.bmp
SetupIconFile=images\uh.ico

; this creates the installer executable in the build folder
; for example: "_build\Unknown-Horizons-2017.2-Setup-VC14-x86.exe"
OutputBaseFilename={#APP_NAME}-{#APP_VERSION}-Setup-{#APP_COMPILER}-x86
OutputDir=..\_build

; disable wizard pages: Welcome (by default), Ready
DisableReadyPage=yes

; enable wizard pages: Select Destination Location
DisableDirPage=no

; Tell Windows to reload the environment, because Registry change (env PATH change)
ChangesEnvironment=yes

; Logging
SetupLogging=yes

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "german";  MessagesFile: "compiler:Languages\German.isl"

; Define the types of installations
; The user gets a drop-down list to select one of these types.
; Selecting a type will selected a set of components.
[Types]
Name: "full";          Description: "Full installation (game and engine)"
Name: "custom";        Description: "Custom installation"; Flags: iscustom

; Define components to install
[Components]
Name: "unknown_horizons"; Description: "[unknown-horizons] Unknown-Horizons";              Types: full
Name: vcredist2015;       Description: "[dep libs] VCRedist2015";                          Types: full

[Files]
Source: "..\repackage\unknown-horizons\*";      DestDir: "{app}\unknown-horizons";      Flags: recursesubdirs ignoreversion; Components: unknown_horizons
Source: "..\repackage\Python27\*";              DestDir: "{app}\python";                Flags: recursesubdirs ignoreversion; Components: unknown_horizons
Source: "..\repackage\vc_redist.x86.exe";       DestDir: "{tmp}";                       Flags: deleteafterinstall;           Components: unknown_horizons

[Tasks]
Name: add_startmenu;       Description: Create Startmenu entries;                         Components: unknown_horizons
Name: add_quicklaunchicon; Description: Create a &Quick Launch icon for Unknown Horizons; GroupDescription: Additional Icons:; Components: unknown_horizons
Name: add_desktopicon;     Description: Create a &Desktop icon for Unknown Horizons;      GroupDescription: Additional Icons:; Components: unknown_horizons

[Icons]
; define a group for the startmenu
Name: {group}\Start Unknown-Horizons; Filename: {app}\unknown-horizons\run_uh.bat; IconFilename: "{app}\unknown-horizons\content\gfx\uh.ico"; Tasks: add_startmenu
Name: {group}\Start Unknown-Horizons Debug; Filename: {app}\unknown-horizons\run_uh.bat; Parameters: "--debug"; IconFilename: "{app}\unknown-horizons\content\gfx\uh.ico"; Tasks: add_startmenu
Name: {group}\Uninstall Unknown-Horizons; Filename: "{uninstallexe}"; Tasks: add_startmenu
; desktop icon
Name: {userdesktop}\Unknown-Horizons; Filename: {app}\unknown-horizons\run_uh.bat; IconFilename: "{app}\unknown-horizons\content\gfx\uh.ico"; Tasks: add_desktopicon
; quick launch icon
Name: {userappdata}\Microsoft\Internet Explorer\Quick Launch\Unknown Horizons; Filename: {app}\run_uh.bat; IconFilename: "{app}\unknown-horizons\content\gfx\uh.ico"; Tasks: add_quicklaunchicon

; Define items to run automatically on installation...
[Run]
; add the Parameters, WorkingDir and StatusMsg as you wish, just keep here
; the conditional installation Check
Filename: "{tmp}\vc_redist.x86.exe"; Parameters: "/q /norestart"; Check: VCRedistNeedsInstall; StatusMsg: "Installing VC++ redistributables..."

[UninstallDelete]
;Type: filesandordirs; Name: "{userdocs}\My Games\unknown-horizons"
Type: filesandordirs; Name: "{app}\python"
Type: filesandordirs; Name: "{app}\unknown-horizons"

[Registry]
; A registry change needs the following directive: [SETUP] ChangesEnvironment=yes
; add path to fife (because libpng16-16.dll and other dependencies needs to be found)
Root: HKCU; Subkey: "Environment"; ValueType:string; ValueName:"PATH"; ValueData:"{olddata};{app}\python\Lib\site-packages\fife"; Flags: preservestringtype; Check: NeedsAddPathLocalUser(ExpandConstant('{app}\python\Lib\site-packages\fife')); Components: unknown_horizons

[Code]
// modification and path lookup helper for env PATH
#include "includes\envpath.iss"

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
  // Removing a path from the PATH variable works in 3 steps:
  // 1. get the old env var PATH
  if (CurUninstallStep =  usUninstall) then
  begin
    SaveOldPathLocalUser();
  end;
  // 2. remove paths from the env var PATH
  if (CurUninstallStep = usPostUninstall) then
  begin
    RemovePathLocalUser(ExpandConstant('{app}') + '\python\Lib\site-packages\fife');
    // 3. refresh environment, so that the modified PATH var is activated
    RefreshEnvironment();
  end;
end;

#IFDEF UNICODE
  #DEFINE AW "W"
#ELSE
  #DEFINE AW "A"
#ENDIF
type
  INSTALLSTATE = Longint;
const
  INSTALLSTATE_INVALIDARG = -2;  { An invalid parameter was passed to the function. }
  INSTALLSTATE_UNKNOWN = -1;     { The product is neither advertised or installed. }
  INSTALLSTATE_ADVERTISED = 1;   { The product is advertised but not installed. }
  INSTALLSTATE_ABSENT = 2;       { The product is installed for a different user. }
  INSTALLSTATE_DEFAULT = 5;      { The product is installed for the current user. }

  // Visual C++ 2015 Redistributable 14.0.24215.1
  VC_2015_REDIST_X86 = '{e2803110-78b3-4664-a479-3611a381656a}';
//  VC_2015_REDIST_X64 = '{d992c12e-cab2-426f-bde3-fb8c53950b0d}';

function MsiQueryProductState(szProduct: string): INSTALLSTATE;
  external 'MsiQueryProductState{#AW}@msi.dll stdcall';

function VCVersionInstalled(const ProductID: string): Boolean;
begin
  Result := MsiQueryProductState(ProductID) = INSTALLSTATE_DEFAULT;
end;

function VCRedistNeedsInstall: Boolean;
begin
  Result := not (VCVersionInstalled(VC_2015_REDIST_X86));
end;


- irrelevant Result setting / AutoSize cleanup

[Code]
type
  TWndProc = function(handle: Integer; msg: UINT; wParam, lParam: LongInt): LongInt;
  TCWPSTRUCT = record
    lParam, wParam: LongInt;
    msg: LongInt;
    hWnd: Integer;
  end;
  TCWPSTRUCTProc = procedure(code: Integer; wParam: LongInt; lParam: TCWPSTRUCT);
  TNMHDR = record
    hwndFrom: LongInt;
    idFrom: LongInt;
    code: LongInt;
  end;
  TNMLISTVIEW = record
    hdr: TNMHDR;
    iItem, iSubItem: Integer;
    uNewState, uOldState, uChanged: UINT;
    ptAction: TPoint;
    lParam: LongInt;
  end;
  TLVData = record
    handle: Integer;
    parHandle: Integer; {Parent window hwnd}
    oldWndProc: Integer;
  end;
  TWinListViewNotifyEvent = procedure(data: TLVData; lvn: TNMLISTVIEW);
  TWinListViewChangingEvent = function(data: TLVData; lvn: TNMLISTVIEW): Boolean;
  TLVBehavior = record
    OnClick: TWinListViewNotifyEvent;
    OnChanging: TWinListViewChangingEvent;
  end;
  TWinListView = record
    data: TLVData;
    behavior: TLVBehavior;
  end;

const
  WM_NOTIFY = $4e;
  WS_EX_CLIENTEDGE = $0200;
  TNMLISTVIEWSIZE = $2c;

{listview-related helpers}
type
  TInitCommonCtls = record
    dwSize, dwICC: Integer;
  end;
  TWinRect = record
    left, top, right, bottom: LongInt;
  end;
  TLVColumn = record
    mask: LongInt;
    fmt: Integer;
    cx: Integer;
    value: WideString;
    valueMax, iSubItem : Integer;
    iImage, iOrder: Integer;
  end;
  TLVItem = record
    mask: LongInt;
    iItem, iSubItem: Integer;
    state, stateMask: LongInt;
    value: WideString;
    {value: Integer;}
    valueMax: Integer;
    iImage: Integer;
    lParam: LongInt;
    iIndent: Integer;   {_if (_WIN32_IE >= 0x0300)}
    iGroupId: Integer;  {_if (_WIN32_WINNT >= 0x0501)}
    cColumns, puColumns: LongInt;
    {piColFmt, iGroup: LongInt;}  {_if (_WIN32_WINNT >= 0x0600)}
  end;

const
  {list view}
  ICC_LISTVIEW_CLASSES = $1;
  WC_LISTVIEW = 'SysListView32';
  LVM_FIRST = $1000;
  NM_FIRST = $0;
  LVM_SETBKCOLOR = (LVM_FIRST + 1);
  LVM_GETITEMCOUNT = (LVM_FIRST + 4);
  LVM_DELETEALLITEMS = (LVM_FIRST + 9);
  LVM_GETNEXTITEM = (LVM_FIRST + 12);

  LVSCW_AUTOSIZE = -1;
  LVSCW_AUTOSIZE_USEHEADER = -2;
  LVM_GETCOLUMNWIDTH = (LVM_FIRST + 29);
  LVM_SETCOLUMNWIDTH = (LVM_FIRST + 30);

  LVM_GETHEADER = (LVM_FIRST + 31);

  LVM_SETTEXTBKCOLOR = (LVM_FIRST + 38);
  LVM_SETITEMSTATE = (LVM_FIRST + 43);
  LVM_SETEXTENDEDLISTVIEWSTYLE = (LVM_FIRST + 54);
  LVM_SETITEM = (LVM_FIRST + 76);
  LVM_INSERTITEM = (LVM_FIRST + 77);
  LVM_INSERTCOLUMN = (LVM_FIRST + 97);
  LVM_GETITEMTEXT = (LVM_FIRST + 115);
  LVM_SETITEMTEXT = (LVM_FIRST + 116);
  LVM_SETOUTLINECOLOR = (LVM_FIRST + 177);
  LPSTR_TEXTCALLBACK = -1;  {((LPWSTR)-1L)}
  {listview: styles}
  LVS_REPORT = $1;
  LVS_EDITLABELS = $0200;
  LVS_SINGLESEL = $4;
  LVS_SHOWSELALWAYS = $8;
  LVS_EX_FULLROWSELECT = $20;
  LVS_EX_GRIDLINES = $1;
  LVS_EX_FLATSB = $100;
  LVS_EX_HEADERDRAGDROP = $10;
  LVS_EX_AUTOSIZECOLUMNS = $10000000;
  LVS_EX_LABELTIP = $4000;
  LVS_EX_BORDERSELECT = $8000;
  LVS_EX_COLUMNOVERFLOW = $80000000;
  {listview: columns}
  LVCFMT_LEFT = $0;
  LVCFMT_RIGHT = $1;
  LVCF_FMT = $1;
  LVCF_WIDTH = $2;
  LVCF_TEXT = $4;
  LVCF_SUBITEM = $8;
  {listview: items}
  LVIF_TEXT = $1;
  LVIF_IMAGE = $2;
  LVIF_STATE = $8;
  LVIS_FOCUSED = $1;
  LVIS_SELECTED = $2;
  LVNI_SELECTED = $2;
  {listview: notifications}
  LVN_FIRST = (0-100);
  LVN_ITEMCHANGING = (LVN_FIRST-0);
  LVN_ITEMCHANGED = (LVN_FIRST-1);
  LVN_DELETEALLITEMS = (LVN_FIRST-4);
  LVN_BEGINLABELEDIT = (LVN_FIRST-75);
  NM_CLICK = (NM_FIRST-2);
  {header}
  HDM_FIRST = $1200;
  HDM_GETITEMCOUNT = (HDM_FIRST + 0);
  {windows}
  WS_CHILD = $40000000;
  HC_ACTION = $0;
  GWL_WNDPROC = (-4);

type
  TLVCache = Array of TWinListView;
var
  _LVControlCache: TLVCache;

function InitCommonCtls(var init: TInitCommonCtls): Boolean; external 'InitCommonControlsEx@comctl32.dll stdcall';
function GetClientRect(handle: Integer; var rect: TWinRect): Boolean; external 'GetClientRect@user32.dll stdcall';
function CreateWindow(exstyle: LongInt; clsName, wndName: String; style: LongInt; x, y, w, h: Integer; parHandle, menu, inst, params: Integer): Integer; external 'CreateWindowExW@user32.dll stdcall';
function MoveWindow(handle, x, y, w, h: Integer; bRepaint: Boolean): Boolean; external 'MoveWindow@user32.dll stdcall';
function ShowWindow(handle: Integer; cmdShow: Integer): Boolean; external 'ShowWindow@user32.dll stdcall';
function LVSendMessageColumn(handle: Integer; msg, wParam: Longint; var LParam: TLVColumn): Longint; external 'SendMessageW@user32.dll stdcall';
function LVSendMessageItem(handle: Integer; msg, wParam: Longint; var LParam: TLVItem): Longint; external 'SendMessageW@user32.dll stdcall';
function CallWindowProc(prevWndProc, handle: Integer; msg: UINT; wParam, LParam: LongInt): Longint; external 'CallWindowProcW@user32.dll stdcall';
function GetWindowLong(handle, index: LongInt): LongInt; external 'GetWindowLongW@user32.dll stdcall setuponly';
function SetWindowLong(handle, index, newvalue: LongInt): LongInt; external 'SetWindowLongW@user32.dll stdcall setuponly';
function WrapWndHookProc(callback: TCWPSTRUCTProc; paramsCount: Integer): LongInt; external 'wrapcallback@files:innocallback.dll';
function WrapWndProc(wndProc: TWndProc; numParams: Integer): LongWord; external 'wrapcallback@files:innocallback.dll stdcall';
procedure MapToNMLV(ptr, size: LongInt; var hdr: TNMLISTVIEW); external 'CopyPtrValue@files:listviewhelpers.dll stdcall loadwithalteredsearchpath';

function _ListView_FindCtrl(ctrlHandle: Integer): TWinListView;
var
  i: Integer;
begin
  for i :=0 to GetArrayLength(_LVControlCache)-1 do begin
    if _LVControlCache[i].data.handle = ctrlHandle then begin
      Result := _LVControlCache[i];
      Exit;
    end;
  end;
end;

function _ListView_FindCtrlByParent(parHandle: Integer): TWinListView;
var
  i: Integer;
begin
  for i :=0 to GetArrayLength(_LVControlCache)-1 do begin
    if _LVControlCache[i].data.parHandle = parHandle then begin
      Result := _LVControlCache[i];
      Exit;
    end;
  end;
end;

procedure _ListView_AddCtrl(lv: TWinListView);
var
  i: Integer;
begin
  i := GetArrayLength(_LVControlCache);
  SetArrayLength(_LVControlCache, GetArrayLength(_LVControlCache)+1);
  _LVControlCache[i] := lv;
end;

procedure _ListView_RemoveCtrl(ctrlHandle: Integer);
var
  i: Integer;
begin
  for i :=0 to GetArrayLength(_LVControlCache)-1 do begin
    if _LVControlCache[i].data.handle = ctrlHandle then begin
      _LVControlCache[i].data.handle := 0;
      _LVControlCache[i].data.parHandle := 0;
      Exit;
    end;
  end;
end;

function _ListView_WndProc(handle: Integer; msg: UINT; wParam, lParam: LongInt): LongInt;
var
  lvn: TNMLISTVIEW;
  lv: TWinListView;
  allowChange: Boolean;
begin
  lv := _ListView_FindCtrlByParent(handle);
  if lv.data.handle = 0 then begin
    Log(Format('ListView_WndProc: unable to match control by parent hWnd=%X', [handle]));
    Exit;
  end;
  if msg = WM_NOTIFY then begin
    MapToNMLV(lParam, TNMLISTVIEWSIZE, lvn);
    if lvn.hdr.code = LVN_ITEMCHANGING then begin
      allowChange := True;
      if lv.behavior.OnChanging <> nil then begin
        allowChange := lv.behavior.OnChanging(lv.data, lvn);
      end;
      Result := Integer(not allowChange);
      Exit;
    end
    else if (lvn.hdr.code = LVN_ITEMCHANGED) and (lv.behavior.OnClick <> nil) then
      lv.behavior.OnClick(lv.data, lvn)
    else if (lvn.hdr.code = LVN_BEGINLABELEDIT) then begin
      Result := 1;  {Readonly labels}
      Exit;
    end;
  end;
  Result := CallWindowProc(lv.data.oldWndProc, handle, msg, wParam, lParam);
end;

function WinRect(l, t, w, h: Integer): TWinRect;
begin
  Result.left := l;
  Result.top := t;
  Result.right := l + w;
  Result.bottom := t + h;
end;

procedure ListView_Init;
var
  init: TInitCommonCtls;
begin
  init.dwSize := 8;
  init.dwICC := ICC_LISTVIEW_CLASSES;
  InitCommonCtls(init);
end;

function ListView_BeginCreate(parHandle: Integer; rc: TWinRect): TWinListView;
var
  r: Integer;
begin
  Result.data.parHandle := parHandle;
  Result.data.handle := CreateWindow(0, WC_LISTVIEW, '',
    WS_CHILD or LVS_REPORT or LVS_EDITLABELS or LVS_SINGLESEL or LVS_SHOWSELALWAYS,
    rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, parHandle, 0, 0, 0);
  {Log(Format('ListView_Create: ctrl hWnd=%X', [Result.data.handle]));}
  SendMessage(Result.data.handle, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, WS_EX_CLIENTEDGE or LVS_EX_FULLROWSELECT or
              LVS_EX_FLATSB or LVS_EX_GRIDLINES or LVS_EX_AUTOSIZECOLUMNS or LVS_EX_LABELTIP or
              LVS_EX_BORDERSELECT or LVS_EX_COLUMNOVERFLOW);
  r := SendMessage(Result.data.handle, LVM_SETOUTLINECOLOR, 0, $0);
  Result.data.oldWndProc := SetWindowLong(parHandle, GWL_WNDPROC, WrapWndProc(@_ListView_WndProc, 4));
end;

procedure ListView_FinalizeCreate(lv: TWinListView);
begin
  _ListView_AddCtrl(lv);
end;

procedure ListView_Free(lv: TWinListView);
begin
  SetWindowLong(lv.data.handle, GWL_WNDPROC, lv.data.oldWndProc);
  lv.data.oldWndProc := 0;
  _ListView_RemoveCtrl(lv.data.handle);
end;

procedure ListView_AddColumn(lv: TWinListView; col, width: Integer; caption: String);
var
  colData: TLVColumn;
  r: Longint;
begin
  with colData do begin
    {format, width, text and subitem members of the structure are valid}
    mask := LVCF_FMT or LVCF_WIDTH or LVCF_TEXT or LVCF_SUBITEM;
    iSubItem := col;
    value := caption;
    cx := ScaleX(width);  {column width}
    fmt := LVCFMT_LEFT;
  end;
  r := LVSendMessageColumn(lv.data.handle, LVM_INSERTCOLUMN, col, colData);
  if r = -1 then
    Log('ListView_InsertColumn failed.');
end;

procedure ListView_AddItem(lv: TWinListView; item: Integer; caption: String);
var
  itemData: TLVItem;
  r: Longint;
begin
  with itemData do begin
    {text, image, state members of the structure are valid}
    mask := LVIF_TEXT or LVIF_IMAGE or LVIF_STATE;
    iSubItem := 0;
    {value := -1;} {Sends an LVN_GETDISPINFO message}
    value := caption;
    iItem := item;
    iImage := item;
    state := 0;
    stateMask := 0;
  end;
  r := LVSendMessageItem(lv.data.handle, LVM_INSERTITEM, 0, itemData);
  if r = -1 then
    Log('ListView_InsertItem failed.');
end;

procedure ListView_AddSubitem(lv: TWinListView; item, subitem: Integer; caption: String);
var
  itemData: TLVItem;
  r: Longint;
begin
  with itemData do begin
    {text, image, state members of the structure are valid}
    mask := LVIF_TEXT;
    iSubItem := subitem;
    value := caption;
  end;
  r := LVSendMessageItem(lv.data.handle, LVM_SETITEMTEXT, item, itemData);
  if r = -1 then
    Log('ListView_SetItemText failed.');
end;

function ListView_GetItemCount(lv: TWinListView): Integer;
begin
  Result := SendMessage(lv.data.handle, LVM_GETITEMCOUNT, 0, 0);
end;

function ListView_GetItemText(lv: TWinListView; item, subitem: Integer): String;
var
  itemData: TLVItem;
  r: Integer;
  buf: String;
begin
  buf := StringOfChar(#0, MAXPATH);
  with itemData do begin
    {text member(s) of the structure are valid}
    mask := LVIF_TEXT;
    iSubItem := subitem;
    value := PAnsiChar(buf);
    valueMax := MAXPATH;
  end;
  r := LVSendMessageItem(lv.data.handle, LVM_GETITEMTEXT, item, itemData);
  if r = -1 then
    Log('ListView_GetItem failed.')
  else
    Result := Copy(itemData.value, 0, r);
end;

function ListView_GetSelectedItem(lv: TWinListView): Integer;
begin
  Result := SendMessage(lv.data.handle, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
end;

procedure ListView_SelectItem(lv: TWinListView; item: Integer);
var
  itemData: TLVItem;
  r: Longint;
begin
  with itemData do begin
    state := LVIS_FOCUSED or LVIS_SELECTED;
    mask := $0F;
  end;
  r := LVSendMessageItem(lv.data.handle, LVM_SETITEMSTATE, item, itemData);
  if r = -1 then
    Log('ListView_SetItemText failed.');
end;

procedure ListView_SetBkColor(lv: TWinListView; color: Integer);
begin
  SendMessage(lv.data.handle, LVM_SETBKCOLOR, 0, color);
end;

procedure ListView_SetTextBkColor(lv: TWinListView; color: Integer);
begin
  SendMessage(lv.data.handle, LVM_SETTEXTBKCOLOR, 0, color);
end;

procedure ListView_DeleteAllItems(lv: TWinListView);
var
  r: Longint;
begin
  r := SendMessage(lv.data.handle, LVM_DELETEALLITEMS, 0, 0);
  if r = -1 then
    Log('ListView_DeleteAllItems failed.');
end;

function ListView_GetColumnCount(lv: TWinListView): Integer;
var
  hH: Integer;
begin
  hH := SendMessage(lv.data.handle, LVM_GETHEADER, 0, 0);
  Result := SendMessage(hH, HDM_GETITEMCOUNT, 0, 0);
end;

procedure ListView_AutoSize(lv: TWinListView);
var
  r, hW, cW, lvW, i, numCols: Integer;
begin
  i := 0; {only resize the first column}
  SendMessage(lv.data.handle, LVM_SETCOLUMNWIDTH, i, LVSCW_AUTOSIZE_USEHEADER);
  hW := hW + SendMessage(lv.data.handle, LVM_GETCOLUMNWIDTH, i, 0);
  SendMessage(lv.data.handle, LVM_SETCOLUMNWIDTH, i, LVSCW_AUTOSIZE);
  cW := cW + SendMessage(lv.data.handle, LVM_GETCOLUMNWIDTH, i, 0);
  if hW > cW then begin
    SendMessage(lv.data.handle, LVM_SETCOLUMNWIDTH, i, hW);
    lvW := lvW + hW;
  end
  else
    lvW := lvW + cW;
end;

[Code]
type
  TWndProc = function(handle: Integer; msg: UINT; wParam, lParam: LongInt): LongInt;
  TCWPSTRUCT = record
    lParam, wParam: LongInt;
    msg: LongInt;
    hWnd: Integer;
  end;
  TCWPSTRUCTProc = procedure(code: Integer; wParam: LongInt; lParam: TCWPSTRUCT);
  TNMHDR = record
    hwndFrom: LongInt;
    idFrom: LongInt;
    code: LongInt;
  end;
  TNMLISTVIEW = record
    hdr: TNMHDR;
    iItem, iSubItem: Integer;
    uNewState, uOldState, uChanged: UINT;
    ptAction: TPoint;
    lParam: LongInt;
  end;
  TLVData = record
    handle: Integer;
    parHandle: Integer; {Parent window hwnd}
    oldWndProc: Integer;
  end;
  TWinListViewNotifyEvent = procedure(data: TLVData; lvn: TNMLISTVIEW);
  TWinListViewChangingEvent = function(data: TLVData; lvn: TNMLISTVIEW): Boolean;
  TLVBehavior = record
    OnClick: TWinListViewNotifyEvent;
    OnChanging: TWinListViewChangingEvent;
  end;
  TWinListView = record
    data: TLVData;
    behavior: TLVBehavior;
  end;

const
  WM_NOTIFY = $4e;
  WS_EX_CLIENTEDGE = $0200;
  TNMLISTVIEWSIZE = $2c;

{listview-related helpers}
type
  TInitCommonCtls = record
    dwSize, dwICC: Integer;
  end;
  TWinRect = record
    left, top, right, bottom: LongInt;
  end;
  TLVColumn = record
    mask: LongInt;
    fmt: Integer;
    cx: Integer;
    value: WideString;
    valueMax, iSubItem : Integer;
    iImage, iOrder: Integer;
  end;
  TLVItem = record
    mask: LongInt;
    iItem, iSubItem: Integer;
    state, stateMask: LongInt;
    value: WideString;
    {value: Integer;}
    valueMax: Integer;
    iImage: Integer;
    lParam: LongInt;
    iIndent: Integer;   {_if (_WIN32_IE >= 0x0300)}
    iGroupId: Integer;  {_if (_WIN32_WINNT >= 0x0501)}
    cColumns, puColumns: LongInt;
    {piColFmt, iGroup: LongInt;}  {_if (_WIN32_WINNT >= 0x0600)}
  end;

const
  {list view}
  ICC_LISTVIEW_CLASSES = $1;
  WC_LISTVIEW = 'SysListView32';
  LVM_FIRST = $1000;
  NM_FIRST = $0;
  LVM_SETBKCOLOR = (LVM_FIRST + 1);
  LVM_GETITEMCOUNT = (LVM_FIRST + 4);
  LVM_DELETEALLITEMS = (LVM_FIRST + 9);
  LVM_GETNEXTITEM = (LVM_FIRST + 12);

  LVSCW_AUTOSIZE = -1;
  LVSCW_AUTOSIZE_USEHEADER = -2;
  LVM_GETCOLUMNWIDTH = (LVM_FIRST + 29);
  LVM_SETCOLUMNWIDTH = (LVM_FIRST + 30);

  LVM_GETHEADER = (LVM_FIRST + 31);

  LVM_SETTEXTBKCOLOR = (LVM_FIRST + 38);
  LVM_SETITEMSTATE = (LVM_FIRST + 43);
  LVM_SETEXTENDEDLISTVIEWSTYLE = (LVM_FIRST + 54);
  LVM_SETITEM = (LVM_FIRST + 76);
  LVM_INSERTITEM = (LVM_FIRST + 77);
  LVM_INSERTCOLUMN = (LVM_FIRST + 97);
  LVM_GETITEMTEXT = (LVM_FIRST + 115);
  LVM_SETITEMTEXT = (LVM_FIRST + 116);
  LVM_SETOUTLINECOLOR = (LVM_FIRST + 177);
  LPSTR_TEXTCALLBACK = -1;  {((LPWSTR)-1L)}
  {listview: styles}
  LVS_REPORT = $1;
  LVS_EDITLABELS = $0200;
  LVS_SINGLESEL = $4;
  LVS_SHOWSELALWAYS = $8;
  LVS_EX_FULLROWSELECT = $20;
  LVS_EX_GRIDLINES = $1;
  LVS_EX_FLATSB = $100;
  LVS_EX_HEADERDRAGDROP = $10;
  LVS_EX_AUTOSIZECOLUMNS = $10000000;
  LVS_EX_LABELTIP = $4000;
  LVS_EX_BORDERSELECT = $8000;
  LVS_EX_COLUMNOVERFLOW = $80000000;
  {listview: columns}
  LVCFMT_LEFT = $0;
  LVCFMT_RIGHT = $1;
  LVCF_FMT = $1;
  LVCF_WIDTH = $2;
  LVCF_TEXT = $4;
  LVCF_SUBITEM = $8;
  {listview: items}
  LVIF_TEXT = $1;
  LVIF_IMAGE = $2;
  LVIF_STATE = $8;
  LVIS_FOCUSED = $1;
  LVIS_SELECTED = $2;
  LVNI_SELECTED = $2;
  {listview: notifications}
  LVN_FIRST = (0-100);
  LVN_ITEMCHANGING = (LVN_FIRST-0);
  LVN_ITEMCHANGED = (LVN_FIRST-1);
  LVN_DELETEALLITEMS = (LVN_FIRST-4);
  LVN_BEGINLABELEDIT = (LVN_FIRST-75);
  NM_CLICK = (NM_FIRST-2);
  {header}
  HDM_FIRST = $1200;
  HDM_GETITEMCOUNT = (HDM_FIRST + 0);
  {windows}
  WS_CHILD = $40000000;
  HC_ACTION = $0;
  GWL_WNDPROC = (-4);

type
  TLVCache = Array of TWinListView;
var
  _LVControlCache: TLVCache;

function InitCommonCtls(var init: TInitCommonCtls): Boolean; external 'InitCommonControlsEx@comctl32.dll stdcall';
function GetClientRect(handle: Integer; var rect: TWinRect): Boolean; external 'GetClientRect@user32.dll stdcall';
function CreateWindow(exstyle: LongInt; clsName, wndName: String; style: LongInt; x, y, w, h: Integer; parHandle, menu, inst, params: Integer): Integer; external 'CreateWindowExW@user32.dll stdcall';
function MoveWindow(handle, x, y, w, h: Integer; bRepaint: Boolean): Boolean; external 'MoveWindow@user32.dll stdcall';
function ShowWindow(handle: Integer; cmdShow: Integer): Boolean; external 'ShowWindow@user32.dll stdcall';
function LVSendMessageColumn(handle: Integer; msg, wParam: Longint; var LParam: TLVColumn): Longint; external 'SendMessageW@user32.dll stdcall';
function LVSendMessageItem(handle: Integer; msg, wParam: Longint; var LParam: TLVItem): Longint; external 'SendMessageW@user32.dll stdcall';
function CallWindowProc(prevWndProc, handle: Integer; msg: UINT; wParam, LParam: LongInt): Longint; external 'CallWindowProcW@user32.dll stdcall';
function GetWindowLong(handle, index: LongInt): LongInt; external 'GetWindowLongW@user32.dll stdcall setuponly';
function SetWindowLong(handle, index, newvalue: LongInt): LongInt; external 'SetWindowLongW@user32.dll stdcall setuponly';
function WrapWndHookProc(callback: TCWPSTRUCTProc; paramsCount: Integer): LongInt; external 'wrapcallback@files:innocallback.dll';
function WrapWndProc(wndProc: TWndProc; numParams: Integer): LongWord; external 'wrapcallback@files:innocallback.dll stdcall';
procedure MapToNMLV(ptr, size: LongInt; var hdr: TNMLISTVIEW); external 'CopyPtrValue@files:listviewhelpers.dll stdcall loadwithalteredsearchpath';

function _ListView_FindCtrl(ctrlHandle: Integer): TWinListView;
var
  i: Integer;
begin
  for i :=0 to GetArrayLength(_LVControlCache)-1 do begin
    if _LVControlCache[i].data.handle = ctrlHandle then begin
      Result := _LVControlCache[i];
      Exit;
    end;
  end;
end;

function _ListView_FindCtrlByParent(parHandle: Integer): TWinListView;
var
  i: Integer;
begin
  for i :=0 to GetArrayLength(_LVControlCache)-1 do begin
    if _LVControlCache[i].data.parHandle = parHandle then begin
      Result := _LVControlCache[i];
      Exit;
    end;
  end;
end;

procedure _ListView_AddCtrl(lv: TWinListView);
var
  i: Integer;
begin
  i := GetArrayLength(_LVControlCache);
  SetArrayLength(_LVControlCache, GetArrayLength(_LVControlCache)+1);
  _LVControlCache[i] := lv;
end;

procedure _ListView_RemoveCtrl(ctrlHandle: Integer);
var
  i: Integer;
begin
  for i :=0 to GetArrayLength(_LVControlCache)-1 do begin
    if _LVControlCache[i].data.handle = ctrlHandle then begin
      _LVControlCache[i].data.handle := 0;
      _LVControlCache[i].data.parHandle := 0;
      Exit;
    end;
  end;
end;

function _ListView_WndProc(handle: Integer; msg: UINT; wParam, lParam: LongInt): LongInt;
var
  lvn: TNMLISTVIEW;
  lv: TWinListView;
  allowChange: Boolean;
begin
  lv := _ListView_FindCtrlByParent(handle);
  if lv.data.handle = 0 then begin
    Log(Format('ListView_WndProc: unable to match control by parent hWnd=%X', [handle]));
    Exit;
    Result := 0;
  end;
  if msg = WM_NOTIFY then begin
    MapToNMLV(lParam, TNMLISTVIEWSIZE, lvn);
    if lvn.hdr.code = LVN_ITEMCHANGING then begin
      allowChange := True;
      if lv.behavior.OnChanging <> nil then begin
        allowChange := lv.behavior.OnChanging(lv.data, lvn);
      end;
      Result := Integer(not allowChange);
      Exit;
    end
    else if (lvn.hdr.code = LVN_ITEMCHANGED) and (lv.behavior.OnClick <> nil) then
      lv.behavior.OnClick(lv.data, lvn)
    else if (lvn.hdr.code = LVN_BEGINLABELEDIT) then begin
      Result := 1;  {Readonly labels}
      Exit;
    end;
  end;
  Result := CallWindowProc(lv.data.oldWndProc, handle, msg, wParam, lParam);
end;

function WinRect(l, t, w, h: Integer): TWinRect;
begin
  Result.left := l;
  Result.top := t;
  Result.right := l + w;
  Result.bottom := t + h;
end;

procedure ListView_Init;
var
  init: TInitCommonCtls;
begin
  init.dwSize := 8;
  init.dwICC := ICC_LISTVIEW_CLASSES;
  InitCommonCtls(init);
end;

function ListView_BeginCreate(parHandle: Integer; rc: TWinRect): TWinListView;
var
  r: Integer;
begin
  Result.data.parHandle := parHandle;
  Result.data.handle := CreateWindow(0, WC_LISTVIEW, '',
    WS_CHILD or LVS_REPORT or LVS_EDITLABELS or LVS_SINGLESEL or LVS_SHOWSELALWAYS,
    rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, parHandle, 0, 0, 0);
  {Log(Format('ListView_Create: ctrl hWnd=%X', [Result.data.handle]));}
  SendMessage(Result.data.handle, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, WS_EX_CLIENTEDGE or LVS_EX_FULLROWSELECT or
              LVS_EX_FLATSB or LVS_EX_GRIDLINES or LVS_EX_AUTOSIZECOLUMNS or LVS_EX_LABELTIP or
              LVS_EX_BORDERSELECT or LVS_EX_COLUMNOVERFLOW);
  r := SendMessage(Result.data.handle, LVM_SETOUTLINECOLOR, 0, $0);
  Result.data.oldWndProc := SetWindowLong(parHandle, GWL_WNDPROC, WrapWndProc(@_ListView_WndProc, 4));
end;

procedure ListView_FinalizeCreate(lv: TWinListView);
begin
  _ListView_AddCtrl(lv);
end;

procedure ListView_Free(lv: TWinListView);
begin
  SetWindowLong(lv.data.handle, GWL_WNDPROC, lv.data.oldWndProc);
  lv.data.oldWndProc := 0;
  _ListView_RemoveCtrl(lv.data.handle);
end;

procedure ListView_AddColumn(lv: TWinListView; col, width: Integer; caption: String);
var
  colData: TLVColumn;
  r: Longint;
begin
  with colData do begin
    {format, width, text and subitem members of the structure are valid}
    mask := LVCF_FMT or LVCF_WIDTH or LVCF_TEXT or LVCF_SUBITEM;
    iSubItem := col;
    value := caption;
    cx := ScaleX(width);  {column width}
    fmt := LVCFMT_LEFT;
  end;
  r := LVSendMessageColumn(lv.data.handle, LVM_INSERTCOLUMN, col, colData);
  if r = -1 then
    Log('ListView_InsertColumn failed.');
end;

procedure ListView_AddItem(lv: TWinListView; item: Integer; caption: String);
var
  itemData: TLVItem;
  r: Longint;
begin
  with itemData do begin
    {text, image, state members of the structure are valid}
    mask := LVIF_TEXT or LVIF_IMAGE or LVIF_STATE;
    iSubItem := 0;
    {value := -1;} {Sends an LVN_GETDISPINFO message}
    value := caption;
    iItem := item;
    iImage := item;
    state := 0;
    stateMask := 0;
  end;
  r := LVSendMessageItem(lv.data.handle, LVM_INSERTITEM, 0, itemData);
  if r = -1 then
    Log('ListView_InsertItem failed.');
end;

procedure ListView_AddSubitem(lv: TWinListView; item, subitem: Integer; caption: String);
var
  itemData: TLVItem;
  r: Longint;
begin
  with itemData do begin
    {text, image, state members of the structure are valid}
    mask := LVIF_TEXT;
    iSubItem := subitem;
    value := caption;
  end;
  r := LVSendMessageItem(lv.data.handle, LVM_SETITEMTEXT, item, itemData);
  if r = -1 then
    Log('ListView_SetItemText failed.');
end;

function ListView_GetItemCount(lv: TWinListView): Integer;
begin
  Result := SendMessage(lv.data.handle, LVM_GETITEMCOUNT, 0, 0);
end;

function ListView_GetItemText(lv: TWinListView; item, subitem: Integer): String;
var
  itemData: TLVItem;
  r: Integer;
  buf: String;
begin
  buf := StringOfChar(#0, MAXPATH);
  with itemData do begin
    {text member(s) of the structure are valid}
    mask := LVIF_TEXT;
    iSubItem := subitem;
    value := PAnsiChar(buf);
    valueMax := MAXPATH;
  end;
  r := LVSendMessageItem(lv.data.handle, LVM_GETITEMTEXT, item, itemData);
  if r = -1 then
    Log('ListView_GetItem failed.')
  else
    Result := Copy(itemData.value, 0, r);
end;

function ListView_GetSelectedItem(lv: TWinListView): Integer;
begin
  Result := SendMessage(lv.data.handle, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
end;

procedure ListView_SelectItem(lv: TWinListView; item: Integer);
var
  itemData: TLVItem;
  r: Longint;
begin
  with itemData do begin
    state := LVIS_FOCUSED or LVIS_SELECTED;
    mask := $0F;
  end;
  r := LVSendMessageItem(lv.data.handle, LVM_SETITEMSTATE, item, itemData);
  if r = -1 then
    Log('ListView_SetItemText failed.');
end;

procedure ListView_SetBkColor(lv: TWinListView; color: Integer);
begin
  SendMessage(lv.data.handle, LVM_SETBKCOLOR, 0, color);
end;

procedure ListView_SetTextBkColor(lv: TWinListView; color: Integer);
begin
  SendMessage(lv.data.handle, LVM_SETTEXTBKCOLOR, 0, color);
end;

procedure ListView_DeleteAllItems(lv: TWinListView);
var
  r: Longint;
begin
  r := SendMessage(lv.data.handle, LVM_DELETEALLITEMS, 0, 0);
  if r = -1 then
    Log('ListView_DeleteAllItems failed.');
end;

function ListView_GetColumnCount(lv: TWinListView): Integer;
var
  hH: Integer;
begin
  hH := SendMessage(lv.data.handle, LVM_GETHEADER, 0, 0);
  Result := SendMessage(hH, HDM_GETITEMCOUNT, 0, 0);
end;

procedure ListView_AutoSize(lv: TWinListView);
var
  r, hW, cW, lvW, i, numCols: Integer;
begin
  {numCols := ListView_GetColumnCount(lv);
   for i := 0 to numCols-1 do begin}
  i := 0; {only resize the first column}
    SendMessage(lv.data.handle, LVM_SETCOLUMNWIDTH, i, LVSCW_AUTOSIZE_USEHEADER);
    hW := hW + SendMessage(lv.data.handle, LVM_GETCOLUMNWIDTH, i, 0);
    SendMessage(lv.data.handle, LVM_SETCOLUMNWIDTH, i, LVSCW_AUTOSIZE);
    cW := cW + SendMessage(lv.data.handle, LVM_GETCOLUMNWIDTH, i, 0);
  {end;}
  if hW > cW then begin
    SendMessage(lv.data.handle, LVM_SETCOLUMNWIDTH, i, hW);
    lvW := lvW + hW;
  end
  else
    lvW := lvW + cW;
end;