// CompressDialog.cpp

#include "StdAfx.h"

#include "../../../../C/CpuArch.h"

#include "../../../Common/IntToString.h"
#include "../../../Common/StringConvert.h"

#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileName.h"
#include "../../../Windows/System.h"

#include "../../Common/MethodProps.h"

#include "../FileManager/BrowseDialog.h"
#include "../FileManager/FormatUtils.h"
#include "../FileManager/HelpUtils.h"
#include "../FileManager/PropertyName.h"
#include "../FileManager/SplitUtils.h"

#include "../Explorer/MyMessages.h"

#include "../Common/ZipRegistry.h"

#include "CompressDialog.h"

#ifndef _UNICODE
extern bool g_IsNT;
#endif

#ifdef LANG
#include "../FileManager/LangUtils.h"
#endif

#include "CompressDialogRes.h"
#include "ExtractRes.h"

#ifdef LANG

// #define IDS_OPTIONS 2100

static const UInt32 kLangIDs[] =
{
  IDT_COMPRESS_ARCHIVE,
  IDT_COMPRESS_UPDATE_MODE,
  IDT_COMPRESS_FORMAT,
  IDT_COMPRESS_LEVEL,
  IDT_COMPRESS_METHOD,
  IDT_COMPRESS_DICTIONARY,
  IDT_COMPRESS_ORDER,
  IDT_COMPRESS_SOLID,
  IDT_COMPRESS_THREADS,
  IDT_COMPRESS_PARAMETERS,
  
  IDB_COMPRESS_OPTIONS, // IDS_OPTIONS

  IDG_COMPRESS_OPTIONS,
  IDX_COMPRESS_SFX,
  IDX_COMPRESS_SHARED,
  IDX_COMPRESS_DEL,

  IDT_COMPRESS_MEMORY,
  IDT_COMPRESS_MEMORY_DE,

  IDG_COMPRESS_ENCRYPTION,
  IDT_COMPRESS_ENCRYPTION_METHOD,
  IDX_COMPRESS_ENCRYPT_FILE_NAMES,

  IDT_PASSWORD_ENTER,
  IDT_PASSWORD_REENTER,
  IDX_PASSWORD_SHOW,

  IDT_SPLIT_TO_VOLUMES,
  IDT_COMPRESS_PATH_MODE,
};
#endif

using namespace NWindows;
using namespace NFile;
using namespace NName;
using namespace NDir;

static const unsigned kHistorySize = 20;

static const UInt32 kSolidLog_NoSolid = 0;
static const UInt32 kSolidLog_FullSolid = 64;

static const UInt32 kLzmaMaxDictSize = (UInt32)15 << 28;

static LPCSTR const kExeExt = ".exe";

static const UInt32 g_Levels[] =
{
  IDS_METHOD_STORE,
  IDS_METHOD_FASTEST,
  0,
  IDS_METHOD_FAST,
  0,
  IDS_METHOD_NORMAL,
  0,
  IDS_METHOD_MAXIMUM,
  0,
  IDS_METHOD_ULTRA
};

enum EMethodID
{
  kCopy,
  kLZMA,
  kLZMA2,
  kPPMd,
  kBZip2,
  kDeflate,
  kDeflate64,
  kPPMdZip,
  kSha256,
  kSha1,
  kCrc32,
  kCrc64,
  kGnu,
  kPosix
};

static LPCSTR const kMethodsNames[] =
{
    "Copy"
  , "LZMA"
  , "LZMA2"
  , "PPMd"
  , "BZip2"
  , "Deflate"
  , "Deflate64"
  , "PPMd"
  , "SHA256"
  , "SHA1"
  , "CRC32"
  , "CRC64"
  , "GNU"
  , "POSIX"
};

static const EMethodID g_7zMethods[] =
{
  kLZMA2,
  kLZMA,
  kPPMd,
  kBZip2
  , kDeflate
  , kDeflate64
  , kCopy
};

static const EMethodID g_7zSfxMethods[] =
{
  kCopy,
  kLZMA,
  kLZMA2,
  kPPMd
};

static const EMethodID g_ZipMethods[] =
{
  kDeflate,
  kDeflate64,
  kBZip2,
  kLZMA,
  kPPMdZip
};

static const EMethodID g_GZipMethods[] =
{
  kDeflate
};

static const EMethodID g_BZip2Methods[] =
{
  kBZip2
};

static const EMethodID g_XzMethods[] =
{
  kLZMA2
};

static const EMethodID g_SwfcMethods[] =
{
  kDeflate
  // kLZMA
};

static const EMethodID g_TarMethods[] =
{
  kGnu,
  kPosix
};

static const EMethodID g_HashMethods[] =
{
    kSha256
  , kSha1
  // , kCrc32
  // , kCrc64
};

static const UInt32 kFF_Filter      = 1 << 0;
static const UInt32 kFF_Solid       = 1 << 1;
static const UInt32 kFF_MultiThread = 1 << 2;
static const UInt32 kFF_Encrypt     = 1 << 3;
static const UInt32 kFF_EncryptFileNames  = 1 << 4;
static const UInt32 kFF_MemUse      = 1 << 5;
static const UInt32 kFF_SFX         = 1 << 6;

/*
static const UInt32 kFF_Time_Win  = 1 << 10;
static const UInt32 kFF_Time_Unix = 1 << 11;
static const UInt32 kFF_Time_DOS  = 1 << 12;
static const UInt32 kFF_Time_1ns  = 1 << 13;
*/

struct CFormatInfo
{
  LPCSTR Name;
  UInt32 LevelsMask;
  unsigned NumMethods;
  const EMethodID *MethodIDs;

  UInt32 Flags;

  bool Filter_() const { return (Flags & kFF_Filter) != 0; }
  bool Solid_() const { return (Flags & kFF_Solid) != 0; }
  bool MultiThread_() const { return (Flags & kFF_MultiThread) != 0; }
  bool Encrypt_() const { return (Flags & kFF_Encrypt) != 0; }
  bool EncryptFileNames_() const { return (Flags & kFF_EncryptFileNames) != 0; }
  bool MemUse_() const { return (Flags & kFF_MemUse) != 0; }
  bool SFX_() const { return (Flags & kFF_SFX) != 0; }
};

#define METHODS_PAIR(x) ARRAY_SIZE(x), x

static const CFormatInfo g_Formats[] =
{
  {
    "",
    // (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    ((UInt32)1 << 10) - 1,
    // (UInt32)(Int32)-1,
    0, NULL,
    kFF_MultiThread | kFF_MemUse
  },
  {
    "7z",
    (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_7zMethods),
    kFF_Filter | kFF_Solid | kFF_MultiThread | kFF_Encrypt |
    kFF_EncryptFileNames | kFF_MemUse | kFF_SFX
    // | kFF_Time_Win
  },
  {
    "Zip",
    (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_ZipMethods),
    kFF_MultiThread | kFF_Encrypt | kFF_MemUse
    // | kFF_Time_Win | kFF_Time_Unix | kFF_Time_DOS
  },
  {
    "GZip",
    (1 << 1) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_GZipMethods),
    kFF_MemUse
    // | kFF_Time_Unix
  },
  {
    "BZip2",
    (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_BZip2Methods),
    kFF_MultiThread | kFF_MemUse
  },
  {
    "xz",
    (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_XzMethods),
    kFF_Solid | kFF_MultiThread | kFF_MemUse
  },
  {
    "Swfc",
    (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_SwfcMethods),
    0
  },
  {
    "Tar",
    (1 << 0),
    METHODS_PAIR(g_TarMethods),
    0
    // kFF_Time_Unix | kFF_Time_Win // | kFF_Time_1ns
  },
  {
    "wim",
    (1 << 0),
    0, NULL,
    0
    // | kFF_Time_Win
  },
  {
    "Hash",
    (0 << 0),
    METHODS_PAIR(g_HashMethods),
    0
  }
};

static bool IsMethodSupportedBySfx(int methodID)
{
  for (unsigned i = 0; i < ARRAY_SIZE(g_7zSfxMethods); i++)
    if (methodID == g_7zSfxMethods[i])
      return true;
  return false;
}


static const
  // NCompressDialog::NUpdateMode::EEnum
  int
  k_UpdateMode_Vals[] =
{
  NCompressDialog::NUpdateMode::kAdd,
  NCompressDialog::NUpdateMode::kUpdate,
  NCompressDialog::NUpdateMode::kFresh,
  NCompressDialog::NUpdateMode::kSync
};
  
static const UInt32 k_UpdateMode_IDs[] =
{
  IDS_COMPRESS_UPDATE_MODE_ADD,
  IDS_COMPRESS_UPDATE_MODE_UPDATE,
  IDS_COMPRESS_UPDATE_MODE_FRESH,
  IDS_COMPRESS_UPDATE_MODE_SYNC
};

static const
  // NWildcard::ECensorPathMode
  int
  k_PathMode_Vals[] =
{
  NWildcard::k_RelatPath,
  NWildcard::k_FullPath,
  NWildcard::k_AbsPath,
};

static const UInt32 k_PathMode_IDs[] =
{
  IDS_PATH_MODE_RELAT,
  IDS_EXTRACT_PATHS_FULL,
  IDS_EXTRACT_PATHS_ABS
};

void AddComboItems(NControl::CComboBox &combo, const UInt32 *langIDs, unsigned numItems, const int *values, int curVal);

void CCompressDialog::SetMethods(const CObjectVector<CCodecInfoUser> &userCodecs)
{
  ExternalMethods.Clear();
  {
    FOR_VECTOR (i, userCodecs)
    {
      const CCodecInfoUser &c = userCodecs[i];
      if (!c.EncoderIsAssigned
          || !c.IsFilter_Assigned
          || c.IsFilter
          || c.NumStreams != 1)
        continue;
      unsigned k;
      for (k = 0; k < ARRAY_SIZE(g_7zMethods); k++)
        if (c.Name.IsEqualTo_Ascii_NoCase(kMethodsNames[g_7zMethods[k]]))
          break;
      if (k != ARRAY_SIZE(g_7zMethods))
        continue;
      ExternalMethods.Add(c.Name);
    }
  }
}
                                         

bool CCompressDialog::OnInit()
{
  #ifdef LANG
  LangSetWindowText(*this, IDD_COMPRESS);
  LangSetDlgItems(*this, kLangIDs, ARRAY_SIZE(kLangIDs));
  // LangSetDlgItemText(*this, IDB_COMPRESS_OPTIONS, IDS_OPTIONS); // IDG_COMPRESS_OPTIONS
  #endif

  {
    UInt64 size = (UInt64)(sizeof(size_t)) << 29;
    _ramSize_Defined = NSystem::GetRamSize(size);
    // size = (UInt64)3 << 62; // for debug only;
    _ramSize = size;
    const UInt64 kMinUseSize = (1 << 26);
    if (size < kMinUseSize)
      size = kMinUseSize;

    unsigned bits = sizeof(size_t) * 8;
    if (bits == 32)
    {
      const UInt32 limit2 = (UInt32)7 << 28;
      if (size > limit2)
        size = limit2;
    }

    _ramSize_Reduced = size;

    // 80% - is auto usage limit in handlers
    _ramUsage_Auto = Calc_From_Val_Percents(size, 80);
  }

  _password1Control.Attach(GetItem(IDE_COMPRESS_PASSWORD1));
  _password2Control.Attach(GetItem(IDE_COMPRESS_PASSWORD2));
  _password1Control.SetText(Info.Password);
  _password2Control.SetText(Info.Password);
  _encryptionMethod.Attach(GetItem(IDC_COMPRESS_ENCRYPTION_METHOD));
  _default_encryptionMethod_Index = -1;

  m_ArchivePath.Attach(GetItem(IDC_COMPRESS_ARCHIVE));
  m_Format.Attach(GetItem(IDC_COMPRESS_FORMAT));
  m_Level.Attach(GetItem(IDC_COMPRESS_LEVEL));
  m_Method.Attach(GetItem(IDC_COMPRESS_METHOD));
  m_Dictionary.Attach(GetItem(IDC_COMPRESS_DICTIONARY));
  m_Order.Attach(GetItem(IDC_COMPRESS_ORDER));
  m_Solid.Attach(GetItem(IDC_COMPRESS_SOLID));
  m_NumThreads.Attach(GetItem(IDC_COMPRESS_THREADS));
  m_MemUse.Attach(GetItem(IDC_COMPRESS_MEM_USE));
  
  m_UpdateMode.Attach(GetItem(IDC_COMPRESS_UPDATE_MODE));
  m_PathMode.Attach(GetItem(IDC_COMPRESS_PATH_MODE));

  m_Volume.Attach(GetItem(IDC_COMPRESS_VOLUME));
  m_Params.Attach(GetItem(IDE_COMPRESS_PARAMETERS));

  AddVolumeItems(m_Volume);

  m_RegistryInfo.Load();
  CheckButton(IDX_PASSWORD_SHOW, m_RegistryInfo.ShowPassword);
  CheckButton(IDX_COMPRESS_ENCRYPT_FILE_NAMES, m_RegistryInfo.EncryptHeaders);
  
  UpdatePasswordControl();

  {
    bool needSetMain = (Info.FormatIndex < 0);
    FOR_VECTOR(i, ArcIndices)
    {
      unsigned arcIndex = ArcIndices[i];
      const CArcInfoEx &ai = (*ArcFormats)[arcIndex];
      int index = (int)m_Format.AddString(ai.Name);
      m_Format.SetItemData(index, arcIndex);
      if (!needSetMain)
      {
        if (Info.FormatIndex == (int)arcIndex)
          m_Format.SetCurSel(index);
        continue;
      }
      if (i == 0 || ai.Name.IsEqualTo_NoCase(m_RegistryInfo.ArcType))
      {
        m_Format.SetCurSel(index);
        Info.FormatIndex = arcIndex;
      }
    }
  }

  CheckButton(IDX_COMPRESS_SFX, Info.SFXMode);

  {
    UString fileName;
    SetArcPathFields(Info.ArcPath, fileName, true);
    StartDirPrefix = DirPrefix;
    SetArchiveName(fileName);
  }
  
  for (unsigned i = 0; i < m_RegistryInfo.ArcPaths.Size() && i < kHistorySize; i++)
    m_ArchivePath.AddString(m_RegistryInfo.ArcPaths[i]);

  AddComboItems(m_UpdateMode, k_UpdateMode_IDs, ARRAY_SIZE(k_UpdateMode_IDs),
      k_UpdateMode_Vals, Info.UpdateMode);

  AddComboItems(m_PathMode, k_PathMode_IDs, ARRAY_SIZE(k_PathMode_IDs),
      k_PathMode_Vals, Info.PathMode);


  TCHAR s[32] = { TEXT('/'), TEXT(' '), 0 };
  ConvertUInt32ToString(NSystem::GetNumberOfProcessors(), s + 2);
  SetItemText(IDT_COMPRESS_HARDWARE_THREADS, s);

  CheckButton(IDX_COMPRESS_SHARED, Info.OpenShareForWrite);
  CheckButton(IDX_COMPRESS_DEL, Info.DeleteAfterCompressing);

  FormatChanged(false); // isChanged

  // OnButtonSFX();

  NormalizePosition();

  return CModalDialog::OnInit();
}

/*
namespace NCompressDialog
{
  bool CInfo::GetFullPathName(UString &result) const
  {
    #ifndef UNDER_CE
    // NDirectory::MySetCurrentDirectory(CurrentDirPrefix);
    #endif
    FString resultF;
    bool res = MyGetFullPathName(us2fs(ArchiveName), resultF);
    result = fs2us(resultF);
    return res;
  }
}
*/

void CCompressDialog::UpdatePasswordControl()
{
  bool showPassword = IsShowPasswordChecked();
  TCHAR c = showPassword ? (TCHAR)0: TEXT('*');
  _password1Control.SetPasswordChar(c);
  _password2Control.SetPasswordChar(c);
  UString password;
  _password1Control.GetText(password);
  _password1Control.SetText(password);
  _password2Control.GetText(password);
  _password2Control.SetText(password);

  ShowItem_Bool(IDT_PASSWORD_REENTER, !showPassword);
  _password2Control.Show_Bool(!showPassword);
}

bool CCompressDialog::OnButtonClicked(int buttonID, HWND buttonHWND)
{
  switch (buttonID)
  {
    case IDB_COMPRESS_SET_ARCHIVE:
    {
      OnButtonSetArchive();
      return true;
    }
    case IDX_COMPRESS_SFX:
    {
      SetMethod(GetMethodID());
      OnButtonSFX();
      SetMemoryUsage();
      return true;
    }
    case IDX_PASSWORD_SHOW:
    {
      UpdatePasswordControl();
      return true;
    }
    case IDB_COMPRESS_OPTIONS:
    {
      COptionsDialog dialog(this);
      if (dialog.Create(*this) == IDOK)
        ShowOptionsString();
      return true;
    }
  }
  return CModalDialog::OnButtonClicked(buttonID, buttonHWND);
}

void CCompressDialog::CheckSFXControlsEnable()
{
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  bool enable = fi.SFX_();
  if (enable)
  {
    const int methodID = GetMethodID();
    enable = (methodID == -1 || IsMethodSupportedBySfx(methodID));
  }
  if (!enable)
    CheckButton(IDX_COMPRESS_SFX, false);
  EnableItem(IDX_COMPRESS_SFX, enable);
}

/*
void CCompressDialog::CheckVolumeEnable()
{
  bool isSFX = IsSFX();
  m_Volume.Enable(!isSFX);
  if (isSFX)
    m_Volume.SetText(TEXT(""));
}
*/

void CCompressDialog::EnableMultiCombo(unsigned id)
{
  NWindows::NControl::CComboBox combo;
  combo.Attach(GetItem(id));
  const bool enable = (combo.GetCount() > 1);
  EnableItem(id, enable);
}

static LRESULT ComboBox_AddStringAscii(NControl::CComboBox &cb, const char *s);

static void Combine_Two_BoolPairs(const CBoolPair &b1, const CBoolPair &b2, CBool1 &res)
{
  if (!b1.Def && b2.Def)
    res.Val = b2.Val;
  else
    res.Val = b1.Val;
}

#define SET_GUI_BOOL(name) \
      Combine_Two_BoolPairs(Info. name, m_RegistryInfo. name, name)


static void Set_Final_BoolPairs(
    const CBool1 &gui,
    CBoolPair &cmd,
    CBoolPair &reg)
{
  if (!cmd.Def)
  {
    reg.Val = gui.Val;
    reg.Def = gui.Val;
  }
  if (gui.Supported)
  {
    cmd.Val = gui.Val;
    cmd.Def = gui.Val;
  }
  else
    cmd.Init();
}

#define SET_FINAL_BOOL_PAIRS(name) \
    Set_Final_BoolPairs(name, Info. name, m_RegistryInfo. name)

void CCompressDialog::FormatChanged(bool isChanged)
{
  SetLevel();
  SetSolidBlockSize();
  SetParams();
  SetMemUseCombo();
  SetNumThreads();

  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  Info.SolidIsSpecified = fi.Solid_();
  Info.EncryptHeadersIsAllowed = fi.EncryptFileNames_();
  
  /*
  const bool multiThreadEnable = fi.MultiThread;
  Info.MultiThreadIsAllowed = multiThreadEnable;
  EnableItem(IDC_COMPRESS_SOLID, fi.Solid);
  EnableItem(IDC_COMPRESS_THREADS, multiThreadEnable);
  const bool methodEnable = (fi.MethodIDs != NULL);
  EnableItem(IDC_COMPRESS_METHOD, methodEnable);
  EnableMultiCombo(IDC_COMPRESS_DICTIONARY, methodEnable);
  EnableItem(IDC_COMPRESS_ORDER, methodEnable);
  */

  CheckSFXControlsEnable();

  {
    if (!isChanged)
    {
      SET_GUI_BOOL (SymLinks);
      SET_GUI_BOOL (HardLinks);
      SET_GUI_BOOL (AltStreams);
      SET_GUI_BOOL (NtSecurity);
      SET_GUI_BOOL (PreserveATime);
    }

    PreserveATime.Supported = true;

    {
      const CArcInfoEx &ai = Get_ArcInfoEx();
      SymLinks.Supported   = ai.Flags_SymLinks();
      HardLinks.Supported  = ai.Flags_HardLinks();
      AltStreams.Supported = ai.Flags_AltStreams();
      NtSecurity.Supported = ai.Flags_NtSecurity();
    }

    ShowOptionsString();
  }
  // CheckVolumeEnable();

  const bool encrypt = fi.Encrypt_();
  EnableItem(IDG_COMPRESS_ENCRYPTION, encrypt);

  EnableItem(IDT_PASSWORD_ENTER, encrypt);
  EnableItem(IDT_PASSWORD_REENTER, encrypt);
  EnableItem(IDE_COMPRESS_PASSWORD1, encrypt);
  EnableItem(IDE_COMPRESS_PASSWORD2, encrypt);
  EnableItem(IDX_PASSWORD_SHOW, encrypt);

  EnableItem(IDT_COMPRESS_ENCRYPTION_METHOD, encrypt);
  EnableItem(IDC_COMPRESS_ENCRYPTION_METHOD, encrypt);
  EnableItem(IDX_COMPRESS_ENCRYPT_FILE_NAMES, fi.EncryptFileNames_());

  ShowItem_Bool(IDX_COMPRESS_ENCRYPT_FILE_NAMES, fi.EncryptFileNames_());

  SetEncryptionMethod();
  SetMemoryUsage();
}


bool CCompressDialog::IsSFX()
{
  CWindow sfxButton = GetItem(IDX_COMPRESS_SFX);
  return sfxButton.IsEnabled() && IsButtonCheckedBool(IDX_COMPRESS_SFX);
}

static int GetExtDotPos(const UString &s)
{
  int dotPos = s.ReverseFind_Dot();
  if (dotPos > s.ReverseFind_PathSepar() + 1)
    return dotPos;
  return -1;
}

void CCompressDialog::OnButtonSFX()
{
  UString fileName;
  m_ArchivePath.GetText(fileName);
  int dotPos = GetExtDotPos(fileName);
  if (IsSFX())
  {
    if (dotPos >= 0)
      fileName.DeleteFrom(dotPos);
    fileName += kExeExt;
    m_ArchivePath.SetText(fileName);
  }
  else
  {
    if (dotPos >= 0)
    {
      UString ext = fileName.Ptr(dotPos);
      if (ext.IsEqualTo_Ascii_NoCase(kExeExt))
      {
        fileName.DeleteFrom(dotPos);
        m_ArchivePath.SetText(fileName);
      }
    }
    SetArchiveName2(false); // it's for OnInit
  }

  // CheckVolumeEnable();
}

bool CCompressDialog::GetFinalPath_Smart(UString &resPath)
{
  UString name;
  m_ArchivePath.GetText(name);
  name.Trim();
  UString tempPath = name;
  if (!IsAbsolutePath(name))
  {
    UString newDirPrefix = DirPrefix;
    if (newDirPrefix.IsEmpty())
      newDirPrefix = StartDirPrefix;
    FString resultF;
    if (!MyGetFullPathName(us2fs(newDirPrefix + name), resultF))
      return false;
    tempPath = fs2us(resultF);
  }
  if (!SetArcPathFields(tempPath, name, false))
    return false;
  FString resultF;
  if (!MyGetFullPathName(us2fs(DirPrefix + name), resultF))
    return false;
  resPath = fs2us(resultF);
  return true;
}

bool CCompressDialog::SetArcPathFields(const UString &path, UString &name, bool always)
{
  FString resDirPrefix;
  FString resFileName;
  bool res = GetFullPathAndSplit(us2fs(path), resDirPrefix, resFileName);
  if (res)
  {
    DirPrefix = fs2us(resDirPrefix);
    name = fs2us(resFileName);
  }
  else
  {
    if (!always)
      return false;
    DirPrefix.Empty();
    name = path;
  }
  SetItemText(IDT_COMPRESS_ARCHIVE_FOLDER, DirPrefix);
  m_ArchivePath.SetText(name);
  return res;
}

static const wchar_t * const k_IncorrectPathMessage = L"Incorrect archive path";

void CCompressDialog::OnButtonSetArchive()
{
  UString path;
  if (!GetFinalPath_Smart(path))
  {
    ShowErrorMessage(*this, k_IncorrectPathMessage);
    return;
  }

  UString title = LangString(IDS_COMPRESS_SET_ARCHIVE_BROWSE);
  UString filterDescription = LangString(IDS_OPEN_TYPE_ALL_FILES);
  filterDescription += " (*.*)";
  UString resPath;
  CurrentDirWasChanged = true;
  if (!MyBrowseForFile(*this, title,
      // DirPrefix.IsEmpty() ? NULL : (const wchar_t *)DirPrefix,
      // NULL,
      path,
      filterDescription,
      NULL, // L"*.*",
      resPath))
    return;
  UString dummyName;
  SetArcPathFields(resPath, dummyName, true);
}

// in ExtractDialog.cpp
extern void AddUniqueString(UStringVector &strings, const UString &srcString);

static bool IsAsciiString(const UString &s)
{
  for (unsigned i = 0; i < s.Len(); i++)
  {
    wchar_t c = s[i];
    if (c < 0x20 || c > 0x7F)
      return false;
  }
  return true;
}


static void AddSize_MB(UString &s, UInt64 size)
{
  const UInt64 v2 = size + ((UInt32)1 << 20) - 1;
  if (size <= v2)
    size = v2;
  s.Add_UInt64(size >> 20);
  s += " MB";
}


void SetErrorMessage_MemUsage(UString &s, UInt64 reqSize, UInt64 ramSize, UInt64 ramLimit, const UString &usageString)
{
  s += "The operation was blocked by 7-Zip";
  s.Add_LF();
  s += "The operation can require big amount of RAM (memory):";
  s.Add_LF();
  s.Add_LF();
  AddSize_MB(s, reqSize);

  if (!usageString.IsEmpty())
  {
    s += " : ";
    s += usageString;
  }

  s.Add_LF();
  AddSize_MB(s, ramSize);
  s += " : RAM";

  // if (ramLimit != 0)
  {
    s.Add_LF();
    AddSize_MB(s, ramLimit);
    s += " : 7-Zip limit";
  }
  
  s.Add_LF();
  s.Add_LF();
  AddLangString(s, IDS_MEM_ERROR);
}


void CCompressDialog::OnOK()
{
  _password1Control.GetText(Info.Password);
  if (IsZipFormat())
  {
    if (!IsAsciiString(Info.Password))
    {
      ShowErrorMessageHwndRes(*this, IDS_PASSWORD_USE_ASCII);
      return;
    }
    UString method = GetEncryptionMethodSpec();
    if (method.IsPrefixedBy_Ascii_NoCase("aes"))
    {
      if (Info.Password.Len() > 99)
      {
        ShowErrorMessageHwndRes(*this, IDS_PASSWORD_TOO_LONG);
        return;
      }
    }
  }
  if (!IsShowPasswordChecked())
  {
    UString password2;
    _password2Control.GetText(password2);
    if (password2 != Info.Password)
    {
      ShowErrorMessageHwndRes(*this, IDS_PASSWORD_NOT_MATCH);
      return;
    }
  }

  {
    UInt64 decompressMem;
    const UInt64 memUsage = GetMemoryUsage_DecompMem(decompressMem);
    if (memUsage != (UInt64)(Int64)-1)
    {
      const UInt64 limit = Get_MemUse_Bytes();
      if (memUsage > limit)
      {
        UString s;
        UString s2 = LangString(IDT_COMPRESS_MEMORY);
        if (s2.IsEmpty())
          GetItemText(IDT_COMPRESS_MEMORY, s2);
        SetErrorMessage_MemUsage(s, memUsage, _ramSize, limit, s2);
        MessageBoxError(s);
        return;
      }
    }
  }

  SaveOptionsInMem();
  {
    UString s;
    if (!GetFinalPath_Smart(s))
    {
      ShowErrorMessage(*this, k_IncorrectPathMessage);
      return;
    }
    
    m_RegistryInfo.ArcPaths.Clear();
    AddUniqueString(m_RegistryInfo.ArcPaths, s);
    Info.ArcPath = s;
  }
  
  Info.UpdateMode = (NCompressDialog::NUpdateMode::EEnum)k_UpdateMode_Vals[m_UpdateMode.GetCurSel()];;
  Info.PathMode = (NWildcard::ECensorPathMode)k_PathMode_Vals[m_PathMode.GetCurSel()];

  Info.Level = GetLevelSpec();
  Info.Dict64 = GetDictSpec();
  Info.Order = GetOrderSpec();
  Info.OrderMode = GetOrderMode();
  Info.NumThreads = GetNumThreadsSpec();

  Info.MemUsage.Clear();
  {
    const UString mus = Get_MemUse_Spec();
    if (!mus.IsEmpty())
    {
      NCompression::CMemUse mu;
      mu.Parse(mus);
      if (mu.IsDefined)
        Info.MemUsage = mu;
    }
  }

  {
    // Info.SolidIsSpecified = g_Formats[GetStaticFormatIndex()].Solid;
    const UInt32 solidLogSize = GetBlockSizeSpec();
    Info.SolidBlockSize = 0;
    if (solidLogSize == (UInt32)(Int32)-1)
      Info.SolidIsSpecified = false;
    else if (solidLogSize > 0)
      Info.SolidBlockSize = (solidLogSize >= 64) ?
          (UInt64)(Int64)-1 :
          ((UInt64)1 << solidLogSize);
  }

  Info.Method = GetMethodSpec();
  Info.EncryptionMethod = GetEncryptionMethodSpec();
  Info.FormatIndex = GetFormatIndex();
  Info.SFXMode = IsSFX();
  Info.OpenShareForWrite = IsButtonCheckedBool(IDX_COMPRESS_SHARED);
  Info.DeleteAfterCompressing = IsButtonCheckedBool(IDX_COMPRESS_DEL);

  m_RegistryInfo.EncryptHeaders =
    Info.EncryptHeaders = IsButtonCheckedBool(IDX_COMPRESS_ENCRYPT_FILE_NAMES);


  /* (Info) is for saving to registry:
     (CBoolPair::Val) will be set as (false), if it was (false)
       in registry at dialog creation, and user didn't click checkbox.
     in another case (CBoolPair::Val) will be set as (true) */

  {
    /* Info properties could be for another archive types.
       so we disable unsupported properties in Info */
    // const CArcInfoEx &ai = Get_ArcInfoEx();

    SET_FINAL_BOOL_PAIRS (SymLinks);
    SET_FINAL_BOOL_PAIRS (HardLinks);
    SET_FINAL_BOOL_PAIRS (AltStreams);
    SET_FINAL_BOOL_PAIRS (NtSecurity);

    SET_FINAL_BOOL_PAIRS (PreserveATime);
  }

  {
    const NCompression::CFormatOptions &fo = Get_FormatOptions();

    Info.TimePrec = fo.TimePrec;
    Info.MTime = fo.MTime;
    Info.CTime = fo.CTime;
    Info.ATime = fo.ATime;
    Info.SetArcMTime = fo.SetArcMTime;
  }

  m_Params.GetText(Info.Options);
  
  UString volumeString;
  m_Volume.GetText(volumeString);
  volumeString.Trim();
  Info.VolumeSizes.Clear();
  
  if (!volumeString.IsEmpty())
  {
    if (!ParseVolumeSizes(volumeString, Info.VolumeSizes))
    {
      ShowErrorMessageHwndRes(*this, IDS_INCORRECT_VOLUME_SIZE);
      return;
    }
    if (!Info.VolumeSizes.IsEmpty())
    {
      const UInt64 volumeSize = Info.VolumeSizes.Back();
      if (volumeSize < (100 << 10))
      {
        wchar_t s[32];
        ConvertUInt64ToString(volumeSize, s);
        if (::MessageBoxW(*this, MyFormatNew(IDS_SPLIT_CONFIRM, s),
            L"7-Zip", MB_YESNOCANCEL | MB_ICONQUESTION) != IDYES)
          return;
      }
    }
  }

  for (int i = 0; i < m_ArchivePath.GetCount(); i++)
  {
    UString sTemp;
    m_ArchivePath.GetLBText(i, sTemp);
    sTemp.Trim();
    AddUniqueString(m_RegistryInfo.ArcPaths, sTemp);
  }
  
  if (m_RegistryInfo.ArcPaths.Size() > kHistorySize)
    m_RegistryInfo.ArcPaths.DeleteBack();
  
  if (Info.FormatIndex >= 0)
    m_RegistryInfo.ArcType = (*ArcFormats)[Info.FormatIndex].Name;
  m_RegistryInfo.ShowPassword = IsShowPasswordChecked();

  m_RegistryInfo.Save();
  
  CModalDialog::OnOK();
}

#define kHelpTopic "fm/plugins/7-zip/add.htm"

void CCompressDialog::OnHelp()
{
  ShowHelpWindow(kHelpTopic);
}

bool CCompressDialog::OnCommand(int code, int itemID, LPARAM lParam)
{
  if (code == CBN_SELCHANGE)
  {
    switch (itemID)
    {
      case IDC_COMPRESS_ARCHIVE:
      {
        // we can 't change m_ArchivePath in that handler !
        DirPrefix.Empty();
        SetItemText(IDT_COMPRESS_ARCHIVE_FOLDER, DirPrefix);

        /*
        UString path;
        m_ArchivePath.GetText(path);
        m_ArchivePath.SetText(L"");
        if (IsAbsolutePath(path))
        {
          UString fileName;
          SetArcPathFields(path, fileName);
          SetArchiveName(fileName);
        }
        */
        return true;
      }
      
      case IDC_COMPRESS_FORMAT:
      {
        const bool isSFX = IsSFX();
        SaveOptionsInMem();
        FormatChanged(true); // isChanged
        SetArchiveName2(isSFX);
        return true;
      }
      
      case IDC_COMPRESS_LEVEL:
      {
        Get_FormatOptions().ResetForLevelChange();

        SetMethod();
        SetSolidBlockSize();
        SetNumThreads();
        CheckSFXNameChange();
        SetMemoryUsage();
        return true;
      }
      
      case IDC_COMPRESS_METHOD:
      {
        MethodChanged();
        SetSolidBlockSize();
        SetNumThreads();
        CheckSFXNameChange();
        SetMemoryUsage();
        if (Get_ArcInfoEx().Flags_HashHandler())
          SetArchiveName2(false);

        return true;
      }
      
      case IDC_COMPRESS_DICTIONARY:
      {
        /* we want to change the reported threads for Auto line
           and keep selected NumThreads option
           So we save selected NumThreads option in memory */
        SaveOptionsInMem();
        const UInt32 blockSizeLog = GetBlockSizeSpec();
        if (// blockSizeLog != (UInt32)(Int32)-1 &&
               blockSizeLog != kSolidLog_NoSolid
            && blockSizeLog != kSolidLog_FullSolid)
        {
          Get_FormatOptions().Reset_BlockLogSize();
          // SetSolidBlockSize(true);
        }

        SetSolidBlockSize();
        SetNumThreads(); // we want to change the reported threads for Auto line only
        SetMemoryUsage();
        return true;
      }
      
      case IDC_COMPRESS_ORDER:
        return true;
      
      case IDC_COMPRESS_SOLID:
      {
        SetMemoryUsage();
        return true;
      }

      case IDC_COMPRESS_THREADS:
      {
        SetMemoryUsage();
        return true;
      }
      
      case IDC_COMPRESS_MEM_USE:
      {
        /* we want to change the reported threads for Auto line
           and keep selected NumThreads option
           So we save selected NumThreads option in memory */
        SaveOptionsInMem();

        SetNumThreads(); // we want to change the reported threads for Auto line only
        SetMemoryUsage();
        return true;
      }
    }
  }
  return CModalDialog::OnCommand(code, itemID, lParam);
}

void CCompressDialog::CheckSFXNameChange()
{
  const bool isSFX = IsSFX();
  CheckSFXControlsEnable();
  if (isSFX != IsSFX())
    SetArchiveName2(isSFX);
}

void CCompressDialog::SetArchiveName2(bool prevWasSFX)
{
  UString fileName;
  m_ArchivePath.GetText(fileName);
  const CArcInfoEx &prevArchiverInfo = (*ArcFormats)[m_PrevFormat];
  if (prevArchiverInfo.Flags_KeepName() || Info.KeepName)
  {
    UString prevExtension;
    if (prevWasSFX)
      prevExtension = kExeExt;
    else
    {
      prevExtension += '.';
      prevExtension += prevArchiverInfo.GetMainExt();
    }
    const unsigned prevExtensionLen = prevExtension.Len();
    if (fileName.Len() >= prevExtensionLen)
      if (StringsAreEqualNoCase(fileName.RightPtr(prevExtensionLen), prevExtension))
        fileName.DeleteFrom(fileName.Len() - prevExtensionLen);
  }
  SetArchiveName(fileName);
}

// if type.KeepName then use OriginalFileName
// else if !KeepName remove extension
// add new extension

void CCompressDialog::SetArchiveName(const UString &name)
{
  UString fileName = name;
  Info.FormatIndex = GetFormatIndex();
  const CArcInfoEx &ai = (*ArcFormats)[Info.FormatIndex];
  m_PrevFormat = Info.FormatIndex;
  if (ai.Flags_KeepName())
  {
    fileName = OriginalFileName;
  }
  else
  {
    if (!Info.KeepName)
    {
      int dotPos = GetExtDotPos(fileName);
      if (dotPos >= 0)
        fileName.DeleteFrom(dotPos);
    }
  }

  if (IsSFX())
    fileName += kExeExt;
  else
  {
    fileName += '.';
    UString ext = ai.GetMainExt();
    if (ai.Flags_HashHandler())
    {
      UString estimatedName;
      GetMethodSpec(estimatedName);
      if (!estimatedName.IsEmpty())
      {
        ext = estimatedName;
        ext.MakeLower_Ascii();
      }
    }
    fileName += ext;
  }
  m_ArchivePath.SetText(fileName);
}


int CCompressDialog::FindRegistryFormat(const UString &name)
{
  FOR_VECTOR (i, m_RegistryInfo.Formats)
  {
    const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[i];
    if (name.IsEqualTo_NoCase(GetUnicodeString(fo.FormatID)))
      return i;
  }
  return -1;
}


unsigned CCompressDialog::FindRegistryFormat_Always(const UString &name)
{
  int index = FindRegistryFormat(name);
  if (index < 0)
  {
    NCompression::CFormatOptions fo;
    fo.FormatID = GetSystemString(name);
    index = m_RegistryInfo.Formats.Add(fo);
  }
  return index;
}


NCompression::CFormatOptions &CCompressDialog::Get_FormatOptions()
{
  const CArcInfoEx &ai = Get_ArcInfoEx();
  return m_RegistryInfo.Formats[FindRegistryFormat_Always(ai.Name)];
}


int CCompressDialog::GetStaticFormatIndex()
{
  const CArcInfoEx &ai = Get_ArcInfoEx();
  for (unsigned i = 0; i < ARRAY_SIZE(g_Formats); i++)
    if (ai.Name.IsEqualTo_Ascii_NoCase(g_Formats[i].Name))
      return i;
  return 0; // -1;
}

void CCompressDialog::SetNearestSelectComboBox(NControl::CComboBox &comboBox, UInt32 value)
{
  for (int i = comboBox.GetCount() - 1; i >= 0; i--)
    if ((UInt32)comboBox.GetItemData(i) <= value)
    {
      comboBox.SetCurSel(i);
      return;
    }
  if (comboBox.GetCount() > 0)
    comboBox.SetCurSel(0);
}

void CCompressDialog::SetLevel2()
{
  m_Level.ResetContent();
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  const CArcInfoEx &ai = Get_ArcInfoEx();
  UInt32 level = 5;
  {
    int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (fo.Level <= 9)
        level = fo.Level;
      else if (fo.Level == (UInt32)(Int32)-1)
        level = 5;
      else
        level = 9;
    }
  }
  
  for (unsigned i = 0; i < sizeof(UInt32) * 8; i++)
  {
    const UInt32 mask = (UInt32)1 << i;
    if ((fi.LevelsMask & mask) != 0)
    {
      UInt32 langID = g_Levels[i];
      UString s;
      s.Add_UInt32(i);
      s += " - ";
      s += LangString(langID);
      int index = (int)m_Level.AddString(s);
      m_Level.SetItemData(index, i);
    }
    if (fi.LevelsMask <= mask)
      break;
  }
  SetNearestSelectComboBox(m_Level, level);
}


static LRESULT ComboBox_AddStringAscii(NControl::CComboBox &cb, const char *s)
{
  return cb.AddString((CSysString)s);
}

// static const char *k_Auto = "- "; // "auto : ";

static void Modify_Auto(AString &s)
{
  s.Insert(0, "*  ");
  // s += " -";
}

void CCompressDialog::SetMethod2(int keepMethodId)
{
  m_Method.ResetContent();
  _auto_MethodId = -1;
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  const CArcInfoEx &ai = Get_ArcInfoEx();
  if (GetLevel() == 0 && !ai.Flags_HashHandler())
  {
    if (!ai.Is_Tar())
    {
      MethodChanged();
      return;
    }
  }
  UString defaultMethod;
  {
    const int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      defaultMethod = fo.Method;
    }
  }
  const bool isSfx = IsSFX();
  bool weUseSameMethod = false;

  const bool is7z = ai.Is_7z();
  
  for (unsigned m = 0;; m++)
  {
    int methodID;
    const char *method;
    if (m < fi.NumMethods)
    {
      methodID = fi.MethodIDs[m];
      method = kMethodsNames[methodID];
      if (is7z)
      if (methodID == kCopy
          || methodID == kDeflate
          || methodID == kDeflate64
          )
        continue;
    }
    else
    {
      if (!is7z)
        break;
      unsigned extIndex = m - fi.NumMethods;
      if (extIndex >= ExternalMethods.Size())
        break;
      methodID = ARRAY_SIZE(kMethodsNames) + extIndex;
      method = ExternalMethods[extIndex].Ptr();
    }
    if (isSfx)
      if (!IsMethodSupportedBySfx(methodID))
        continue;

    AString s (method);
    int writtenMethodId = methodID;
    if (m == 0)
    {
      _auto_MethodId = methodID;
      writtenMethodId = -1;
      Modify_Auto(s);
    }
    const int itemIndex = (int)ComboBox_AddStringAscii(m_Method, s);
    m_Method.SetItemData(itemIndex, writtenMethodId);
    if (keepMethodId == methodID)
    {
      m_Method.SetCurSel(itemIndex);
      weUseSameMethod = true;
      continue;
    }
    if ((defaultMethod.IsEqualTo_Ascii_NoCase(method) || m == 0) && !weUseSameMethod)
      m_Method.SetCurSel(itemIndex);
  }
  
  if (!weUseSameMethod)
    MethodChanged();
}



bool CCompressDialog::IsZipFormat()
{
  return Get_ArcInfoEx().Is_Zip();
}

bool CCompressDialog::IsXzFormat()
{
  return Get_ArcInfoEx().Is_Xz();
}

void CCompressDialog::SetEncryptionMethod()
{
  _encryptionMethod.ResetContent();
  _default_encryptionMethod_Index = -1;
  const CArcInfoEx &ai = Get_ArcInfoEx();
  if (ai.Is_7z())
  {
    ComboBox_AddStringAscii(_encryptionMethod, "AES-256");
    _encryptionMethod.SetCurSel(0);
    _default_encryptionMethod_Index = 0;
  }
  else if (ai.Is_Zip())
  {
    int index = FindRegistryFormat(ai.Name);
    UString encryptionMethod;
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      encryptionMethod = fo.EncryptionMethod;
    }
    int sel = 0;
    // if (ZipCryptoIsAllowed)
    {
      ComboBox_AddStringAscii(_encryptionMethod, "ZipCrypto");
      sel = (encryptionMethod.IsPrefixedBy_Ascii_NoCase("aes") ? 1 : 0);
      _default_encryptionMethod_Index = 0;
    }
    ComboBox_AddStringAscii(_encryptionMethod, "AES-256");
    _encryptionMethod.SetCurSel(sel);
  }
}


int CCompressDialog::GetMethodID_RAW()
{
  if (m_Method.GetCount() <= 0)
    return -1;
  return (int)(Int32)(UInt32)m_Method.GetItemData_of_CurSel();
}

int CCompressDialog::GetMethodID()
{
  int raw = GetMethodID_RAW();
  if (raw < 0)
    return _auto_MethodId;
  return raw;
}


UString CCompressDialog::GetMethodSpec(UString &estimatedName)
{
  estimatedName.Empty();
  if (m_Method.GetCount() < 1)
    return estimatedName;
  const int methodIdRaw = GetMethodID_RAW();
  int methodId = methodIdRaw;
  if (methodIdRaw < 0)
    methodId = _auto_MethodId;
  UString s;
  if (methodId >= 0)
  {
    if ((unsigned)methodId < ARRAY_SIZE(kMethodsNames))
      estimatedName = kMethodsNames[methodId];
    else
      estimatedName = ExternalMethods[methodId - ARRAY_SIZE(kMethodsNames)];
    if (methodIdRaw >= 0)
      s = estimatedName;
  }
  return s;
}


UString CCompressDialog::GetMethodSpec()
{
  UString estimatedName;
  UString s = GetMethodSpec(estimatedName);
  return s;
}

bool CCompressDialog::IsMethodEqualTo(const UString &s)
{
  UString estimatedName;
  const UString shortName = GetMethodSpec(estimatedName);
  if (s.IsEmpty())
    return shortName.IsEmpty();
  return s.IsEqualTo_NoCase(estimatedName);
}


UString CCompressDialog::GetEncryptionMethodSpec()
{
  UString s;
  if (_encryptionMethod.GetCount() > 0
      && _encryptionMethod.GetCurSel() != _default_encryptionMethod_Index)
  {
    _encryptionMethod.GetText(s);
    s.RemoveChar(L'-');
  }
  return s;
}

static const size_t k_Auto_Dict = (size_t)0 - 1;


int CCompressDialog::AddDict2(size_t sizeReal, size_t sizeShow)
{
  char c = 0;
  unsigned moveBits = 0;
       if ((sizeShow & 0xFFFFF) == 0) { moveBits = 20; c = 'M'; }
  else if ((sizeShow &   0x3FF) == 0) { moveBits = 10; c = 'K'; }
  AString s;
  s.Add_UInt64(sizeShow >> moveBits);
  s.Add_Space();
  if (moveBits != 0)
    s += c;
  s += 'B';
  if (sizeReal == k_Auto_Dict)
    Modify_Auto(s);
  const int index = (int)ComboBox_AddStringAscii(m_Dictionary, s);
  m_Dictionary.SetItemData(index, sizeReal);
  return index;
}


int CCompressDialog::AddDict(size_t size)
{
  return AddDict2(size, size);
}


void CCompressDialog::SetDictionary2()
{
  m_Dictionary.ResetContent();
  // _auto_Dict = (UInt32)1 << 24; // we can use this dictSize to calculate _auto_Solid for unknown method for 7z
  _auto_Dict = (UInt32)(Int32)-1; // for debug:

  const CArcInfoEx &ai = Get_ArcInfoEx();
  UInt32 defaultDict = (UInt32)(Int32)-1;
  {
    const int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (IsMethodEqualTo(fo.Method))
        defaultDict = fo.Dictionary;
    }
  }
  
  const int methodID = GetMethodID();
  const UInt32 level = GetLevel2();
  if (methodID < 0)
    return;
  
  switch (methodID)
  {
    case kLZMA:
    case kLZMA2:
    {
      {
        _auto_Dict =
            ( level <= 3 ? ((UInt32)1 << (level * 2 + 16)) :
            ( level <= 6 ? ((UInt32)1 << (level + 19)) :
            ( level <= 7 ? ((UInt32)1 << 25) : ((UInt32)1 << 26)
            )));
      }

      // we use threshold 3.75 GiB to switch to kLzmaMaxDictSize.
      if (defaultDict != (UInt32)(Int32)-1
          && defaultDict >= ((UInt32)15 << 28))
        defaultDict = kLzmaMaxDictSize;
      
      const size_t kLzmaMaxDictSize_Up = (size_t)1 << (20 + sizeof(size_t) / 4 * 6);
      
      int curSel = AddDict2(k_Auto_Dict, _auto_Dict);

      for (unsigned i = (16 - 1) * 2; i <= (32 - 1) * 2; i++)
      {
        if (i < (20 - 1) * 2
            && i != (16 - 1) * 2
            && i != (18 - 1) * 2)
          continue;
        if (i == (20 - 1) * 2 + 1)
          continue;
        const size_t dict_up = (size_t)(2 + (i & 1)) << (i / 2);
        size_t dict = dict_up;
        if (dict_up >= kLzmaMaxDictSize)
          dict = kLzmaMaxDictSize; // we reduce dictionary
        
        const int index = AddDict(dict);
        // AddDict2(dict, dict_up); // for debug : we show 4 GB

        // const UInt32 numThreads = 2;
        // const UInt64 memUsage = GetMemoryUsageComp_Threads_Dict(numThreads, dict);
        if (defaultDict != (UInt32)(Int32)-1)
          if (dict <= defaultDict || curSel <= 0)
          // if (!maxRamSize_Defined || memUsage <= maxRamSize)
            curSel = index;
        if (dict_up >= kLzmaMaxDictSize_Up)
          break;
      }
      
      m_Dictionary.SetCurSel(curSel);
      break;
    }
    
    case kPPMd:
    {
      _auto_Dict = (UInt32)1 << (level + 19);

      const UInt32 kPpmd_Default_4g = (UInt32)0 - ((UInt32)1 << 10);
      const size_t kPpmd_MaxDictSize_Up = (size_t)1 << (29 + sizeof(size_t) / 8);

      if (defaultDict != (UInt32)(Int32)-1
          && defaultDict >= ((UInt32)15 << 28)) // threshold
        defaultDict = kPpmd_Default_4g;

      int curSel = AddDict2(k_Auto_Dict, _auto_Dict);

      for (unsigned i = (20 - 1) * 2; i <= (32 - 1) * 2; i++)
      {
        if (i == (20 - 1) * 2 + 1)
          continue;

        const size_t dict_up = (size_t)(2 + (i & 1)) << (i / 2);
        size_t dict = dict_up;
        if (dict_up >= kPpmd_Default_4g)
          dict = kPpmd_Default_4g;

        const int index = AddDict2(dict, dict_up);
        // AddDict2((UInt32)((UInt32)0 - 2), dict_up); // for debug
        // AddDict(dict_up); // for debug
        // const UInt64 memUsage = GetMemoryUsageComp_Threads_Dict(1, dict);
        if (defaultDict != (UInt32)(Int32)-1)
          if (dict <= defaultDict || curSel <= 0)
            // if (!maxRamSize_Defined || memUsage <= maxRamSize)
            curSel = index;
        if (dict_up >= kPpmd_MaxDictSize_Up)
          break;
      }
      m_Dictionary.SetCurSel(curSel);
      break;
    }

    case kPPMdZip:
    {
      _auto_Dict = (UInt32)1 << (level + 19);
      
      int curSel = AddDict2(k_Auto_Dict, _auto_Dict);

      for (unsigned i = 20; i <= 28; i++)
      {
        const UInt32 dict = (UInt32)1 << i;
        const int index = AddDict(dict);
        // const UInt64 memUsage = GetMemoryUsageComp_Threads_Dict(1, dict);
        if (defaultDict != (UInt32)(Int32)-1)
          if (dict <= defaultDict || curSel <= 0)
            // if (!maxRamSize_Defined || memUsage <= maxRamSize)
            curSel = index;
      }
      m_Dictionary.SetCurSel(curSel);
      break;
    }

    case kDeflate:
    case kDeflate64:
    {
      const UInt32 dict = (methodID == kDeflate ? (UInt32)(1 << 15) : (UInt32)(1 << 16));
      _auto_Dict = dict;
      AddDict2(k_Auto_Dict, _auto_Dict);
      m_Dictionary.SetCurSel(0);
      // EnableItem(IDC_COMPRESS_DICTIONARY, false);
      break;
    }
    
    case kBZip2:
    {
      {
             if (level >= 5) _auto_Dict = (900 << 10);
        else if (level >= 3) _auto_Dict = (500 << 10);
        else                 _auto_Dict = (100 << 10);
      }

      int curSel = AddDict2(k_Auto_Dict, _auto_Dict);
      
      for (unsigned i = 1; i <= 9; i++)
      {
        const UInt32 dict = ((UInt32)i * 100) << 10;
        AddDict(dict);
        // AddDict2(i * 100000, dict);
        if (defaultDict != (UInt32)(Int32)-1)
          if (i <= defaultDict / 100000 || curSel <= 0)
            curSel = m_Dictionary.GetCount() - 1;
      }
      m_Dictionary.SetCurSel(curSel);
      break;
    }

    case kCopy:
    {
      _auto_Dict = 0;
      AddDict(0);
      m_Dictionary.SetCurSel(0);
      break;
    }
  }
}


UInt32 CCompressDialog::GetComboValue(NWindows::NControl::CComboBox &c, int defMax)
{
  if (c.GetCount() <= defMax)
    return (UInt32)(Int32)-1;
  return (UInt32)c.GetItemData_of_CurSel();
}


UInt64 CCompressDialog::GetComboValue_64(NWindows::NControl::CComboBox &c, int defMax)
{
  if (c.GetCount() <= defMax)
    return (UInt64)(Int64)-1;
  // LRESULT is signed. so we cast it to unsigned size_t at first:
  LRESULT val = c.GetItemData_of_CurSel();
  if (val == (LPARAM)(INT_PTR)(-1))
    return (UInt64)(Int64)-1;
  return (UInt64)(size_t)c.GetItemData_of_CurSel();
}

UInt32 CCompressDialog::GetLevel2()
{
  UInt32 level = GetLevel();
  if (level == (UInt32)(Int32)-1)
    level = 5;
  return level;
}


int CCompressDialog::AddOrder(UInt32 size)
{
  char s[32];
  ConvertUInt32ToString(size, s);
  int index = (int)ComboBox_AddStringAscii(m_Order, s);
  m_Order.SetItemData(index, size);
  return index;
}

int CCompressDialog::AddOrder_Auto()
{
  AString s;
  s.Add_UInt32(_auto_Order);
  Modify_Auto(s);
  int index = (int)ComboBox_AddStringAscii(m_Order, s);
  m_Order.SetItemData(index, (LPARAM)(INT_PTR)(-1));
  return index;
}

void CCompressDialog::SetOrder2()
{
  m_Order.ResetContent();
  
  _auto_Order = 1;

  const CArcInfoEx &ai = Get_ArcInfoEx();
  UInt32 defaultOrder = (UInt32)(Int32)-1;

  {
    const int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (IsMethodEqualTo(fo.Method))
        defaultOrder = fo.Order;
    }
  }

  const int methodID = GetMethodID();
  const UInt32 level = GetLevel2();
  if (methodID < 0)
    return;
  
  switch (methodID)
  {
    case kLZMA:
    case kLZMA2:
    {
      _auto_Order = (level < 7 ? 32 : 64);
      int curSel = AddOrder_Auto();
      for (unsigned i = 2 * 2; i < 8 * 2; i++)
      {
        UInt32 order = ((UInt32)(2 + (i & 1)) << (i / 2));
        if (order > 256)
          order = 273;
        const int index = AddOrder(order);
        if (defaultOrder != (UInt32)(Int32)-1)
          if (order <= defaultOrder || curSel <= 0)
            curSel = index;
      }
      m_Order.SetCurSel(curSel);
      break;
    }

    case kDeflate:
    case kDeflate64:
    {
      {
             if (level >= 9) _auto_Order = 128;
        else if (level >= 7) _auto_Order = 64;
        else                 _auto_Order = 32;
      }
      int curSel = AddOrder_Auto();
      for (unsigned i = 2 * 2; i < 8 * 2; i++)
      {
        UInt32 order = ((UInt32)(2 + (i & 1)) << (i / 2));
        if (order > 256)
          order = (methodID == kDeflate64 ? 257 : 258);
        const int index = AddOrder(order);
        if (defaultOrder != (UInt32)(Int32)-1)
          if (order <= defaultOrder || curSel <= 0)
            curSel = index;
      }
      
      m_Order.SetCurSel(curSel);
      break;
    }
   
    case kPPMd:
    {
      {
             if (level >= 9) _auto_Order = 32;
        else if (level >= 7) _auto_Order = 16;
        else if (level >= 5) _auto_Order = 6;
        else                 _auto_Order = 4;
      }
      
      int curSel = AddOrder_Auto();

      for (unsigned i = 0;; i++)
      {
        UInt32 order = i + 2;
        if (i >= 2)
          order = (4 + ((i - 2) & 3)) << ((i - 2) / 4);
        const int index = AddOrder(order);
        if (defaultOrder != (UInt32)(Int32)-1)
          if (order <= defaultOrder || curSel <= 0)
            curSel = index;
        if (order >= 32)
          break;
      }
      m_Order.SetCurSel(curSel);
      break;
    }

    case kPPMdZip:
    {
      _auto_Order = level + 3;
      int curSel = AddOrder_Auto();
      for (unsigned i = 2; i <= 16; i++)
      {
        const int index = AddOrder(i);
        if (defaultOrder != (UInt32)(Int32)-1)
          if (i <= defaultOrder || curSel <= 0)
            curSel = index;
      }
      m_Order.SetCurSel(curSel);
      break;
    }
    
    // case kBZip2:
    default:
      break;
  }
}

bool CCompressDialog::GetOrderMode()
{
  switch (GetMethodID())
  {
    case kPPMd:
    case kPPMdZip:
      return true;
  }
  return false;
}


static UInt64 Get_Lzma2_ChunkSize(UInt64 dict)
{
  // we use same default chunk sizes as defined in 7z encoder and lzma2 encoder
  UInt64 cs = (UInt64)dict << 2;
  const UInt32 kMinSize = (UInt32)1 << 20;
  const UInt32 kMaxSize = (UInt32)1 << 28;
  if (cs < kMinSize) cs = kMinSize;
  if (cs > kMaxSize) cs = kMaxSize;
  if (cs < dict) cs = dict;
  cs += (kMinSize - 1);
  cs &= ~(UInt64)(kMinSize - 1);
  return cs;
}


static void Add_Size(AString &s, UInt64 val)
{
  unsigned moveBits = 0;
  char c = 0;
       if ((val & 0x3FFFFFFF) == 0) { moveBits = 30; c = 'G'; }
  else if ((val &    0xFFFFF) == 0) { moveBits = 20; c = 'M'; }
  else if ((val &      0x3FF) == 0) { moveBits = 10; c = 'K'; }
  s.Add_UInt64(val >> moveBits);
  s.Add_Space();
  if (moveBits != 0)
    s += c;
  s += 'B';
}


void CCompressDialog::SetSolidBlockSize2()
{
  m_Solid.ResetContent();
  _auto_Solid = 1 << 20;

  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  if (!fi.Solid_())
    return;

  const UInt32 level = GetLevel2();
  if (level == 0)
    return;

  UInt64 dict = GetDict2();
  if (dict == (UInt64)(Int64)-1)
  {
    dict = 1 << 25; // default dict for unknown methods
    // return;
  }


  UInt32 defaultBlockSize = (UInt32)(Int32)-1;

  const CArcInfoEx &ai = Get_ArcInfoEx();

  /*
  if (usePrevDictionary)
    defaultBlockSize = GetBlockSizeSpec();
  else
  */
  {
    const int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (IsMethodEqualTo(fo.Method))
        defaultBlockSize = fo.BlockLogSize;
    }
  }

  const bool is7z = ai.Is_7z();

  const UInt64 cs = Get_Lzma2_ChunkSize(dict);

  // Solid Block Size
  UInt64 blockSize = cs; // for xz

  if (is7z)
  {
    // we use same default block sizes as defined in 7z encoder
    UInt64 kMaxSize = (UInt64)1 << 32;
    const int methodId = GetMethodID();
    if (methodId == kLZMA2)
    {
      blockSize = cs << 6;
      kMaxSize = (UInt64)1 << 34;
    }
    else
    {
      UInt64 dict2 = dict;
      if (methodId == kBZip2)
      {
        dict2 /= 100000;
        if (dict2 < 1)
          dict2 = 1;
        dict2 *= 100000;
      }
      blockSize = dict2 << 7;
    }

    const UInt32 kMinSize = (UInt32)1 << 24;
    if (blockSize < kMinSize) blockSize = kMinSize;
    if (blockSize > kMaxSize) blockSize = kMaxSize;
  }

  _auto_Solid = blockSize;

  int curSel;
  {
    AString s;
    Add_Size(s, _auto_Solid);
    Modify_Auto(s);
    int index = (int)ComboBox_AddStringAscii(m_Solid, s);
    m_Solid.SetItemData(index, (UInt32)(Int32)-1);
    curSel = index;
  }

  if (is7z)
  {
    UString s ('-');
    // kSolidLog_NoSolid = 0 for xz means default blockSize
    if (is7z)
      LangString(IDS_COMPRESS_NON_SOLID, s);
    const int index = (int)m_Solid.AddString(s);
    m_Solid.SetItemData(index, (UInt32)kSolidLog_NoSolid);
    if (defaultBlockSize == kSolidLog_NoSolid)
      curSel = index;
  }
  
  for (unsigned i = 20; i <= 36; i++)
  {
    AString s;
    Add_Size(s, (UInt64)1 << i);
    const int index = (int)ComboBox_AddStringAscii(m_Solid, s);
    m_Solid.SetItemData(index, (UInt32)i);
    if (defaultBlockSize != (UInt32)(Int32)-1)
      if (i <= defaultBlockSize || index <= 1)
        curSel = index;
  }
  
  {
    const int index = (int)m_Solid.AddString(LangString(IDS_COMPRESS_SOLID));
    m_Solid.SetItemData(index, kSolidLog_FullSolid);
    if (defaultBlockSize == kSolidLog_FullSolid)
      curSel = index;
  }

  m_Solid.SetCurSel(curSel);
}


void CCompressDialog::SetNumThreads2()
{
  _auto_NumThreads = 1;

  m_NumThreads.ResetContent();
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  if (!fi.MultiThread_())
    return;

  const UInt32 numHardwareThreads = NSystem::GetNumberOfProcessors();
    // 64; // for debug:

  UInt32 defaultValue = numHardwareThreads;
  bool useAutoThreads = true;

  {
    const CArcInfoEx &ai = Get_ArcInfoEx();
    int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (IsMethodEqualTo(fo.Method) && fo.NumThreads != (UInt32)(Int32)-1)
      {
        defaultValue = fo.NumThreads;
        useAutoThreads = false;
      }
    }
  }

  UInt32 numAlgoThreadsMax = numHardwareThreads * 2;
  const int methodID = GetMethodID();
  switch (methodID)
  {
    case kLZMA: numAlgoThreadsMax = 2; break;
    case kLZMA2: numAlgoThreadsMax = 256; break;
    case kBZip2: numAlgoThreadsMax = 32; break;
    case kCopy:
    case kPPMd:
    case kDeflate:
    case kDeflate64:
    case kPPMdZip:
      numAlgoThreadsMax = 1;
  }
  const bool isZip = IsZipFormat();
  if (isZip)
  {
    numAlgoThreadsMax =
      #ifdef _WIN32
        64; // _WIN32 supports only 64 threads in one group. So no need for more threads here
      #else
        128;
      #endif
  }

  UInt32 autoThreads = numHardwareThreads;
  if (autoThreads > numAlgoThreadsMax)
    autoThreads = numAlgoThreadsMax;

  const UInt64 memUse_Limit = Get_MemUse_Bytes();

  if (autoThreads > 1 && _ramSize_Defined)
  {
    if (isZip)
    {
      for (; autoThreads > 1; autoThreads--)
      {
        const UInt64 dict64 = GetDict2();
        UInt64 decompressMemory;
        const UInt64 usage = GetMemoryUsage_Threads_Dict_DecompMem(autoThreads, dict64, decompressMemory);
        if (usage <= memUse_Limit)
          break;
      }
    }
    else if (methodID == kLZMA2)
    {
      const UInt64 dict64 = GetDict2();
      const UInt32 numThreads1 = (GetLevel2() >= 5 ? 2 : 1);
      UInt32 numBlockThreads = autoThreads / numThreads1;
      for (; numBlockThreads > 1; numBlockThreads--)
      {
        autoThreads = numBlockThreads * numThreads1;
        UInt64 decompressMemory;
        const UInt64 usage = GetMemoryUsage_Threads_Dict_DecompMem(autoThreads, dict64, decompressMemory);
        if (usage <= memUse_Limit)
          break;
      }
      autoThreads = numBlockThreads * numThreads1;
    }
  }

  _auto_NumThreads = autoThreads;

  int curSel = -1;
  {
    AString s;
    s.Add_UInt32(autoThreads);
    Modify_Auto(s);
    int index = (int)ComboBox_AddStringAscii(m_NumThreads, s);
    m_NumThreads.SetItemData(index, (LPARAM)(INT_PTR)(-1));
    // m_NumThreads.SetItemData(index, autoThreads);
    if (useAutoThreads)
      curSel = index;
  }

  if (numAlgoThreadsMax != autoThreads || autoThreads != 1)
  for (UInt32 i = 1; i <= numHardwareThreads * 2 && i <= numAlgoThreadsMax; i++)
  {
    char s[32];
    ConvertUInt32ToString(i, s);
    int index = (int)ComboBox_AddStringAscii(m_NumThreads, s);
    m_NumThreads.SetItemData(index, (UInt32)i);
    if (!useAutoThreads && i == defaultValue)
      curSel = index;
  }
 
  m_NumThreads.SetCurSel(curSel);
}


static void AddMemSize(UString &res, UInt64 size)
{
  char c;
  unsigned moveBits = 0;
  if (size >= ((UInt64)1 << 31) && (size & 0x3FFFFFFF) == 0)
    { moveBits = 30; c = 'G'; }
  else // if (size >= ((UInt32)1 << 21) && (size & 0xFFFFF) == 0)
    { moveBits = 20; c = 'M'; }
  // else { moveBits = 10; c = 'K'; }
  res.Add_UInt64(size >> moveBits);
  res.Add_Space();
  if (moveBits != 0)
    res += c;
  res += 'B';
}


int CCompressDialog::AddMemComboItem(UInt64 val, bool isPercent, bool isDefault)
{
  UString sUser;
  UString sRegistry;
  if (isPercent)
  {
    UString s;
    s.Add_UInt64(val);
    s += '%';
    if (isDefault)
      sUser = "* ";
    else
      sRegistry = s;
    sUser += s;
  }
  else
  {
    AddMemSize(sUser, val);
    sRegistry = sUser;
    for (;;)
    {
      int pos = sRegistry.Find(L' ');
      if (pos < 0)
        break;
      sRegistry.Delete(pos);
    }
    if (!sRegistry.IsEmpty())
      if (sRegistry.Back() == 'B')
        sRegistry.DeleteBack();
  }
  const unsigned dataIndex = _memUse_Strings.Add(sRegistry);
  const int index = (int)m_MemUse.AddString(sUser);
  m_MemUse.SetItemData(index, dataIndex);
  return index;
}



void CCompressDialog::SetMemUseCombo()
{
  _memUse_Strings.Clear();
  m_MemUse.ResetContent();
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];

  {
    const bool enable = fi.MemUse_();
    ShowItem_Bool(IDT_COMPRESS_MEMORY, enable);
    ShowItem_Bool(IDT_COMPRESS_MEMORY_VALUE, enable);
    ShowItem_Bool(IDT_COMPRESS_MEMORY_DE, enable);
    ShowItem_Bool(IDT_COMPRESS_MEMORY_DE_VALUE, enable);
    ShowItem_Bool(IDC_COMPRESS_MEM_USE, enable);
    EnableItem(IDC_COMPRESS_MEM_USE, enable);
    if (!enable)
      return;
  }

  UInt64 curMem_Bytes = 0;
  UInt64 curMem_Percents = 0;
  bool needSetCur_Bytes = false;
  bool needSetCur_Percents = false;
  {
    const NCompression::CFormatOptions &fo = Get_FormatOptions();
    if (!fo.MemUse.IsEmpty())
    {
      NCompression::CMemUse mu;
      mu.Parse(fo.MemUse);
      if (mu.IsDefined)
      {
        if (mu.IsPercent)
        {
          curMem_Percents = mu.Val;
          needSetCur_Percents = true;
        }
        else
        {
          curMem_Bytes = mu.GetBytes(_ramSize_Reduced);
          needSetCur_Bytes = true;
        }
      }
    }
  }

  
  // 80% - is auto usage limit in handlers
  AddMemComboItem(80, true, true);
  m_MemUse.SetCurSel(0);

  {
    for (unsigned i = 10;; i += 10)
    {
      UInt64 size = i;
      if (i > 100)
        size = (UInt64)(Int64)-1;
      if (needSetCur_Percents && size >= curMem_Percents)
      {
        const int index = AddMemComboItem(curMem_Percents, true);
        m_MemUse.SetCurSel(index);
        needSetCur_Percents = false;
        if (size == curMem_Percents)
          continue;
      }
      if (size == (UInt64)(Int64)-1)
        break;
      AddMemComboItem(size, true);
    }
  }
  {
    for (unsigned i = (27) * 2;; i++)
    {
      UInt64 size = (UInt64)(2 + (i & 1)) << (i / 2);
      if (i > (20 + sizeof(size_t) * 3 - 1) * 2)
        size = (UInt64)(Int64)-1;
      if (needSetCur_Bytes && size >= curMem_Bytes)
      {
        const int index = AddMemComboItem(curMem_Bytes);
        m_MemUse.SetCurSel(index);
        needSetCur_Bytes = false;
        if (size == curMem_Bytes)
          continue;
      }
      if (size == (UInt64)(Int64)-1)
        break;
      AddMemComboItem(size);
    }
  }
}


UString CCompressDialog::Get_MemUse_Spec()
{
  if (m_MemUse.GetCount() < 1)
    return UString();
  return _memUse_Strings[(unsigned)m_MemUse.GetItemData_of_CurSel()];
}


UInt64 CCompressDialog::Get_MemUse_Bytes()
{
  const UString mus = Get_MemUse_Spec();
  NCompression::CMemUse mu;
  if (!mus.IsEmpty())
  {
    mu.Parse(mus);
    if (mu.IsDefined)
      return mu.GetBytes(_ramSize_Reduced);
  }
  return _ramUsage_Auto; // _ramSize_Reduced; // _ramSize;;
}



UInt64 CCompressDialog::GetMemoryUsage_DecompMem(UInt64 &decompressMemory)
{
  return GetMemoryUsage_Dict_DecompMem(GetDict2(), decompressMemory);
}

UInt64 CCompressDialog::GetMemoryUsageComp_Threads_Dict(UInt32 numThreads, UInt64 dict64)
{
  UInt64 decompressMemory;
  return GetMemoryUsage_Threads_Dict_DecompMem(numThreads, dict64, decompressMemory);
}

UInt64 CCompressDialog::GetMemoryUsage_Dict_DecompMem(UInt64 dict64, UInt64 &decompressMemory)
{
  return GetMemoryUsage_Threads_Dict_DecompMem(GetNumThreads2(), dict64, decompressMemory);
}

UInt64 CCompressDialog::GetMemoryUsage_Threads_Dict_DecompMem(UInt32 numThreads, UInt64 dict64, UInt64 &decompressMemory)
{
  decompressMemory = (UInt64)(Int64)-1;
  if (dict64 == (UInt64)(Int64)-1)
    return (UInt64)(Int64)-1;

  UInt32 level = GetLevel2();
  if (level == 0)
  {
    decompressMemory = (1 << 20);
    return decompressMemory;
  }
  UInt64 size = 0;

  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  if (fi.Filter_() && level >= 9)
    size += (12 << 20) * 2 + (5 << 20);
  // UInt32 numThreads = GetNumThreads2();
  
  UInt32 numMainZipThreads = 1;

  if (IsZipFormat())
  {
    UInt32 numSubThreads = 1;
    if (GetMethodID() == kLZMA && numThreads > 1 && level >= 5)
      numSubThreads = 2;
    numMainZipThreads = numThreads / numSubThreads;
    if (numMainZipThreads > 1)
      size += (UInt64)numMainZipThreads * ((size_t)sizeof(size_t) << 23);
    else
      numMainZipThreads = 1;
  }
  
  const int methodId = GetMethodID();
  
  switch (methodId)
  {
    case kLZMA:
    case kLZMA2:
    {
      const UInt32 dict = (dict64 >= kLzmaMaxDictSize ? kLzmaMaxDictSize : (UInt32)dict64);
      UInt32 hs = dict - 1;
      hs |= (hs >> 1);
      hs |= (hs >> 2);
      hs |= (hs >> 4);
      hs |= (hs >> 8);
      hs >>= 1;
      if (hs >= (1 << 24))
        hs >>= 1;
      hs |= (1 << 16) - 1;
      // if (numHashBytes >= 5)
      if (level < 5)
        hs |= (256 << 10) - 1;
      hs++;
      UInt64 size1 = (UInt64)hs * 4;
      size1 += (UInt64)dict * 4;
      if (level >= 5)
        size1 += (UInt64)dict * 4;
      size1 += (2 << 20);

      UInt32 numThreads1 = 1;
      if (numThreads > 1 && level >= 5)
      {
        size1 += (2 << 20) + (4 << 20);
        numThreads1 = 2;
      }
      
      UInt32 numBlockThreads = numThreads / numThreads1;
    
      UInt64 chunkSize = 0; // it's solid chunk

      if (methodId != kLZMA && numBlockThreads != 1)
      {
        chunkSize = Get_Lzma2_ChunkSize(dict);

        if (IsXzFormat())
        {
          UInt32 blockSizeLog = GetBlockSizeSpec();
          if (blockSizeLog != (UInt32)(Int32)-1)
          {
            if (blockSizeLog == kSolidLog_FullSolid)
            {
              numBlockThreads = 1;
              chunkSize = 0;
            }
            else if (blockSizeLog != kSolidLog_NoSolid)
              chunkSize = (UInt64)1 << blockSizeLog;
          }
        }
      }

      if (chunkSize == 0)
      {
        const UInt32 kBlockSizeMax = (UInt32)0 - (UInt32)(1 << 16);
        UInt64 blockSize = (UInt64)dict + (1 << 16)
          + (numThreads1 > 1 ? (1 << 20) : 0);
        blockSize += (blockSize >> (blockSize < ((UInt32)1 << 30) ? 1 : 2));
        if (blockSize >= kBlockSizeMax)
          blockSize = kBlockSizeMax;
        size += numBlockThreads * (size1 + blockSize);
      }
      else
      {
        size += numBlockThreads * (size1 + chunkSize);
        UInt32 numPackChunks = numBlockThreads + (numBlockThreads / 8) + 1;
        if (chunkSize < ((UInt32)1 << 26)) numBlockThreads++;
        if (chunkSize < ((UInt32)1 << 24)) numBlockThreads++;
        if (chunkSize < ((UInt32)1 << 22)) numBlockThreads++;
        size += numPackChunks * chunkSize;
      }

      decompressMemory = dict + (2 << 20);
      return size;
    }
  
    case kPPMd:
    {
      decompressMemory = dict64 + (2 << 20);
      return size + decompressMemory;
    }
    
    case kDeflate:
    case kDeflate64:
    {
      UInt64 size1 = 3 << 20;
      // if (level >= 7)
        size1 += (1 << 20);
      size += size1 * numMainZipThreads;
      decompressMemory = (2 << 20);
      return size;
    }
    
    case kBZip2:
    {
      decompressMemory = (7 << 20);
      UInt64 memForOneThread = (10 << 20);
      return size + memForOneThread * numThreads;
    }
    
    case kPPMdZip:
    {
      decompressMemory = dict64 + (2 << 20);
      return size + (UInt64)decompressMemory * numThreads;
    }
  }
  
  return (UInt64)(Int64)-1;
}



static void AddMemUsage(UString &s, UInt64 v)
{
  const char *post;
  if (v <= ((UInt64)16 << 30))
  {
    v = (v + (1 << 20) - 1) >> 20;
    post = "MB";
  }
  else if (v <= ((UInt64)64 << 40))
  {
    v = (v + (1 << 30) - 1) >> 30;
    post = "GB";
  }
  else
  {
    const UInt64 v2 = v + ((UInt64)1 << 40) - 1;
    if (v <= v2)
      v = v2;
    v >>= 40;
    post = "TB";
  }
  s.Add_UInt64(v);
  s.Add_Space();
  s += post;
}


void CCompressDialog::PrintMemUsage(UINT res, UInt64 value)
{
  if (value == (UInt64)(Int64)-1)
  {
    SetItemText(res, TEXT("?"));
    return;
  }
  UString s;
  AddMemUsage(s, value);
  if (res == IDT_COMPRESS_MEMORY_VALUE)
  {
    const UString mus = Get_MemUse_Spec();
    NCompression::CMemUse mu;
    if (!mus.IsEmpty())
      mu.Parse(mus);
    if (mu.IsDefined)
    {
      s += " / ";
      AddMemUsage(s, mu.GetBytes(_ramSize_Reduced));
    }
    else if (_ramSize_Defined)
    {
      s += " / ";
      AddMemUsage(s, _ramUsage_Auto);
    }

    if (_ramSize_Defined)
    {
      s += " / ";
      AddMemUsage(s, _ramSize);
    }
  }
  SetItemText(res, s);
}

    
void CCompressDialog::SetMemoryUsage()
{
  UInt64 decompressMem;
  const UInt64 memUsage = GetMemoryUsage_DecompMem(decompressMem);
  PrintMemUsage(IDT_COMPRESS_MEMORY_VALUE, memUsage);
  PrintMemUsage(IDT_COMPRESS_MEMORY_DE_VALUE, decompressMem);
}

void CCompressDialog::SetParams()
{
  const CArcInfoEx &ai = Get_ArcInfoEx();
  m_Params.SetText(TEXT(""));
  const int index = FindRegistryFormat(ai.Name);
  if (index >= 0)
  {
    const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
    m_Params.SetText(fo.Options);
  }
}

void CCompressDialog::SaveOptionsInMem()
{
  /* these options are for (Info.FormatIndex).
     If it's called just after format changing,
     then it's format that was selected before format changing
     So we store previous format properties */

  m_Params.GetText(Info.Options);
  Info.Options.Trim();

  const CArcInfoEx &ai = (*ArcFormats)[Info.FormatIndex];
  const unsigned index = FindRegistryFormat_Always(ai.Name);
  NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
  fo.Options = Info.Options;
  fo.Level = GetLevelSpec();
  {
    const UInt64 dict64 = GetDictSpec();
    UInt32 dict32;
    if (dict64 == (UInt64)(Int64)-1)
      dict32 = (UInt32)(Int32)-1;
    else
    {
      dict32 = (UInt32)dict64;
      if (dict64 != dict32)
      {
        /* here we must write 32-bit value for registry that indicates big_value
           (UInt32)(Int32)-1  : is used as marker for default size
           (UInt32)(Int32)-2  : it can be used to indicate big value (4 GiB)
           the value must be larger than threshold
        */
        dict32 = (UInt32)(Int32)-2;
        // dict32 = kLzmaMaxDictSize; // it must be larger than threshold
      }
    }
    fo.Dictionary = dict32;
  }

  fo.Order = GetOrderSpec();
  fo.Method = GetMethodSpec();
  fo.EncryptionMethod = GetEncryptionMethodSpec();
  fo.NumThreads = GetNumThreadsSpec();
  fo.BlockLogSize = GetBlockSizeSpec();
  fo.MemUse = Get_MemUse_Spec();
}


unsigned CCompressDialog::GetFormatIndex()
{
  return (unsigned)m_Format.GetItemData_of_CurSel();
}



static void AddText_from_BoolPair(AString &s, const char *name, const CBoolPair &bp)
{
  if (bp.Def)
  {
    s.Add_OptSpaced(name);
    if (!bp.Val)
      s += "-";
  }
  /*
  else if (bp.Val)
  {
    s.Add_OptSpaced("[");
    s += name;
    s += "]";
  }
  */
}


static void AddText_from_Bool1(AString &s, const char *name, const CBool1 &b)
{
  if (b.Supported && b.Val)
    s.Add_OptSpaced(name);
}


void CCompressDialog::ShowOptionsString()
{
  NCompression::CFormatOptions &fo = Get_FormatOptions();

  AString s;
  if (fo.IsSet_TimePrec())
  {
    s.Add_OptSpaced("tp");
    s.Add_UInt32(fo.TimePrec);
  }
  AddText_from_BoolPair(s, "tm", fo.MTime);
  AddText_from_BoolPair(s, "tc", fo.CTime);
  AddText_from_BoolPair(s, "ta", fo.ATime);
  AddText_from_BoolPair(s, "-stl", fo.SetArcMTime);

  // const CArcInfoEx &ai = Get_ArcInfoEx();
  AddText_from_Bool1(s, "SL",  SymLinks);
  AddText_from_Bool1(s, "HL",  HardLinks);
  AddText_from_Bool1(s, "AS",  AltStreams);
  AddText_from_Bool1(s, "Sec", NtSecurity);

  // AddText_from_Bool1(s, "Preserve", PreserveATime);
  
  SetItemText(IDT_COMPRESS_OPTIONS, GetUnicodeString(s));
}





// ---------- OPTIONS ----------


void COptionsDialog::CheckButton_Bool1(UINT id, const CBool1 &b1)
{
  CheckButton(id, b1.Val);
}

void COptionsDialog::GetButton_Bool1(UINT id, CBool1 &b1)
{
  b1.Val = IsButtonCheckedBool(id);
}


void COptionsDialog::CheckButton_BoolBox(
    bool supported, const CBoolPair &b2, CBoolBox &bb)
{
  const bool isSet = b2.Def;
  const bool val = isSet ? b2.Val : bb.DefaultVal;

  bb.IsSupported = supported;

  CheckButton (bb.Set_Id, isSet);
  ShowItem_Bool (bb.Set_Id, supported);
  CheckButton (bb.Id, val);
  EnableItem (bb.Id, isSet);
  ShowItem_Bool (bb.Id, supported);
}

void COptionsDialog::GetButton_BoolBox(CBoolBox &bb)
{
  // we save value for invisible buttons too
  bb.BoolPair.Val = IsButtonCheckedBool (bb.Id);
  bb.BoolPair.Def = IsButtonCheckedBool (bb.Set_Id);
}


void COptionsDialog::Store_TimeBoxes()
{
  TimePrec = GetPrecSpec();
  GetButton_BoolBox (MTime);
  GetButton_BoolBox (CTime);
  GetButton_BoolBox (ATime);
  GetButton_BoolBox (ZTime);
}


UInt32 COptionsDialog::GetComboValue(NWindows::NControl::CComboBox &c, int defMax)
{
  if (c.GetCount() <= defMax)
    return (UInt32)(Int32)-1;
  return (UInt32)c.GetItemData_of_CurSel();
}

static const unsigned kTimePrec_Win  = 0;
static const unsigned kTimePrec_Unix = 1;
static const unsigned kTimePrec_DOS  = 2;
static const unsigned kTimePrec_1ns  = 3;

static void AddTimeOption(UString &s, UInt32 val, const UString &unit, const char *sys = NULL)
{
  // s += " : ";
  {
    AString s2;
    s2.Add_UInt32(val);
    s += s2;
  }
  s.Add_Space();
  s += unit;
  if (sys)
  {
    s += " : ";
    s += sys;
  }
}

int COptionsDialog::AddPrec(unsigned prec, bool isDefault)
{
  UString s;
  UInt32 writePrec = prec;
  if (isDefault)
  {
    // s += "* ";
    // writePrec = (UInt32)(Int32)-1;
  }
       if (prec == kTimePrec_Win)  AddTimeOption(s, 100, NsString, "Windows");
  else if (prec == kTimePrec_Unix) AddTimeOption(s, 1, SecString, "Unix");
  else if (prec == kTimePrec_DOS)  AddTimeOption(s, 2, SecString, "DOS");
  else if (prec == kTimePrec_1ns)  AddTimeOption(s, 1, NsString, "Linux");
  else if (prec == k_PropVar_TimePrec_Base) AddTimeOption(s, 1, SecString);
  else if (prec >= k_PropVar_TimePrec_Base)
  {
    UInt32 d = 1;
    for (unsigned i = prec; i < k_PropVar_TimePrec_Base + 9; i++)
      d *= 10;
    AddTimeOption(s, d, NsString);
  }
  else
    s.Add_UInt32(prec);
  const int index = (int)m_Prec.AddString(s);
  m_Prec.SetItemData(index, writePrec);
  return index;
}


void COptionsDialog::SetPrec()
{
  // const CFormatInfo &fi = g_Formats[cd->GetStaticFormatIndex()];
  const CArcInfoEx &ai = cd->Get_ArcInfoEx();

  // UInt32 flags = fi.Flags;

  UInt32 flags = ai.Get_TimePrecFlags();
  UInt32 defaultPrec = ai.Get_DefaultTimePrec();
  if (defaultPrec != 0)
    flags |= ((UInt32)1 << defaultPrec);

  // const NCompression::CFormatOptions &fo = cd->Get_FormatOptions();

  // unsigned defaultPrec = kTimePrec_Win;

  if (ai.Is_GZip())
    defaultPrec = kTimePrec_Unix;

  {
    UString s;
    s += GetNameOfProperty(kpidType, L"type");
    s += ": ";
    s += ai.Name;
    if (ai.Is_Tar())
    {
      const int methodID = cd->GetMethodID();
      
      // for debug
      // defaultPrec = kTimePrec_Unix;
      // flags = (UInt32)1 << kTimePrec_Unix;

      s += ":";
      if (methodID >= 0 && (unsigned)methodID < ARRAY_SIZE(kMethodsNames))
        s += kMethodsNames[methodID];
      if (methodID == kPosix)
      {
        // for debug
        // flags |= (UInt32)1 << kTimePrec_Win;
        // flags |= (UInt32)1 << kTimePrec_1ns;
      }
    }
    else
    {
      // if (is_for_MethodChanging) return;
    }
    
    SetItemText(IDT_COMPRESS_TIME_INFO, s);
  }

  m_Prec.ResetContent();
  _auto_Prec = defaultPrec;

  unsigned selectedPrec = defaultPrec;
  {
    // if (TimePrec >= kTimePrec_Win && TimePrec <= kTimePrec_DOS)
    if ((Int32)TimePrec >= 0)
      selectedPrec = TimePrec;
  }

  int curSel = -1;
  int defaultPrecIndex = -1;
  for (unsigned prec = 0;
      // prec <= k_PropVar_TimePrec_HighPrec;
      prec <= k_PropVar_TimePrec_1ns;
      prec++)
  {
    if (((flags >> prec) & 1) == 0)
      continue;
    const bool isDefault = (defaultPrec == prec);
    const int index = AddPrec(prec, isDefault);
    if (isDefault)
      defaultPrecIndex = index;
    if (selectedPrec == prec)
      curSel = index;
  }

  if (curSel < 0 && selectedPrec > kTimePrec_DOS)
    curSel = AddPrec(selectedPrec, false); // isDefault
  if (curSel < 0)
    curSel = defaultPrecIndex;
  if (curSel >= 0)
    m_Prec.SetCurSel(curSel);

  {
    const bool isSet = IsSet_TimePrec();
    const int count = m_Prec.GetCount();
    const bool showPrec = (count != 0);
    ShowItem_Bool(IDC_COMPRESS_TIME_PREC, showPrec);
    ShowItem_Bool(IDT_COMPRESS_TIME_PREC, showPrec);
    EnableItem(IDC_COMPRESS_TIME_PREC, isSet && (count > 1));

    CheckButton(IDX_COMPRESS_PREC_SET, isSet);
    const bool setIsSupported = isSet || (count > 1);
    EnableItem(IDX_COMPRESS_PREC_SET, setIsSupported);
    ShowItem_Bool(IDX_COMPRESS_PREC_SET, setIsSupported);
  }

  SetTimeMAC();
}


void COptionsDialog::SetTimeMAC()
{
  const CArcInfoEx &ai = cd->Get_ArcInfoEx();

  const
  bool m_allow = ai.Flags_MTime();
  bool c_allow = ai.Flags_CTime();
  bool a_allow = ai.Flags_ATime();

  if (ai.Is_Tar())
  {
    const int methodID = cd->GetMethodID();
    c_allow = false;
    a_allow = false;
    if (methodID == kPosix)
    {
      // c_allow = true; // do we need it as change time ?
      a_allow = true;
    }
  }

  if (ai.Is_Zip())
  {
    // const int methodID = GetMethodID();
    UInt32 prec = GetPrec();
    if (prec == (UInt32)(Int32)-1)
      prec = _auto_Prec;
    if (prec != kTimePrec_Win)
    {
      c_allow = false;
      a_allow = false;
    }
  }

  
  /*
  MTime.DefaultVal = true;
  CTime.DefaultVal = false;
  ATime.DefaultVal = false;
  */

  MTime.DefaultVal = ai.Flags_MTime_Default();
  CTime.DefaultVal = ai.Flags_CTime_Default();
  ATime.DefaultVal = ai.Flags_ATime_Default();
  
  ZTime.DefaultVal = false;

  const NCompression::CFormatOptions &fo = cd->Get_FormatOptions();
  
  CheckButton_BoolBox (m_allow, fo.MTime, MTime );
  CheckButton_BoolBox (c_allow, fo.CTime, CTime );
  CheckButton_BoolBox (a_allow, fo.ATime, ATime );
  CheckButton_BoolBox (true, fo.SetArcMTime, ZTime);

  if (m_allow && !fo.MTime.Def)
  {
    const bool isSingleFile = ai.Flags_KeepName();
    if (!isSingleFile)
    {
      // we can hide changing checkboxes for MTime here:
      ShowItem_Bool (MTime.Set_Id, false);
      EnableItem (MTime.Id, false);
    }
  }
  // On_CheckBoxSet_Prec_Clicked();
  // const bool isSingleFile = ai.Flags_KeepName();
  // mtime for Gz can be
}



void COptionsDialog::On_CheckBoxSet_Prec_Clicked()
{
  const bool isSet = IsButtonCheckedBool(IDX_COMPRESS_PREC_SET);
  if (!isSet)
  {
    // We save current MAC boxes to memory before SetPrec()
    Store_TimeBoxes();
    Reset_TimePrec();
    SetPrec();
  }
  EnableItem(IDC_COMPRESS_TIME_PREC, isSet);
}

void COptionsDialog::On_CheckBoxSet_Clicked(const CBoolBox &bb)
{
  const bool isSet = IsButtonCheckedBool(bb.Set_Id);
  if (!isSet)
    CheckButton(bb.Id, bb.DefaultVal);
  EnableItem(bb.Id, isSet);
}




#ifdef LANG
static const UInt32 kLangIDs_Options[] =
{
  IDX_COMPRESS_NT_SYM_LINKS,
  IDX_COMPRESS_NT_HARD_LINKS,
  IDX_COMPRESS_NT_ALT_STREAMS,
  IDX_COMPRESS_NT_SECUR,

  IDG_COMPRESS_TIME,
  IDT_COMPRESS_TIME_PREC,
  IDX_COMPRESS_MTIME,
  IDX_COMPRESS_CTIME,
  IDX_COMPRESS_ATIME,
  IDX_COMPRESS_ZTIME,
  IDX_COMPRESS_PRESERVE_ATIME
};
#endif


bool COptionsDialog::OnInit()
{
  #ifdef LANG
  LangSetWindowText(*this, IDB_COMPRESS_OPTIONS); // IDS_OPTIONS
  LangSetDlgItems(*this, kLangIDs_Options, ARRAY_SIZE(kLangIDs_Options));
  // LangSetDlgItemText(*this, IDB_COMPRESS_TIME_DEFAULT, IDB_COMPRESS_TIME_DEFAULT);
  // LangSetDlgItemText(*this, IDX_COMPRESS_TIME_DEFAULT, IDX_COMPRESS_TIME_DEFAULT);
  #endif

  LangString(IDS_COMPRESS_SEC, SecString);
  if (SecString.IsEmpty())
    SecString = "sec";
  LangString(IDS_COMPRESS_NS, NsString);
  if (NsString.IsEmpty())
    NsString = "ns";

  {
    // const CArcInfoEx &ai = cd->Get_ArcInfoEx();
    
    ShowItem_Bool ( IDX_COMPRESS_NT_SYM_LINKS,    cd->SymLinks.Supported);
    ShowItem_Bool ( IDX_COMPRESS_NT_HARD_LINKS,   cd->HardLinks.Supported);
    ShowItem_Bool ( IDX_COMPRESS_NT_ALT_STREAMS,  cd->AltStreams.Supported);
    ShowItem_Bool ( IDX_COMPRESS_NT_SECUR,        cd->NtSecurity.Supported);

    ShowItem_Bool ( IDG_COMPRESS_NTFS,
           cd->SymLinks.Supported
        || cd->HardLinks.Supported
        || cd->AltStreams.Supported
        || cd->NtSecurity.Supported);
  }

   /* we read property from two sources:
       1) command line  : (Info)
       2) registry      : (m_RegistryInfo)
     (Info) has priority, if both are no defined */

  CheckButton_Bool1 ( IDX_COMPRESS_NT_SYM_LINKS,   cd->SymLinks);
  CheckButton_Bool1 ( IDX_COMPRESS_NT_HARD_LINKS,  cd->HardLinks);
  CheckButton_Bool1 ( IDX_COMPRESS_NT_ALT_STREAMS, cd->AltStreams);
  CheckButton_Bool1 ( IDX_COMPRESS_NT_SECUR,       cd->NtSecurity);

  CheckButton_Bool1 (IDX_COMPRESS_PRESERVE_ATIME, cd->PreserveATime);

  m_Prec.Attach (GetItem(IDC_COMPRESS_TIME_PREC));

  MTime.SetIDs ( IDX_COMPRESS_MTIME, IDX_COMPRESS_MTIME_SET);
  CTime.SetIDs ( IDX_COMPRESS_CTIME, IDX_COMPRESS_CTIME_SET);
  ATime.SetIDs ( IDX_COMPRESS_ATIME, IDX_COMPRESS_ATIME_SET);
  ZTime.SetIDs ( IDX_COMPRESS_ZTIME, IDX_COMPRESS_ZTIME_SET);

  {
    const NCompression::CFormatOptions &fo = cd->Get_FormatOptions();
    TimePrec = fo.TimePrec;
    MTime.BoolPair = fo.MTime;
    CTime.BoolPair = fo.CTime;
    ATime.BoolPair = fo.ATime;
    ZTime.BoolPair = fo.SetArcMTime;
  }
  
  SetPrec();

  NormalizePosition();

  return CModalDialog::OnInit();
}


bool COptionsDialog::OnCommand(int code, int itemID, LPARAM lParam)
{
  if (code == CBN_SELCHANGE)
  {
    switch (itemID)
    {
      case IDC_COMPRESS_TIME_PREC:
      {
        Store_TimeBoxes();
        SetTimeMAC(); // for zip/tar
        return true;
      }
    }
  }
  return CModalDialog::OnCommand(code, itemID, lParam);
}


bool COptionsDialog::OnButtonClicked(int buttonID, HWND buttonHWND)
{
  switch (buttonID)
  {
    case IDX_COMPRESS_PREC_SET:  { On_CheckBoxSet_Prec_Clicked(); return true; }
    case IDX_COMPRESS_MTIME_SET: { On_CheckBoxSet_Clicked (MTime); return true; }
    case IDX_COMPRESS_CTIME_SET: { On_CheckBoxSet_Clicked (CTime); return true; }
    case IDX_COMPRESS_ATIME_SET: { On_CheckBoxSet_Clicked (ATime); return true; }
    case IDX_COMPRESS_ZTIME_SET: { On_CheckBoxSet_Clicked (ZTime); return true; }
  }
  return CModalDialog::OnButtonClicked(buttonID, buttonHWND);
}


void COptionsDialog::OnOK()
{
  GetButton_Bool1 (IDX_COMPRESS_NT_SYM_LINKS,   cd->SymLinks);
  GetButton_Bool1 (IDX_COMPRESS_NT_HARD_LINKS,  cd->HardLinks);
  GetButton_Bool1 (IDX_COMPRESS_NT_ALT_STREAMS, cd->AltStreams);
  GetButton_Bool1 (IDX_COMPRESS_NT_SECUR,       cd->NtSecurity);
  GetButton_Bool1 (IDX_COMPRESS_PRESERVE_ATIME, cd->PreserveATime);

  Store_TimeBoxes();
  {
    NCompression::CFormatOptions &fo = cd->Get_FormatOptions();
    fo.TimePrec = TimePrec;
    fo.MTime = MTime.BoolPair;
    fo.CTime = CTime.BoolPair;
    fo.ATime = ATime.BoolPair;
    fo.SetArcMTime = ZTime.BoolPair;
  }

  CModalDialog::OnOK();
}

void COptionsDialog::OnHelp()
{
  ShowHelpWindow(kHelpTopic);
}
