// ContextMenu.cpp

#include "StdAfx.h"

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

#include "../../../Windows/COM.h"
#include "../../../Windows/DLL.h"
#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileFind.h"
#include "../../../Windows/FileName.h"
#include "../../../Windows/MemoryGlobal.h"
#include "../../../Windows/Menu.h"
#include "../../../Windows/ProcessUtils.h"
#include "../../../Windows/Shell.h"

#include "../../PropID.h"

#include "../Common/ArchiveName.h"
#include "../Common/CompressCall.h"
#include "../Common/ExtractingFilePath.h"
#include "../Common/ZipRegistry.h"

#include "../FileManager/FormatUtils.h"
#include "../FileManager/PropertyName.h"

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

#include "ContextMenu.h"
#include "ContextMenuFlags.h"
#include "MyMessages.h"

#include "resource.h"

// #define SHOW_DEBUG_CTX_MENU

#ifdef SHOW_DEBUG_CTX_MENU
#include <stdio.h>
#endif

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

#ifndef UNDER_CE
#define EMAIL_SUPPORT 1
#endif

extern LONG g_DllRefCount;

#ifdef _WIN32
extern HINSTANCE g_hInstance;
#endif
    
#ifdef SHOW_DEBUG_CTX_MENU

void Print_Ptr(void *p, char *s)
{
  char temp[64];
  ConvertUInt64ToHex((UInt64)(void *)p, temp);
  AString s2;
  s2 += temp;
  s2.Add_Space();
  s2 += s;
  OutputDebugStringA(s2);
}

void Print_Number(UInt32 number, char *s)
{
  char temp[64];
  ConvertUInt64ToString(number, temp);
  AString s2;
  s2 += temp;
  s2.Add_Space();
  s2 += s;
  OutputDebugStringA(s2);
}

#define ODS(sz) Print_Ptr(this, sz)
#define ODS_U(s) OutputDebugStringW(s);
// #define ODS(sz)
// #define ODS_U(s)

#define ODS2(sz) Print_Ptr(this, sz)

#else

#define Print_Number(number, s)
#define ODS(sz)
#define ODS_U(s)
#define ODS2(sz)

#endif



CZipContextMenu::CZipContextMenu():
   _isMenuForFM(false),
   _dropMode(false),
   _bitmap(NULL),
   IsSeparator(false),
   IsRoot(true),
   CurrentSubCommand(0)
{
  ODS("-- CZipContextMenu()");

  InterlockedIncrement(&g_DllRefCount);
  _bitmap = ::LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_MENU_LOGO));
}

CZipContextMenu::~CZipContextMenu()
{
  ODS("== ~CZipContextMenu");
  if (_bitmap != NULL)
    DeleteObject(_bitmap);
  InterlockedDecrement(&g_DllRefCount);
}

HRESULT CZipContextMenu::GetFileNames(LPDATAOBJECT dataObject, UStringVector &fileNames)
{
  fileNames.Clear();
  if (!dataObject)
    return E_INVALIDARG;

  #ifndef UNDER_CE
  
  FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  NCOM::CStgMedium stgMedium;
  HRESULT result = dataObject->GetData(&fmte, &stgMedium);
  if (result != S_OK)
    return result;
  stgMedium._mustBeReleased = true;

  NShell::CDrop drop(false);
  NMemory::CGlobalLock globalLock(stgMedium->hGlobal);
  drop.Attach((HDROP)globalLock.GetPointer());
  drop.QueryFileNames(fileNames);
  
  #endif

  return S_OK;
}

// IShellExtInit

STDMETHODIMP CZipContextMenu::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT dataObject, HKEY /* hkeyProgID */)
{
  // OutputDebugString(TEXT("::Initialize\r\n"));
  _dropMode = false;
  _dropPath.Empty();
  if (pidlFolder != 0)
  {
    #ifndef UNDER_CE
    if (NShell::GetPathFromIDList(pidlFolder, _dropPath))
    {
      // OutputDebugString(path);
      // OutputDebugString(TEXT("\r\n"));
      NName::NormalizeDirPathPrefix(_dropPath);
      _dropMode = !_dropPath.IsEmpty();
    }
    else
    #endif
      _dropPath.Empty();
  }

  /*
  m_IsFolder = false;
  if (pidlFolder == 0)
  */
  // pidlFolder is NULL :(
  return GetFileNames(dataObject, _fileNames);
}

HRESULT CZipContextMenu::InitContextMenu(const wchar_t * /* folder */, const wchar_t * const *names, unsigned numFiles)
{
  _isMenuForFM = true;
  _fileNames.Clear();
  for (UInt32 i = 0; i < numFiles; i++)
  {
    // MessageBoxW(0, names[i], NULL, 0);
    // OutputDebugStringW(names[i]);
    _fileNames.Add(names[i]);
  }
  _dropMode = false;
  return S_OK;
}


/////////////////////////////
// IContextMenu

static LPCSTR const kMainVerb = "SevenZip";
static LPCSTR const kOpenCascadedVerb = "SevenZip.OpenWithType.";
static LPCSTR const kCheckSumCascadedVerb = "SevenZip.Checksum";


struct CContextMenuCommand
{
  UInt32 flag;
  CZipContextMenu::ECommandInternalID CommandInternalID;
  LPCSTR Verb;
  UINT ResourceID;
};

#define CMD_REC(cns, verb, ids)  { NContextMenuFlags::cns, CZipContextMenu::cns, verb, ids }

static const CContextMenuCommand g_Commands[] =
{
  CMD_REC( kOpen,        "Open",        IDS_CONTEXT_OPEN),
  CMD_REC( kExtract,     "Extract",     IDS_CONTEXT_EXTRACT),
  CMD_REC( kExtractHere, "ExtractHere", IDS_CONTEXT_EXTRACT_HERE),
  CMD_REC( kExtractTo,   "ExtractTo",   IDS_CONTEXT_EXTRACT_TO),
  CMD_REC( kTest,        "Test",        IDS_CONTEXT_TEST),
  CMD_REC( kCompress,           "Compress",           IDS_CONTEXT_COMPRESS),
  CMD_REC( kCompressEmail,      "CompressEmail",      IDS_CONTEXT_COMPRESS_EMAIL),
  CMD_REC( kCompressTo7z,       "CompressTo7z",       IDS_CONTEXT_COMPRESS_TO),
  CMD_REC( kCompressTo7zEmail,  "CompressTo7zEmail",  IDS_CONTEXT_COMPRESS_TO_EMAIL),
  CMD_REC( kCompressToZip,      "CompressToZip",      IDS_CONTEXT_COMPRESS_TO),
  CMD_REC( kCompressToZipEmail, "CompressToZipEmail", IDS_CONTEXT_COMPRESS_TO_EMAIL)
};


struct CHashCommand
{
  CZipContextMenu::ECommandInternalID CommandInternalID;
  LPCSTR UserName;
  LPCSTR MethodName;
};

static const CHashCommand g_HashCommands[] =
{
  { CZipContextMenu::kHash_CRC32,  "CRC-32",  "CRC32" },
  { CZipContextMenu::kHash_CRC64,  "CRC-64",  "CRC64" },
  { CZipContextMenu::kHash_SHA1,   "SHA-1",   "SHA1" },
  { CZipContextMenu::kHash_SHA256, "SHA-256", "SHA256" },
  { CZipContextMenu::kHash_All,    "*",       "*" },
  { CZipContextMenu::kHash_Generate_SHA256, "SHA-256 -> file.sha256", "SHA256" },
  { CZipContextMenu::kHash_TestArc, "Checksum : Test", "Hash" }
};


static int FindCommand(CZipContextMenu::ECommandInternalID &id)
{
  for (unsigned i = 0; i < ARRAY_SIZE(g_Commands); i++)
    if (g_Commands[i].CommandInternalID == id)
      return i;
  return -1;
}


void CZipContextMenu::FillCommand(ECommandInternalID id, UString &mainString, CCommandMapItem &cmi)
{
  mainString.Empty();
  int i = FindCommand(id);
  if (i < 0)
    throw 201908;
  const CContextMenuCommand &command = g_Commands[(unsigned)i];
  cmi.CommandInternalID = command.CommandInternalID;
  cmi.Verb = kMainVerb;
  cmi.Verb += command.Verb;
  // cmi.HelpString = cmi.Verb;
  LangString(command.ResourceID, mainString);
  cmi.UserString = mainString;
  // return true;
}


static UString LangStringAlt(UInt32 id, const char *altString)
{
  UString s = LangString(id);
  if (s.IsEmpty())
    s = altString;
  return s;
}

  
void CZipContextMenu::AddCommand(ECommandInternalID id, UString &mainString, CCommandMapItem &cmi)
{
  FillCommand(id, mainString, cmi);
  _commandMap.Add(cmi);
}


static void MyInsertMenu(CMenu &menu, int pos, UINT id, const UString &s, HBITMAP bitmap)
{
  if (!menu)
    return;
  CMenuItem mi;
  mi.fType = MFT_STRING;
  mi.fMask = MIIM_TYPE | MIIM_ID;
  if (bitmap)
    mi.fMask |= MIIM_CHECKMARKS;
  mi.wID = id;
  mi.StringValue = s;
  mi.hbmpUnchecked = bitmap;
  // mi.hbmpChecked = bitmap; // do we need hbmpChecked ???
  if (!menu.InsertItem(pos, true, mi))
    throw 20190816;

  // SetMenuItemBitmaps also works
  // ::SetMenuItemBitmaps(menu, pos, MF_BYPOSITION, bitmap, NULL);
}


static void MyAddSubMenu(
    CObjectVector<CZipContextMenu::CCommandMapItem> &_commandMap,
    const char *verb,
    CMenu &menu, int pos, UINT id, const UString &s, HMENU hSubMenu, HBITMAP bitmap)
{
  CZipContextMenu::CCommandMapItem cmi;
  cmi.CommandInternalID = CZipContextMenu::kCommandNULL;
  cmi.Verb = verb;
  cmi.IsPopup = true;
  // cmi.HelpString = verb;
  cmi.UserString = s;
  _commandMap.Add(cmi);

  if (!menu)
    return;

  CMenuItem mi;
  mi.fType = MFT_STRING;
  mi.fMask = MIIM_SUBMENU | MIIM_TYPE | MIIM_ID;
  if (bitmap)
    mi.fMask |= MIIM_CHECKMARKS;
  mi.wID = id;
  mi.hSubMenu = hSubMenu;
  mi.hbmpUnchecked = bitmap;
  
  mi.StringValue = s;
  if (!menu.InsertItem(pos, true, mi))
    throw 20190817;
}


static const char * const kArcExts[] =
{
    "7z"
  , "bz2"
  , "gz"
  , "rar"
  , "zip"
};

static bool IsItArcExt(const UString &ext)
{
  for (unsigned i = 0; i < ARRAY_SIZE(kArcExts); i++)
    if (ext.IsEqualTo_Ascii_NoCase(kArcExts[i]))
      return true;
  return false;
}

UString GetSubFolderNameForExtract(const UString &arcName);
UString GetSubFolderNameForExtract(const UString &arcName)
{
  int dotPos = arcName.ReverseFind_Dot();
  if (dotPos < 0)
    return Get_Correct_FsFile_Name(arcName) + L'~';

  const UString ext = arcName.Ptr(dotPos + 1);
  UString res = arcName.Left(dotPos);
  res.TrimRight();
  dotPos = res.ReverseFind_Dot();
  if (dotPos > 0)
  {
    const UString ext2 = res.Ptr(dotPos + 1);
    if ((ext.IsEqualTo_Ascii_NoCase("001") && IsItArcExt(ext2))
        || (ext.IsEqualTo_Ascii_NoCase("rar") &&
          (  ext2.IsEqualTo_Ascii_NoCase("part001")
          || ext2.IsEqualTo_Ascii_NoCase("part01")
          || ext2.IsEqualTo_Ascii_NoCase("part1"))))
      res.DeleteFrom(dotPos);
    res.TrimRight();
  }
  return Get_Correct_FsFile_Name(res);
}

static void ReduceString(UString &s)
{
  const unsigned kMaxSize = 64;
  if (s.Len() <= kMaxSize)
    return;
  s.Delete(kMaxSize / 2, s.Len() - kMaxSize);
  s.Insert(kMaxSize / 2, L" ... ");
}

static UString GetQuotedReducedString(const UString &s)
{
  UString s2 = s;
  ReduceString(s2);
  s2.Replace(L"&", L"&&");
  return GetQuotedString(s2);
}

static void MyFormatNew_ReducedName(UString &s, const UString &name)
{
  s = MyFormatNew(s, GetQuotedReducedString(name));
}

static const char * const kExtractExcludeExtensions =
  " 3gp"
  " aac ans ape asc asm asp aspx avi awk"
  " bas bat bmp"
  " c cs cls clw cmd cpp csproj css ctl cxx"
  " def dep dlg dsp dsw"
  " eps"
  " f f77 f90 f95 fla flac frm"
  " gif"
  " h hpp hta htm html hxx"
  " ico idl inc ini inl"
  " java jpeg jpg js"
  " la lnk log"
  " mak manifest wmv mov mp3 mp4 mpe mpeg mpg m4a"
  " ofr ogg"
  " pac pas pdf php php3 php4 php5 phptml pl pm png ps py pyo"
  " ra rb rc reg rka rm rtf"
  " sed sh shn shtml sln sql srt swa"
  " tcl tex tiff tta txt"
  " vb vcproj vbs"
  " wav wma wv"
  " xml xsd xsl xslt"
  " ";

/*
static const char * const kNoOpenAsExtensions =
  " 7z arj bz2 cab chm cpio flv gz lha lzh lzma rar swm tar tbz2 tgz wim xar xz z zip ";
*/

static const char * const kOpenTypes[] =
{
    ""
  , "*"
  , "#"
  , "#:e"
  // , "#:a"
  , "7z"
  , "zip"
  , "cab"
  , "rar"
};

static bool FindExt(const char *p, const FString &name)
{
  int dotPos = name.ReverseFind_Dot();
  if (dotPos < 0 || dotPos == (int)name.Len() - 1)
    return false;

  AString s;
  
  for (unsigned pos = dotPos + 1;; pos++)
  {
    wchar_t c = name[pos];
    if (c == 0)
      break;
    if (c >= 0x80)
      return false;
    s += (char)MyCharLower_Ascii((char)c);
  }
  
  for (unsigned i = 0; p[i] != 0;)
  {
    unsigned j;
    for (j = i; p[j] != ' '; j++);
    if (s.Len() == j - i && memcmp(p + i, (const char *)s, s.Len()) == 0)
      return true;
    i = j + 1;
  }
  
  return false;
}

static bool DoNeedExtract(const FString &name)
{
  return !FindExt(kExtractExcludeExtensions, name);
}

// we must use diferent Verbs for Popup subMenu.
void CZipContextMenu::AddMapItem_ForSubMenu(const char *verb)
{
  CCommandMapItem cmi;
  cmi.CommandInternalID = kCommandNULL;
  cmi.Verb = verb;
  // cmi.HelpString = verb;
  _commandMap.Add(cmi);
}


static HRESULT RETURN_WIN32_LastError_AS_HRESULT()
{
  DWORD lastError = ::GetLastError();
  if (lastError == 0)
    return E_FAIL;
  return HRESULT_FROM_WIN32(lastError);
}


/*
  we add CCommandMapItem to _commandMap for each new Menu ID.
  so then we use _commandMap[offset].
  That way we can execute commands that have menu item.
  Another non-implemented way:
    We can return the number off all possible commands in QueryContextMenu().
    so the caller could call InvokeCommand() via string verb even
    without using menu items.
*/
    


STDMETHODIMP CZipContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu,
      UINT commandIDFirst, UINT commandIDLast, UINT flags)
{
  ODS("+ QueryContextMenu()");
  COM_TRY_BEGIN
  try {

  #ifdef SHOW_DEBUG_CTX_MENU
  { char s[256]; sprintf(s, "QueryContextMenu: index=%d first=%d last=%d flags=%x _files=%d",
    indexMenu, commandIDFirst, commandIDLast, flags, _fileNames.Size()); OutputDebugStringA(s); }

  /*
  for (UInt32 i = 0; i < _fileNames.Size(); i++)
  {
    OutputDebugStringW(_fileNames[i]);
  }
  */
  #endif

  LoadLangOneTime();
  
  if (_fileNames.Size() == 0)
  {
    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);
    // return E_INVALIDARG;
  }

  if (commandIDFirst > commandIDLast)
    return E_INVALIDARG;


  UINT currentCommandID = commandIDFirst;
  
  if ((flags & 0x000F) != CMF_NORMAL
      && (flags & CMF_VERBSONLY) == 0
      && (flags & CMF_EXPLORE) == 0)
    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, currentCommandID - commandIDFirst);
  // return MAKE_HRESULT(SEVERITY_SUCCESS, 0, currentCommandID);
  // 19.01 : we changed from (currentCommandID) to (currentCommandID - commandIDFirst)
  // why it was so before?

  _commandMap.Clear();

  CMenu popupMenu;
  CMenuDestroyer menuDestroyer;

  CContextMenuInfo ci;
  ci.Load();

  _elimDup = ci.ElimDup;

  HBITMAP bitmap = NULL;
  if (ci.MenuIcons.Val)
    bitmap = _bitmap;

  UINT subIndex = indexMenu;

  ODS("### 50");
  
  if (ci.Cascaded.Val)
  {
    if (hMenu)
    if (!popupMenu.CreatePopup())
      return RETURN_WIN32_LastError_AS_HRESULT();
    menuDestroyer.Attach(popupMenu);

    /* 9.31: we commented the following code. Probably we don't need.
    Check more systems. Maybe it was for old Windows? */
    /*
    AddMapItem_ForSubMenu();
    currentCommandID++;
    */
    subIndex = 0;
  }
  else
  {
    popupMenu.Attach(hMenu);
    CMenuItem mi;
    mi.fType = MFT_SEPARATOR;
    mi.fMask = MIIM_TYPE;
    if (hMenu)
    popupMenu.InsertItem(subIndex++, true, mi);
  }

  UInt32 contextMenuFlags = ci.Flags;

  NFind::CFileInfo fi0;
  FString folderPrefix;
  
  if (_fileNames.Size() > 0)
  {
    const UString &fileName = _fileNames.Front();

    #if defined(_WIN32) && !defined(UNDER_CE)
    if (NName::IsDevicePath(us2fs(fileName)))
    {
      // CFileInfo::Find can be slow for device files. So we don't call it.
      // we need only name here.
      fi0.Name = us2fs(fileName.Ptr(NName::kDevicePathPrefixSize)); // change it 4 - must be constant
      folderPrefix =
        #ifdef UNDER_CE
          "\\";
        #else
          "C:\\";
        #endif
    }
    else
    #endif
    {
      if (!fi0.Find(us2fs(fileName)))
      {
        throw 20190820;
        // return RETURN_WIN32_LastError_AS_HRESULT();
      }
      GetOnlyDirPrefix(us2fs(fileName), folderPrefix);
    }
  }

  ODS("### 100");

  UString mainString;
  
  if (_fileNames.Size() == 1 && currentCommandID + 14 <= commandIDLast)
  {
    if (!fi0.IsDir() && DoNeedExtract(fi0.Name))
    {
      // Open
      bool thereIsMainOpenItem = ((contextMenuFlags & NContextMenuFlags::kOpen) != 0);
      if (thereIsMainOpenItem)
      {
        CCommandMapItem cmi;
        AddCommand(kOpen, mainString, cmi);
        MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
      }
      if ((contextMenuFlags & NContextMenuFlags::kOpenAs) != 0
          // && (!thereIsMainOpenItem || !FindExt(kNoOpenAsExtensions, fi0.Name))
          && hMenu // we want to reduce number of menu items below 16
          )
      {
        CMenu subMenu;
        if (!hMenu || subMenu.CreatePopup())
        {
          MyAddSubMenu(_commandMap, kOpenCascadedVerb, popupMenu, subIndex++, currentCommandID++, LangString(IDS_CONTEXT_OPEN), subMenu, bitmap);
          _commandMap.Back().CtxCommandType = CtxCommandType_OpenRoot;

          UINT subIndex2 = 0;
          for (unsigned i = (thereIsMainOpenItem ? 1 : 0); i < ARRAY_SIZE(kOpenTypes); i++)
          {
            CCommandMapItem cmi;
            if (i == 0)
              FillCommand(kOpen, mainString, cmi);
            else
            {
              mainString = kOpenTypes[i];
              cmi.CommandInternalID = kOpen;
              cmi.Verb = kMainVerb;
              cmi.Verb += ".Open.";
              cmi.Verb += mainString;
              // cmi.HelpString = cmi.Verb;
              cmi.ArcType = mainString;
              cmi.CtxCommandType = CtxCommandType_OpenChild;
            }
            _commandMap.Add(cmi);
            Set_UserString_in_LastCommand(mainString);
            MyInsertMenu(subMenu, subIndex2++, currentCommandID++, mainString, bitmap);
          }

          subMenu.Detach();
        }
      }
    }
  }

  if (_fileNames.Size() > 0 && currentCommandID + 10 <= commandIDLast)
  {
    bool needExtract = (!fi0.IsDir() && DoNeedExtract(fi0.Name));
    
    if (!needExtract)
    {
      for (unsigned i = 1; i < _fileNames.Size(); i++)
      {
        NFind::CFileInfo fi;
        if (!fi.Find(us2fs(_fileNames[i])))
        {
          throw 20190821;
          // return RETURN_WIN32_LastError_AS_HRESULT();
        }
        if (!fi.IsDir() && DoNeedExtract(fi.Name))
        {
          needExtract = true;
          break;
        }
      }
    }
    
    // const UString &fileName = _fileNames.Front();
    
    if (needExtract)
    {
      {
        UString baseFolder = fs2us(folderPrefix);
        if (_dropMode)
          baseFolder = _dropPath;
    
        UString specFolder ('*');
        if (_fileNames.Size() == 1)
          specFolder = GetSubFolderNameForExtract(fs2us(fi0.Name));
        specFolder.Add_PathSepar();

        if ((contextMenuFlags & NContextMenuFlags::kExtract) != 0)
        {
          // Extract
          CCommandMapItem cmi;
          cmi.Folder = baseFolder + specFolder;
          AddCommand(kExtract, mainString, cmi);
          MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
        }

        if ((contextMenuFlags & NContextMenuFlags::kExtractHere) != 0)
        {
          // Extract Here
          CCommandMapItem cmi;
          cmi.Folder = baseFolder;
          AddCommand(kExtractHere, mainString, cmi);
          MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
        }

        if ((contextMenuFlags & NContextMenuFlags::kExtractTo) != 0)
        {
          // Extract To
          CCommandMapItem cmi;
          UString s;
          cmi.Folder = baseFolder + specFolder;
          AddCommand(kExtractTo, s, cmi);
          MyFormatNew_ReducedName(s, specFolder);
          Set_UserString_in_LastCommand(s);
          MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
        }
      }

      if ((contextMenuFlags & NContextMenuFlags::kTest) != 0)
      {
        // Test
        CCommandMapItem cmi;
        AddCommand(kTest, mainString, cmi);
        MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
      }
    }
    
    const UString arcName = CreateArchiveName(_fileNames, _fileNames.Size() == 1 ? &fi0 : NULL);

    UString arcName7z = arcName;
    arcName7z += ".7z";
    UString arcNameZip = arcName;
    arcNameZip += ".zip";

    // Compress
    if ((contextMenuFlags & NContextMenuFlags::kCompress) != 0)
    {
      CCommandMapItem cmi;
      if (_dropMode)
        cmi.Folder = _dropPath;
      else
        cmi.Folder = fs2us(folderPrefix);
      cmi.ArcName = arcName;
      AddCommand(kCompress, mainString, cmi);
      MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
    }

    #ifdef EMAIL_SUPPORT
    // CompressEmail
    if ((contextMenuFlags & NContextMenuFlags::kCompressEmail) != 0 && !_dropMode)
    {
      CCommandMapItem cmi;
      cmi.ArcName = arcName;
      AddCommand(kCompressEmail, mainString, cmi);
      MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
    }
    #endif

    // CompressTo7z
    if (contextMenuFlags & NContextMenuFlags::kCompressTo7z &&
        !arcName7z.IsEqualTo_NoCase(fs2us(fi0.Name)))
    {
      CCommandMapItem cmi;
      UString s;
      if (_dropMode)
        cmi.Folder = _dropPath;
      else
        cmi.Folder = fs2us(folderPrefix);
      cmi.ArcName = arcName7z;
      cmi.ArcType = "7z";
      AddCommand(kCompressTo7z, s, cmi);
      MyFormatNew_ReducedName(s, arcName7z);
      Set_UserString_in_LastCommand(s);
      MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
    }

    #ifdef EMAIL_SUPPORT
    // CompressTo7zEmail
    if ((contextMenuFlags & NContextMenuFlags::kCompressTo7zEmail) != 0  && !_dropMode)
    {
      CCommandMapItem cmi;
      UString s;
      cmi.ArcName = arcName7z;
      cmi.ArcType = "7z";
      AddCommand(kCompressTo7zEmail, s, cmi);
      MyFormatNew_ReducedName(s, arcName7z);
      Set_UserString_in_LastCommand(s);
      MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
    }
    #endif

    // CompressToZip
    if (contextMenuFlags & NContextMenuFlags::kCompressToZip &&
        !arcNameZip.IsEqualTo_NoCase(fs2us(fi0.Name)))
    {
      CCommandMapItem cmi;
      UString s;
      if (_dropMode)
        cmi.Folder = _dropPath;
      else
        cmi.Folder = fs2us(folderPrefix);
      cmi.ArcName = arcNameZip;
      cmi.ArcType = "zip";
      AddCommand(kCompressToZip, s, cmi);
      MyFormatNew_ReducedName(s, arcNameZip);
      Set_UserString_in_LastCommand(s);
      MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
    }

    #ifdef EMAIL_SUPPORT
    // CompressToZipEmail
    if ((contextMenuFlags & NContextMenuFlags::kCompressToZipEmail) != 0  && !_dropMode)
    {
      CCommandMapItem cmi;
      UString s;
      cmi.ArcName = arcNameZip;
      cmi.ArcType = "zip";
      AddCommand(kCompressToZipEmail, s, cmi);
      MyFormatNew_ReducedName(s, arcNameZip);
      Set_UserString_in_LastCommand(s);
      MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
    }
    #endif
  }


  // don't use InsertMenu:  See MSDN:
  // PRB: Duplicate Menu Items In the File Menu For a Shell Context Menu Extension
  // ID: Q214477
  
  if (ci.Cascaded.Val)
  {
    CMenu menu;
    menu.Attach(hMenu);
    menuDestroyer.Disable();
    MyAddSubMenu(_commandMap, kMainVerb, menu, indexMenu++, currentCommandID++, (UString)"7-Zip",
        popupMenu, // popupMenu.Detach(),
        bitmap);
  }
  else
  {
    // popupMenu.Detach();
    indexMenu = subIndex;
  }

  const bool needCrc = ((contextMenuFlags &
      (NContextMenuFlags::kCRC |
       NContextMenuFlags::kCRC_Cascaded)) != 0);
  
  if (
      // !_isMenuForFM && // 21.04: we don't hide CRC SHA menu in 7-Zip FM
      needCrc
      && currentCommandID + 1 < commandIDLast)
  {
    CMenu subMenu;
    // CMenuDestroyer menuDestroyer_CRC;

    UINT subIndex_CRC = 0;
    
    if (!hMenu || subMenu.CreatePopup())
    {
      // menuDestroyer_CRC.Attach(subMenu);
      const bool insertHashMenuTo7zipMenu = (ci.Cascaded.Val
          && (contextMenuFlags & NContextMenuFlags::kCRC_Cascaded) != 0);
      
      CMenu menu;
      {
        int indexInParent;
        if (insertHashMenuTo7zipMenu)
        {
          indexInParent = subIndex;
          menu.Attach(popupMenu);
        }
        else
        {
          indexInParent = indexMenu;
          menu.Attach(hMenu);
          // menuDestroyer_CRC.Disable();
        }
        MyAddSubMenu(_commandMap, kCheckSumCascadedVerb, menu, indexInParent++, currentCommandID++, (UString)"CRC SHA", subMenu,
          /* insertHashMenuTo7zipMenu ? NULL : */ bitmap);
        _commandMap.Back().CtxCommandType = CtxCommandType_CrcRoot;
        if (!insertHashMenuTo7zipMenu)
          indexMenu = indexInParent;
      }

      for (unsigned i = 0; i < ARRAY_SIZE(g_HashCommands); i++)
      {
        if (currentCommandID >= commandIDLast)
          break;
        const CHashCommand &hc = g_HashCommands[i];
        CCommandMapItem cmi;
        cmi.CommandInternalID = hc.CommandInternalID;
        cmi.Verb = kCheckSumCascadedVerb;
        cmi.Verb += '.';
        cmi.Verb += hc.MethodName;
        UString s;
        s += hc.UserName;
        
        if (hc.CommandInternalID == kHash_Generate_SHA256)
        {
          {
            popupMenu.Attach(hMenu);
            CMenuItem mi;
            mi.fType = MFT_SEPARATOR;
            mi.fMask = MIIM_TYPE;
            subMenu.InsertItem(subIndex_CRC++, true, mi);
          }

          UString name;
          if (_fileNames.Size() > 1)
            name = CreateArchiveName(_fileNames, _fileNames.Size() == 1 ? &fi0 : NULL);
          else
            name = fs2us(fi0.Name);
          name += ".sha256";
          cmi.Folder= folderPrefix;
          cmi.ArcName = name;
          s = "SHA-256 -> ";
          s += name;
        }
        else if (hc.CommandInternalID == kHash_TestArc)
        {
          s = LangStringAlt(IDS_CONTEXT_TEST, "Test archive");
          s += " : ";
          s += GetNameOfProperty(kpidChecksum, UString("Checksum"));
        }

        // cmi.HelpString = cmi.Verb;
        cmi.UserString = s;
        cmi.CtxCommandType = CtxCommandType_CrcChild;
        _commandMap.Add(cmi);
        MyInsertMenu(subMenu, subIndex_CRC++, currentCommandID++, s, bitmap);
      }
      
      subMenu.Detach();
    }
  }

  popupMenu.Detach();

  /*
  if (!ci.Cascaded.Val)
    indexMenu = subIndex;
  */

  ODS("### 400");

  #ifdef SHOW_DEBUG_CTX_MENU
  { char s[256]; sprintf(s, "Commands=%d currentCommandID - commandIDFirst = %d",
    _commandMap.Size(), currentCommandID - commandIDFirst); OutputDebugStringA(s); }
  #endif

  if (_commandMap.Size() != currentCommandID - commandIDFirst)
    throw 20190818;
  return MAKE_HRESULT(SEVERITY_SUCCESS, 0, currentCommandID - commandIDFirst);

  }
  catch(...)
  {
    /* we added some menu items already : num_added_menu_items,
       So we MUST return (number_of_defined_ids), where (number_of_defined_ids >= num_added_menu_items)
       This will prevent incorrect menu working, when same IDs can be
       assigned in multiple menu items from different subhandlers.
       And we must add items to _commandMap before adding to menu.
     */
    #ifdef SHOW_DEBUG_CTX_MENU
    { char s[256]; sprintf(s, "catch() exception: Commands=%d",
      _commandMap.Size()); OutputDebugStringA(s); }
    #endif
    // if (_commandMap.Size() != 0)
      return MAKE_HRESULT(SEVERITY_SUCCESS, 0, _commandMap.Size());
    // throw;
  }
  COM_TRY_END
}


int CZipContextMenu::FindVerb(const UString &verb)
{
  FOR_VECTOR (i, _commandMap)
    if (_commandMap[i].Verb == verb)
      return i;
  return -1;
}

static UString Get7zFmPath()
{
  return fs2us(NWindows::NDLL::GetModuleDirPrefix()) + L"7zFM.exe";
}


#ifdef UNDER_CE
  #define MY__IS_INTRESOURCE(_r) ((((ULONG_PTR)(_r)) >> 16) == 0)
#else
  #define MY__IS_INTRESOURCE(_r) IS_INTRESOURCE(_r)
#endif


#ifdef SHOW_DEBUG_CTX_MENU
static void PrintStringA(const char *name, LPCSTR ptr)
{
  AString m;
  m += name;
  m += ": ";
  char s[32];
  sprintf(s, "%p", ptr);
  m += s;
  if (!MY__IS_INTRESOURCE(ptr))
  {
    m += ": \"";
    m += ptr;
    m += "\"";
  }
  OutputDebugStringA(m);
}

#if !defined(UNDER_CE)
static void PrintStringW(const char *name, LPCWSTR ptr)
{
  UString m;
  m += name;
  m += ": ";
  char s[32];
  sprintf(s, "%p", ptr);
  m += s;
  if (!MY__IS_INTRESOURCE(ptr))
  {
    m += ": \"";
    m += ptr;
    m += "\"";
  }
  OutputDebugStringW(m);
}
#endif
#endif


STDMETHODIMP CZipContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO commandInfo)
{
  COM_TRY_BEGIN

  #ifdef SHOW_DEBUG_CTX_MENU

  { char s[1280]; sprintf(s,
      #ifdef _WIN64
      "64"
      #else
      "32"
      #endif
      ": InvokeCommand: cbSize=%d flags=%x "
    , commandInfo->cbSize, commandInfo->fMask); OutputDebugStringA(s); }

    PrintStringA("Verb", commandInfo->lpVerb);
    PrintStringA("Parameters", commandInfo->lpParameters);
    PrintStringA("Directory", commandInfo->lpDirectory);
  #endif

  int commandOffset = -1;

  // xp64 / Win10 : explorer.exe sends 0 in lpVerbW
  // MSDN: if (IS_INTRESOURCE(lpVerbW)), we must use LOWORD(lpVerb) as sommand offset

  // FIXME: MINGW doesn't define CMINVOKECOMMANDINFOEX
  #if !defined(UNDER_CE) /* && defined(_MSC_VER) */
  bool unicodeVerb = false;
  if (commandInfo->cbSize == sizeof(CMINVOKECOMMANDINFOEX) &&
      (commandInfo->fMask & CMIC_MASK_UNICODE) != 0)
  {
    LPCMINVOKECOMMANDINFOEX commandInfoEx = (LPCMINVOKECOMMANDINFOEX)commandInfo;
    if (!MY__IS_INTRESOURCE(commandInfoEx->lpVerbW))
    {
      unicodeVerb = true;
      commandOffset = FindVerb(commandInfoEx->lpVerbW);
    }
    
    #ifdef SHOW_DEBUG_CTX_MENU
    PrintStringW("VerbW", commandInfoEx->lpVerbW);
    PrintStringW("ParametersW", commandInfoEx->lpParametersW);
    PrintStringW("DirectoryW", commandInfoEx->lpDirectoryW);
    PrintStringW("TitleW", commandInfoEx->lpTitleW);
    PrintStringA("Title", commandInfoEx->lpTitle);
    #endif
  }
  if (!unicodeVerb)
  #endif
  {
    #ifdef SHOW_DEBUG_CTX_MENU
    OutputDebugStringA("use non-UNICODE verb");
    #endif
    // if (HIWORD(commandInfo->lpVerb) == 0)
    if (MY__IS_INTRESOURCE(commandInfo->lpVerb))
      commandOffset = LOWORD(commandInfo->lpVerb);
    else
      commandOffset = FindVerb(GetUnicodeString(commandInfo->lpVerb));
  }

  #ifdef SHOW_DEBUG_CTX_MENU
  { char s[128]; sprintf(s, "commandOffset=%d",
      commandOffset); OutputDebugStringA(s); }
  #endif

  if (commandOffset < 0 || (unsigned)commandOffset >= _commandMap.Size())
    return E_INVALIDARG;
  const CCommandMapItem &cmi = _commandMap[(unsigned)commandOffset];
  return InvokeCommandCommon(cmi);
  COM_TRY_END
}


HRESULT CZipContextMenu::InvokeCommandCommon(const CCommandMapItem &cmi)
{
  const ECommandInternalID cmdID = cmi.CommandInternalID;

  try
  {
    switch (cmdID)
    {
      case kOpen:
      {
        UString params;
        params = GetQuotedString(_fileNames[0]);
        if (!cmi.ArcType.IsEmpty())
        {
          params += " -t";
          params += cmi.ArcType;
        }
        MyCreateProcess(Get7zFmPath(), params);
        break;
      }
      case kExtract:
      case kExtractHere:
      case kExtractTo:
      {
        ExtractArchives(_fileNames, cmi.Folder,
            (cmdID == kExtract), // showDialog
            (cmdID == kExtractTo) && _elimDup.Val // elimDup
            );
        break;
      }
      case kTest:
      {
        TestArchives(_fileNames);
        break;
      }
      case kCompress:
      case kCompressEmail:
      case kCompressTo7z:
      case kCompressTo7zEmail:
      case kCompressToZip:
      case kCompressToZipEmail:
      {
        const bool email =
            (cmdID == kCompressEmail) ||
            (cmdID == kCompressTo7zEmail) ||
            (cmdID == kCompressToZipEmail);
        const bool showDialog =
            (cmdID == kCompress) ||
            (cmdID == kCompressEmail);
        const bool addExtension = (cmdID == kCompress || cmdID == kCompressEmail);
        CompressFiles(cmi.Folder,
            cmi.ArcName, cmi.ArcType,
            addExtension,
            _fileNames, email, showDialog,
            false // waitFinish
            );
        break;
      }
      
      case kHash_CRC32:
      case kHash_CRC64:
      case kHash_SHA1:
      case kHash_SHA256:
      case kHash_All:
      case kHash_Generate_SHA256:
      case kHash_TestArc:
      {
        for (unsigned i = 0; i < ARRAY_SIZE(g_HashCommands); i++)
        {
          const CHashCommand &hc = g_HashCommands[i];
          if (hc.CommandInternalID == cmdID)
          {
            if (cmdID == kHash_TestArc)
            {
              TestArchives(_fileNames, true); // hashMode
              break;
            }
            UString generateName;
            if (cmdID == kHash_Generate_SHA256)
              generateName = cmi.ArcName;
            CalcChecksum(_fileNames, (UString)hc.MethodName,
                cmi.Folder, generateName);
            break;
          }
        }
        break;
      }
      case kCommandNULL:
        break;
    }
  }
  catch(...)
  {
    ::MessageBoxW(0, L"Error", L"7-Zip", MB_ICONERROR);
  }
  return S_OK;
}



static void MyCopyString(void *dest, const UString &src, bool writeInUnicode, UINT size)
{
  if (size != 0)
    size--;
  if (writeInUnicode)
  {
    UString s = src;
    s.DeleteFrom(size);
    MyStringCopy((wchar_t *)dest, s);
  }
  else
  {
    AString s = GetAnsiString(src);
    s.DeleteFrom(size);
    MyStringCopy((char *)dest, s);
  }
}


STDMETHODIMP CZipContextMenu::GetCommandString(UINT_PTR commandOffset, UINT uType,
    UINT * /* pwReserved */ , LPSTR pszName, UINT cchMax)
{
  COM_TRY_BEGIN

  const int cmdOffset = (int)commandOffset;
  
  #ifdef SHOW_DEBUG_CTX_MENU
  { char s[256]; sprintf(s, "GetCommandString: cmdOffset=%d uType=%d cchMax = %d",
      cmdOffset, uType, cchMax); OutputDebugStringA(s); }
  #endif

  if (uType == GCS_VALIDATEA || uType == GCS_VALIDATEW)
  {
    if (cmdOffset < 0 || (unsigned)cmdOffset >= _commandMap.Size())
      return S_FALSE;
    else
      return S_OK;
  }

  if (cmdOffset < 0 || (unsigned)cmdOffset >= _commandMap.Size())
  {
    #ifdef SHOW_DEBUG_CTX_MENU
    OutputDebugStringA("---------------- cmdOffset: E_INVALIDARG");
    #endif
    return E_INVALIDARG;
  }

  const CCommandMapItem &cmi = _commandMap[(unsigned)cmdOffset];

  if (uType == GCS_HELPTEXTA || uType == GCS_HELPTEXTW)
  {
    // we can return "Verb" here for debug purposes.
    // HelpString;
    MyCopyString(pszName, cmi.Verb, uType == GCS_HELPTEXTW, cchMax);
    return S_OK;
  }

  if (uType == GCS_VERBA || uType == GCS_VERBW)
  {
    MyCopyString(pszName, cmi.Verb, uType == GCS_VERBW, cchMax);
    return S_OK;
  }
  
  return E_INVALIDARG;
  
  COM_TRY_END
}



// ---------- IExplorerCommand ----------

static HRESULT WINAPI My_SHStrDupW(LPCWSTR src, LPWSTR *dest)
{
  if (src)
  {
    const SIZE_T size = (wcslen(src) + 1) * sizeof(WCHAR);
    WCHAR *p = (WCHAR *)CoTaskMemAlloc(size);
    if (p)
    {
      memcpy(p, src, size);
      *dest = p;
      return S_OK;
    }
  }
  *dest = NULL;
  return E_OUTOFMEMORY;
}


#define CZipExplorerCommand CZipContextMenu

class CCoTaskWSTR
{
  LPWSTR m_str;
  CLASS_NO_COPY(CCoTaskWSTR)
public:
  CCoTaskWSTR(): m_str(NULL) {}
  ~CCoTaskWSTR() { ::CoTaskMemFree(m_str); }
  LPWSTR* operator&() { return &m_str; }
  operator LPCWSTR () const { return m_str; }
  // operator LPCOLESTR() const { return m_str; }
  operator bool() const { return m_str != NULL; }
  // bool operator!() const { return m_str == NULL; }

  /*
  void Wipe_and_Free()
  {
    if (m_str)
    {
      memset(m_str, 0, ::SysStringLen(m_str) * sizeof(*m_str));
      Empty();
    }
  }
  */

private:
  /*
  CCoTaskWSTR(LPCOLESTR src) { m_str = ::CoTaskMemAlloc(src); }
  
  CCoTaskWSTR& operator=(LPCOLESTR src)
  {
    ::CoTaskMemFree(m_str);
    m_str = ::SysAllocString(src);
    return *this;
  }
 

  void Empty()
  {
    ::CoTaskMemFree(m_str);
    m_str = NULL;
  }
  */
};

static void LoadPaths(IShellItemArray *psiItemArray, UStringVector &paths)
{
  if (psiItemArray)
  {
    DWORD numItems = 0;
    if (psiItemArray->GetCount(&numItems) == S_OK)
    {
      for (DWORD i = 0; i < numItems; i++)
      {
        CMyComPtr<IShellItem> item;
        if (psiItemArray->GetItemAt(i, &item) == S_OK && item)
        {
          CCoTaskWSTR displayName;
          if (item->GetDisplayName(SIGDN_FILESYSPATH, &displayName) == S_OK
              && (bool)displayName)
          {
            OutputDebugStringW(displayName);
            paths.Add((LPCWSTR)displayName);
          }
        }
      }
    }
  }
}


void CZipExplorerCommand::LoadItems(IShellItemArray *psiItemArray)
{
  SubCommands.Clear();

  UStringVector paths;
  LoadPaths(psiItemArray, paths);
  _fileNames = paths;

  HRESULT res = QueryContextMenu(
      NULL, // hMenu,
      0, // indexMenu,
      0, // commandIDFirst,
      0 + 999, // commandIDLast,
      CMF_NORMAL);

  if (FAILED(res))
    return /* res */;


  CZipExplorerCommand *crcHandler = NULL;
  CZipExplorerCommand *openHandler = NULL;

  bool useCascadedCrc = true; // false;
  bool useCascadedOpen = true; // false;

  for (unsigned i = 0; i < _commandMap.Size(); i++)
  {
    const CCommandMapItem &cmi = _commandMap[i];


    if (cmi.IsPopup)
      if (!cmi.IsSubMenu())
        continue;

    // if (cmi.IsSubMenu()) continue // for debug
      
    CZipContextMenu *shellExt = new CZipContextMenu();
    shellExt->IsRoot = false;

    if (cmi.CtxCommandType == CtxCommandType_CrcRoot && !useCascadedCrc)
      shellExt->IsSeparator = true;

    {
      CZipExplorerCommand *handler = this;
      if (cmi.CtxCommandType == CtxCommandType_CrcChild && crcHandler)
        handler = crcHandler;
      else if (cmi.CtxCommandType == CtxCommandType_OpenChild && openHandler)
        handler = openHandler;
      handler->SubCommands.AddNew() = shellExt;
    }

    shellExt->_commandMap_Cur.Add(cmi);

    ODS_U(cmi.UserString);

    if (cmi.CtxCommandType == CtxCommandType_CrcRoot && useCascadedCrc)
      crcHandler = shellExt;
    if (cmi.CtxCommandType == CtxCommandType_OpenRoot && useCascadedOpen)
    {
      // ODS2("cmi.CtxCommandType == CtxCommandType_OpenRoot");
      openHandler = shellExt;
    }
  }
}


STDMETHODIMP CZipExplorerCommand::GetTitle(IShellItemArray *psiItemArray, LPWSTR *ppszName)
{
  ODS("- GetTitle()");
 // COM_TRY_BEGIN
  if (IsSeparator)
  {
    *ppszName = NULL;
    return S_FALSE;
  }

  UString name;
  if (IsRoot)
  {
    LoadItems(psiItemArray);
    name = "7-Zip"; //  "New"
  }
  else
    name = "7-Zip item";
  
  if (!_commandMap_Cur.IsEmpty())
  {
    const CCommandMapItem &mi = _commandMap_Cur[0];
    // s += mi.Verb;
    // s += " : ";
    name = mi.UserString;
  }

  return My_SHStrDupW(name, ppszName);
  // return S_OK;
  // COM_TRY_END
}


STDMETHODIMP CZipExplorerCommand::GetIcon(IShellItemArray * /* psiItemArray */, LPWSTR *ppszIcon)
{
  ODS("- GetIcon()");
  // COM_TRY_BEGIN
  *ppszIcon = NULL;
  // return E_NOTIMPL;
  UString imageName = fs2us(NWindows::NDLL::GetModuleDirPrefix());
  // imageName += "7zG.exe";
  imageName += "7-zip.dll";
  // imageName += ",190";
  return My_SHStrDupW(imageName, ppszIcon);
  // COM_TRY_END
}


STDMETHODIMP CZipExplorerCommand::GetToolTip (IShellItemArray * /* psiItemArray */, LPWSTR *ppszInfotip)
{
  // COM_TRY_BEGIN
  ODS("- GetToolTip()");
  *ppszInfotip = NULL;
  return E_NOTIMPL;
  // COM_TRY_END
}


STDMETHODIMP CZipExplorerCommand::GetCanonicalName(GUID *pguidCommandName)
{
  // COM_TRY_BEGIN
  ODS("- GetCanonicalName()");
  *pguidCommandName = GUID_NULL;
  return E_NOTIMPL;
  // COM_TRY_END
}


STDMETHODIMP CZipExplorerCommand::GetState(IShellItemArray * /* psiItemArray */, BOOL /* fOkToBeSlow */, EXPCMDSTATE *pCmdState)
{
  // COM_TRY_BEGIN
  ODS("- GetState()");
  *pCmdState = ECS_ENABLED;
  return S_OK;
  // COM_TRY_END
}




STDMETHODIMP CZipExplorerCommand::Invoke(IShellItemArray *psiItemArray, IBindCtx * /* pbc */)
{
  COM_TRY_BEGIN

  if (_commandMap_Cur.IsEmpty())
    return E_INVALIDARG;

  ODS("- Invoke()");
  UStringVector paths;
  LoadPaths(psiItemArray, paths);
  _fileNames = paths;
  return InvokeCommandCommon(_commandMap_Cur[0]);

  COM_TRY_END
}


STDMETHODIMP CZipExplorerCommand::GetFlags(EXPCMDFLAGS *pFlags)
{
  ODS("- GetFlags()");
  // COM_TRY_BEGIN
  EXPCMDFLAGS f = ECF_DEFAULT;
  if (IsSeparator)
    f = ECF_ISSEPARATOR;
  else if (IsRoot)
    f = ECF_HASSUBCOMMANDS;
  else
  {
    if (!_commandMap_Cur.IsEmpty())
    {
      // const CCommandMapItem &cmi = ;
      if (_commandMap_Cur[0].IsSubMenu())
      {
        // ODS("ECF_HASSUBCOMMANDS");
        f = ECF_HASSUBCOMMANDS;
      }
    }
  }
  *pFlags = f;
  return S_OK;
  // COM_TRY_END
}


STDMETHODIMP CZipExplorerCommand::EnumSubCommands(IEnumExplorerCommand **ppEnum)
{
  ODS("- EnumSubCommands()");
  // COM_TRY_BEGIN
  *ppEnum = NULL;

  if (!_commandMap_Cur.IsEmpty() && _commandMap_Cur[0].IsSubMenu())
  {
  }
  else
  {
    if (!IsRoot)
      return E_NOTIMPL;
    if (SubCommands.IsEmpty())
    {
      return E_NOTIMPL;
    }
  }
 
  // shellExt->
  return QueryInterface(IID_IEnumExplorerCommand, (void **)ppEnum);

  // return S_OK;
  // COM_TRY_END
}


STDMETHODIMP CZipContextMenu::Next(ULONG celt, IExplorerCommand **pUICommand, ULONG *pceltFetched)
{
  ODS("CZipContextMenu::Next()");
  Print_Number(celt, "celt");
  Print_Number(CurrentSubCommand, "CurrentSubCommand");
  Print_Number(SubCommands.Size(), "SubCommands.Size()");
  
  COM_TRY_BEGIN
  ULONG fetched = 0;

  ULONG i;
  for (i = 0; i < celt; i++)
  {
    pUICommand[i] = NULL;
  }
  
  for (i = 0; i < celt && CurrentSubCommand < SubCommands.Size(); i++)
  {
    pUICommand[i] = SubCommands[CurrentSubCommand++];
    pUICommand[i]->AddRef();
    fetched++;
  }
  
  if (pceltFetched)
    *pceltFetched = fetched;

  ODS(fetched == celt ? " === OK === " : "=== ERROR ===");

  // we return S_FALSE for (fetched == 0)
  return (fetched == celt) ? S_OK : S_FALSE;
  COM_TRY_END
}


STDMETHODIMP CZipContextMenu::Skip(ULONG celt)
{
  ODS("CZipContextMenu::Skip()");
  celt = celt;
  return E_NOTIMPL;
}


STDMETHODIMP CZipContextMenu::Reset(void)
{
  ODS("CZipContextMenu::Reset()");
  CurrentSubCommand = 0;
  return S_OK;
}


STDMETHODIMP CZipContextMenu::Clone(IEnumExplorerCommand **ppenum)
{
  ODS("CZipContextMenu::Clone()");
  *ppenum = NULL;
  return E_NOTIMPL;
}
