#include "StdAfx.h"

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

#include "../../../Windows/COM.h"
#include "../../../Windows/Clipboard.h"
#include "../../../Windows/Menu.h"
#include "../../../Windows/PropVariant.h"
#include "../../../Windows/PropVariantConv.h"

#include "../../PropID.h"
#include "../Common/PropIDUtils.h"
#include "../Explorer/ContextMenu.h"

#include "App.h"
#include "FormatUtils.h"
#include "LangUtils.h"
#include "ListViewDialog.h"
#include "MyLoadMenu.h"
#include "PropertyName.h"

#include "PropertyNameRes.h"
#include "resource.h"

// #define SHOW_DEBUG_PANEL_MENU

using namespace NWindows;

extern
LONG g_DllRefCount;
LONG g_DllRefCount = 0;

static const UINT kSevenZipStartMenuID = kMenuCmdID_Plugin_Start;
static const UINT kSystemStartMenuID = kMenuCmdID_Plugin_Start + 400;


#ifdef SHOW_DEBUG_PANEL_MENU
static void Print_Ptr(void *p, const char *s)
{
  char temp[32];
  ConvertUInt64ToHex((UInt64)(void *)p, temp);
  AString m;
  m += temp;
  m.Add_Space();
  m += s;
  OutputDebugStringA(m);
}
#define ODS(sz) { Print_Ptr(this, sz); }
#define ODS_U(s) { OutputDebugStringW(s); }
#else
#define ODS(sz)
#define ODS_U(s)
#endif


void CPanel::InvokeSystemCommand(const char *command)
{
  NCOM::CComInitializer comInitializer;
  if (!IsFsOrPureDrivesFolder())
    return;
  CRecordVector<UInt32> operatedIndices;
  Get_ItemIndices_Operated(operatedIndices);
  if (operatedIndices.IsEmpty())
    return;
  CMyComPtr<IContextMenu> contextMenu;
  if (CreateShellContextMenu(operatedIndices, contextMenu) != S_OK)
    return;

  CMINVOKECOMMANDINFO ci;
  ZeroMemory(&ci, sizeof(ci));
  ci.cbSize = sizeof(CMINVOKECOMMANDINFO);
  ci.hwnd = GetParent();
  ci.lpVerb = command;
  contextMenu->InvokeCommand(&ci);
}

static const char * const kSeparator = "------------------------";
static const char * const kSeparatorSmall = "----------------";

extern UString ConvertSizeToString(UInt64 value) throw();
bool IsSizeProp(UINT propID) throw();

UString GetOpenArcErrorMessage(UInt32 errorFlags);


static void AddListAscii(CListViewDialog &dialog, const char *s)
{
  dialog.Strings.Add((UString)s);
  dialog.Values.AddNew();
}

static void AddSeparator(CListViewDialog &dialog)
{
  AddListAscii(dialog, kSeparator);
}

static void AddSeparatorSmall(CListViewDialog &dialog)
{
  AddListAscii(dialog, kSeparatorSmall);
}

static void AddPropertyPair(const UString &name, const UString &val, CListViewDialog &dialog)
{
  dialog.Strings.Add(name);
  dialog.Values.Add(val);
}


static void AddPropertyString(PROPID propID, const wchar_t *nameBSTR,
    const NCOM::CPropVariant &prop, CListViewDialog &dialog)
{
  if (prop.vt != VT_EMPTY)
  {
    UString val;

    if (propID == kpidErrorFlags ||
        propID == kpidWarningFlags)
    {
      UInt32 flags = GetOpenArcErrorFlags(prop);
      if (flags == 0)
        return;
      if (flags != 0)
        val = GetOpenArcErrorMessage(flags);
    }

    if (val.IsEmpty())
    {
      if ((prop.vt == VT_UI8 || prop.vt == VT_UI4 || prop.vt == VT_UI2) && IsSizeProp(propID))
      {
        UInt64 v = 0;
        ConvertPropVariantToUInt64(prop, v);
        val = ConvertSizeToString(v);
      }
      else
        ConvertPropertyToString2(val, prop, propID, 9); // we send 9 - is ns precision
    }

    if (!val.IsEmpty())
    {
      if (propID == kpidErrorType)
      {
        AddPropertyPair(L"Open WARNING:", L"Cannot open the file as expected archive type", dialog);
      }
      AddPropertyPair(GetNameOfProperty(propID, nameBSTR), val, dialog);
    }
  }
}


static void AddPropertyString(PROPID propID, UInt64 val, CListViewDialog &dialog)
{
  NCOM::CPropVariant prop = val;
  AddPropertyString(propID, NULL, prop, dialog);
}


static const Byte kSpecProps[] =
{
  kpidPath,
  kpidType,
  kpidErrorType,
  kpidError,
  kpidErrorFlags,
  kpidWarning,
  kpidWarningFlags,
  kpidOffset,
  kpidPhySize,
  kpidTailSize
};

void CPanel::Properties()
{
  CMyComPtr<IGetFolderArcProps> getFolderArcProps;
  _folder.QueryInterface(IID_IGetFolderArcProps, &getFolderArcProps);
  if (!getFolderArcProps)
  {
    InvokeSystemCommand("properties");
    return;
  }
  
  {
    CListViewDialog message;
    // message.DeleteIsAllowed = false;
    // message.SelectFirst = false;

    CRecordVector<UInt32> operatedIndices;
    Get_ItemIndices_Operated(operatedIndices);
    
    if (operatedIndices.Size() == 1)
    {
      UInt32 index = operatedIndices[0];
      // message += "Item:\n");
      UInt32 numProps;
      if (_folder->GetNumberOfProperties(&numProps) == S_OK)
      {
        for (UInt32 i = 0; i < numProps; i++)
        {
          CMyComBSTR name;
          PROPID propID;
          VARTYPE varType;
          
          if (_folder->GetPropertyInfo(i, &name, &propID, &varType) != S_OK)
            continue;
          
          NCOM::CPropVariant prop;
          if (_folder->GetProperty(index, propID, &prop) != S_OK)
            continue;
          AddPropertyString(propID, name, prop, message);
        }
      }


      if (_folderRawProps)
      {
        _folderRawProps->GetNumRawProps(&numProps);
        for (UInt32 i = 0; i < numProps; i++)
        {
          CMyComBSTR name;
          PROPID propID;
          if (_folderRawProps->GetRawPropInfo(i, &name, &propID) != S_OK)
            continue;

          const void *data;
          UInt32 dataSize;
          UInt32 propType;
          if (_folderRawProps->GetRawProp(index, propID, &data, &dataSize, &propType) != S_OK)
            continue;

          if (dataSize != 0)
          {
            AString s;
            if (propID == kpidNtSecure)
              ConvertNtSecureToString((const Byte *)data, dataSize, s);
            else
            {
              const unsigned kMaxDataSize = 1 << 8;
              if (dataSize > kMaxDataSize)
              {
                s += "data:";
                s.Add_UInt32(dataSize);
              }
              else
              {
                char temp[kMaxDataSize * 2 + 2];
                if (dataSize <= 8 && (propID == kpidCRC || propID == kpidChecksum))
                  ConvertDataToHex_Upper(temp, (const Byte *)data, dataSize);
                else
                  ConvertDataToHex_Lower(temp, (const Byte *)data, dataSize);
                s += temp;
              }
            }
            AddPropertyPair(GetNameOfProperty(propID, name), (UString)s.Ptr(), message);
          }
        }
      }

      AddSeparator(message);
    }
    else if (operatedIndices.Size() >= 1)
    {
      UInt64 packSize = 0;
      UInt64 unpackSize = 0;
      UInt64 numFiles = 0;
      UInt64 numDirs = 0;

      FOR_VECTOR (i, operatedIndices)
      {
        const UInt32 index = operatedIndices[i];
        unpackSize += GetItemSize(index);
        packSize += GetItem_UInt64Prop(index, kpidPackSize);
        if (IsItem_Folder(index))
        {
          numDirs++;
          numDirs += GetItem_UInt64Prop(index, kpidNumSubDirs);
          numFiles += GetItem_UInt64Prop(index, kpidNumSubFiles);
        }
        else
          numFiles++;
      }
      {
        wchar_t temp[32];
        ConvertUInt32ToString(operatedIndices.Size(), temp);
        AddPropertyPair(L"", MyFormatNew(g_App.LangString_N_SELECTED_ITEMS, temp), message);
      }

      if (numDirs != 0)
        AddPropertyString(kpidNumSubDirs, numDirs, message);
      if (numFiles != 0)
        AddPropertyString(kpidNumSubFiles, numFiles, message);
      AddPropertyString(kpidSize, unpackSize, message);
      AddPropertyString(kpidPackSize, packSize, message);

      AddSeparator(message);
    }

        
    /*
    AddLangString(message, IDS_PROP_FILE_TYPE);
    message += kPropValueSeparator;
    message += GetFolderTypeID();
    message.Add_LF();
    */

    {
      NCOM::CPropVariant prop;
      if (_folder->GetFolderProperty(kpidPath, &prop) == S_OK)
      {
        AddPropertyString(kpidName, L"Path", prop, message);
      }
    }

    CMyComPtr<IFolderProperties> folderProperties;
    _folder.QueryInterface(IID_IFolderProperties, &folderProperties);
    if (folderProperties)
    {
      UInt32 numProps;
      if (folderProperties->GetNumberOfFolderProperties(&numProps) == S_OK)
      {
        for (UInt32 i = 0; i < numProps; i++)
        {
          CMyComBSTR name;
          PROPID propID;
          VARTYPE vt;
          if (folderProperties->GetFolderPropertyInfo(i, &name, &propID, &vt) != S_OK)
            continue;
          NCOM::CPropVariant prop;
          if (_folder->GetFolderProperty(propID, &prop) != S_OK)
            continue;
          AddPropertyString(propID, name, prop, message);
        }
      }
    }

    if (getFolderArcProps)
    {
      CMyComPtr<IFolderArcProps> getProps;
      getFolderArcProps->GetFolderArcProps(&getProps);
      if (getProps)
      {
        UInt32 numLevels;
        if (getProps->GetArcNumLevels(&numLevels) != S_OK)
          numLevels = 0;
        for (UInt32 level2 = 0; level2 < numLevels; level2++)
        {
          {
            UInt32 level = numLevels - 1 - level2;
            UInt32 numProps;
            if (getProps->GetArcNumProps(level, &numProps) == S_OK)
            {
              const int kNumSpecProps = Z7_ARRAY_SIZE(kSpecProps);

              AddSeparator(message);
              
              for (Int32 i = -(int)kNumSpecProps; i < (Int32)numProps; i++)
              {
                CMyComBSTR name;
                PROPID propID;
                VARTYPE vt;
                if (i < 0)
                  propID = kSpecProps[i + kNumSpecProps];
                else if (getProps->GetArcPropInfo(level, (UInt32)i, &name, &propID, &vt) != S_OK)
                  continue;
                NCOM::CPropVariant prop;
                if (getProps->GetArcProp(level, propID, &prop) != S_OK)
                  continue;
                AddPropertyString(propID, name, prop, message);
              }
            }
          }
          
          if (level2 < numLevels - 1)
          {
            const UInt32 level = numLevels - 1 - level2;
            UInt32 numProps;
            if (getProps->GetArcNumProps2(level, &numProps) == S_OK)
            {
              AddSeparatorSmall(message);
              for (UInt32 i = 0; i < numProps; i++)
              {
                CMyComBSTR name;
                PROPID propID;
                VARTYPE vt;
                if (getProps->GetArcPropInfo2(level, i, &name, &propID, &vt) != S_OK)
                  continue;
                NCOM::CPropVariant prop;
                if (getProps->GetArcProp2(level, propID, &prop) != S_OK)
                  continue;
                AddPropertyString(propID, name, prop, message);
              }
            }
          }
        }
        
        {
          // we ERROR message for NonOpen level
              bool needSep = true;
              const int kNumSpecProps = Z7_ARRAY_SIZE(kSpecProps);
              for (Int32 i = -(int)kNumSpecProps; i < 0; i++)
              {
                CMyComBSTR name;
                const PROPID propID = kSpecProps[i + kNumSpecProps];
                NCOM::CPropVariant prop;
                if (getProps->GetArcProp(numLevels, propID, &prop) != S_OK)
                  continue;
                if (needSep)
                {
                  AddSeparator(message);
                  AddSeparator(message);
                  needSep = false;
                }
                AddPropertyString(propID, name, prop, message);
              }
        }

      }
    }

    message.Title = LangString(IDS_PROPERTIES);
    message.NumColumns = 2;
    message.Create(GetParent());
  }
}



void CPanel::EditCut()
{
  // InvokeSystemCommand("cut");
}

void CPanel::EditCopy()
{
  /*
  CMyComPtr<IGetFolderArcProps> getFolderArcProps;
  _folder.QueryInterface(IID_IGetFolderArcProps, &getFolderArcProps);
  if (!getFolderArcProps)
  {
    InvokeSystemCommand("copy");
    return;
  }
  */
  UString s;
  CRecordVector<UInt32> indices;
  Get_ItemIndices_Selected(indices);
  FOR_VECTOR (i, indices)
  {
    if (i != 0)
      s += "\xD\n";
    s += GetItemName(indices[i]);
  }
  ClipboardSetText(_mainWindow, s);
}

void CPanel::EditPaste()
{
  /*
  UStringVector names;
  ClipboardGetFileNames(names);
  CopyFromNoAsk(names);
  UString s;
  for (int i = 0; i < names.Size(); i++)
  {
    s += L' ';
    s += names[i];
  }

  MessageBoxW(0, s, L"", 0);
  */

  // InvokeSystemCommand("paste");
}



struct CFolderPidls
{
  LPITEMIDLIST parent;
  CRecordVector<LPITEMIDLIST> items;

  CFolderPidls(): parent(NULL) {}
  ~CFolderPidls()
  {
    FOR_VECTOR (i, items)
      CoTaskMemFree(items[i]);
    CoTaskMemFree(parent);
  }
};


HRESULT ShellFolder_ParseDisplayName(IShellFolder *shellFolder,
    HWND hwnd, const UString &path, LPITEMIDLIST *ppidl);
HRESULT ShellFolder_ParseDisplayName(IShellFolder *shellFolder,
    HWND hwnd, const UString &path, LPITEMIDLIST *ppidl)
{
  // ULONG eaten = 0;
  return shellFolder->ParseDisplayName(hwnd, NULL,
      path.Ptr_non_const(),
      NULL, // &eaten
      ppidl, NULL);
}


HRESULT CPanel::CreateShellContextMenu(
    const CRecordVector<UInt32> &operatedIndices,
    CMyComPtr<IContextMenu> &systemContextMenu)
{
  ODS("==== CPanel::CreateShellContextMenu");
  systemContextMenu.Release();
  UString folderPath = GetFsPath();

  CMyComPtr<IShellFolder> desktopFolder;
  RINOK(::SHGetDesktopFolder(&desktopFolder))
  if (!desktopFolder)
  {
    // ShowMessage("Failed to get Desktop folder");
    return E_FAIL;
  }
  
  CFolderPidls pidls;
  // NULL is allowed for parentHWND in ParseDisplayName()
  const HWND parentHWND_for_ParseDisplayName = GetParent();
  // if (folderPath.IsEmpty()), then ParseDisplayName returns pidls of "My Computer"
  /* win10: ParseDisplayName() supports folder path with tail slash
    ParseDisplayName() returns {
      E_INVALIDARG         : path with super path prefix "\\\\?\\"
      ERROR_FILE_NOT_FOUND : path for network share (\\server\path1\long path2") larger than MAX_PATH
    } */
  const HRESULT res = ShellFolder_ParseDisplayName(desktopFolder,
      parentHWND_for_ParseDisplayName,
      folderPath, &pidls.parent);
  if (res != S_OK)
  {
    ODS_U(folderPath);
    if (res != E_INVALIDARG)
      return res;
    if (!NFile::NName::If_IsSuperPath_RemoveSuperPrefix(folderPath))
      return res;
    RINOK(ShellFolder_ParseDisplayName(desktopFolder,
        parentHWND_for_ParseDisplayName,
        folderPath, &pidls.parent))
  }
  if (!pidls.parent)
    return E_FAIL;

  /*
  UString path2;
  NShell::GetPathFromIDList(pidls.parent, path2);
  ODS_U(path2);
  */

  if (operatedIndices.IsEmpty())
  {
    // how to get IContextMenu, if there are no selected files?
    return E_FAIL;

    /*
    xp64 :
    1) we can't use GetUIObjectOf() with (numItems == 0), it throws exception
    2) we can't use desktopFolder->GetUIObjectOf() with absolute pidls of folder
        context menu items are different in that case:
          "Open / Explorer" for folder
          "Delete" for "My Computer" icon
          "Preperties" for "System"
    */
    /*
    parentFolder = desktopFolder;
    pidls.items.AddInReserved(pidls.parent);
    pidls.parent = NULL;
    */

    // CreateViewObject() doesn't show all context menu items
    /*
    HRESULT res = parentFolder->CreateViewObject(
        GetParent(), IID_IContextMenu, (void**)&systemContextMenu);
    */
  }
  
  CMyComPtr<IShellFolder> parentFolder;
  RINOK(desktopFolder->BindToObject(pidls.parent,
      NULL, IID_IShellFolder, (void**)&parentFolder))
  if (!parentFolder)
    return E_FAIL;

  ODS("==== CPanel::CreateShellContextMenu pidls START");
  
  pidls.items.ClearAndReserve(operatedIndices.Size());
  UString fileName;
  FOR_VECTOR (i, operatedIndices)
  {
    fileName.Empty();
    Add_ItemRelPath2_To_String(operatedIndices[i], fileName);
    /* ParseDisplayName() in win10 returns:
         E_INVALIDARG : if empty name, or path with dots only: "." , ".."
         HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) : if there is no such file
    */
    LPITEMIDLIST pidl = NULL;
    RINOK(ShellFolder_ParseDisplayName(parentFolder,
        parentHWND_for_ParseDisplayName,
        fileName, &pidl))
    if (!pidl)
      return E_FAIL;
    pidls.items.AddInReserved(pidl);
  }

  ODS("==== CPanel::CreateShellContextMenu pidls END");
  // Get IContextMenu for items
  RINOK(parentFolder->GetUIObjectOf(GetParent(),
      pidls.items.Size(), (LPCITEMIDLIST *)(void *)pidls.items.ConstData(),
      IID_IContextMenu, NULL, (void**)&systemContextMenu))
  ODS("==== CPanel::CreateShellContextMenu GetUIObjectOf finished");
  if (!systemContextMenu)
  {
    // ShowMessage("Unable to get context menu interface");
    return E_FAIL;
  }
  return S_OK;
}


// #define SHOW_DEBUG_FM_CTX_MENU

#ifdef SHOW_DEBUG_FM_CTX_MENU

static void PrintHex(UString &s, UInt32 v)
{
  char sz[32];
  ConvertUInt32ToHex(v, sz);
  s += sz;
}

static void PrintContextStr(UString &s, IContextMenu *ctxm, unsigned i, unsigned id, const char *name)
{
  s += " | ";
  s += name;
  s += ": ";
  UString s1;
  {
    char buf[256];
    buf[0] = 0;
    const HRESULT res = ctxm->GetCommandString(i, id,
        NULL, buf, Z7_ARRAY_SIZE(buf) - 1);
    if (res != S_OK)
    {
      PrintHex(s1, res);
      s1.Add_Space();
    }
    s1 += GetUnicodeString(buf);
  }
  UString s2;
  {
    wchar_t buf2[256];
    buf2[0] = 0;
    const HRESULT res = ctxm->GetCommandString(i, id | GCS_UNICODE,
        NULL, (char *)buf2, Z7_ARRAY_SIZE(buf2) - sizeof(wchar_t));
    if (res != S_OK)
    {
      PrintHex(s2, res);
      s2.Add_Space();
    }
    s2 += buf2;
  }
  s += s1;
  if (s2.Compare(s1) != 0)
  {
    s += " Unicode: ";
    s += s2;
  }
}

static void PrintAllContextItems(IContextMenu *ctxm, unsigned num)
{
  for (unsigned i = 0; i < num; i++)
  {
    UString s;
    s.Add_UInt32(i);
    s += ": ";
    PrintContextStr(s, ctxm, i, GCS_VALIDATEA, "valid");
    PrintContextStr(s, ctxm, i, GCS_VERBA, "verb");
    PrintContextStr(s, ctxm, i, GCS_HELPTEXTA, "helptext");
    OutputDebugStringW(s);
  }
}

#endif


void CPanel::CreateSystemMenu(HMENU menuSpec,
    bool showExtendedVerbs,
    const CRecordVector<UInt32> &operatedIndices,
    CMyComPtr<IContextMenu> &systemContextMenu)
{
  systemContextMenu.Release();

  CreateShellContextMenu(operatedIndices, systemContextMenu);

  if (!systemContextMenu)
    return;
  
  /*
  // Set up a CMINVOKECOMMANDINFO structure.
  CMINVOKECOMMANDINFO ci;
  ZeroMemory(&ci, sizeof(ci));
  ci.cbSize = sizeof(CMINVOKECOMMANDINFO);
  ci.hwnd = GetParent();
  */
  
  /*
  if (Sender == GoBtn)
  {
    // Verbs that can be used are cut, paste,
    // properties, delete, and so on.
    String action;
    if (CutRb->Checked)
      action = "cut";
    else if (CopyRb->Checked)
      action = "copy";
    else if (DeleteRb->Checked)
      action = "delete";
    else if (PropertiesRb->Checked)
      action = "properties";
    
    ci.lpVerb = action.c_str();
    result = cm->InvokeCommand(&ci);
    if (result)
      ShowMessage(
      "Error copying file to clipboard.");
    
  }
  else
  */
  {
    // HMENU hMenu = CreatePopupMenu();
    CMenu popupMenu;
    CMenuDestroyer menuDestroyer(popupMenu);
    if (!popupMenu.CreatePopup())
      throw 210503;
    const HMENU hMenu = popupMenu;
    DWORD flags = CMF_EXPLORE;
    if (showExtendedVerbs)
      flags |= Z7_WIN_CMF_EXTENDEDVERBS;
    ODS("=== systemContextMenu->QueryContextMenu START");
    const HRESULT res = systemContextMenu->QueryContextMenu(hMenu, 0, kSystemStartMenuID, 0x7FFF, flags);
    ODS("=== systemContextMenu->QueryContextMenu END");
    if (SUCCEEDED(res))
    {
      #ifdef SHOW_DEBUG_FM_CTX_MENU
      PrintAllContextItems(systemContextMenu, (unsigned)res);
      #endif
      
      CMenu menu;
      menu.Attach(menuSpec);
      CMenuItem menuItem;
      menuItem.fMask = MIIM_SUBMENU | MIIM_TYPE | MIIM_ID;
      menuItem.fType = MFT_STRING;
      menuItem.hSubMenu = popupMenu.Detach();
      menuDestroyer.Disable();
      LangString(IDS_SYSTEM, menuItem.StringValue);
      menu.InsertItem(0, true, menuItem);
    }
    /*
    if (Cmd < 100 && Cmd != 0)
    {
      ci.lpVerb = MAKEINTRESOURCE(Cmd - 1);
      ci.lpParameters = "";
      ci.lpDirectory = "";
      ci.nShow = SW_SHOWNORMAL;
      cm->InvokeCommand(&ci);
    }
    // If Cmd is > 100 then it's one of our
    // inserted menu items.
    else
      // Find the menu item.
      for (int i = 0; i < popupMenu1->Items->Count; i++)
      {
        TMenuItem* menu = popupMenu1->Items->Items[i];
        // Call its OnClick handler.
        if (menu->Command == Cmd - 100)
          menu->OnClick(this);
      }
      // Release the memory allocated for the menu.
      DestroyMenu(hMenu);
    */
  }
}

void CPanel::CreateFileMenu(HMENU menuSpec)
{
  CreateFileMenu(menuSpec, _sevenZipContextMenu, _systemContextMenu, true); // programMenu
}

void CPanel::CreateSevenZipMenu(HMENU menuSpec,
    bool showExtendedVerbs,
    const CRecordVector<UInt32> &operatedIndices,
    int firstDirIndex,
    CMyComPtr<IContextMenu> &sevenZipContextMenu)
{
  sevenZipContextMenu.Release();

  CMenu menu;
  menu.Attach(menuSpec);
  // CMenuDestroyer menuDestroyer(menu);
  // menu.CreatePopup();

  CZipContextMenu *contextMenuSpec = new CZipContextMenu;
  CMyComPtr<IContextMenu> contextMenu = contextMenuSpec;
  // if (contextMenu.CoCreateInstance(CLSID_CZipContextMenu, IID_IContextMenu) == S_OK)
  {
    /*
    CMyComPtr<IInitContextMenu> initContextMenu;
    if (contextMenu.QueryInterface(IID_IInitContextMenu, &initContextMenu) != S_OK)
      return;
    */
    ODS("=== FileName List Add START")
    // for (unsigned y = 0; y < 10000; y++, contextMenuSpec->_fileNames.Clear())
    GetFilePaths(operatedIndices, contextMenuSpec->_fileNames);
    ODS("=== FileName List Add END")
    contextMenuSpec->Init_For_7zFM();
    contextMenuSpec->_attribs.FirstDirIndex = firstDirIndex;
    {
      DWORD flags = CMF_EXPLORE;
      if (showExtendedVerbs)
        flags |= Z7_WIN_CMF_EXTENDEDVERBS;
      const HRESULT res = contextMenu->QueryContextMenu(menu,
          0, // indexMenu
          kSevenZipStartMenuID, // first
          kSystemStartMenuID - 1, // last
          flags);
      ODS("=== contextMenu->QueryContextMenu END")
      const bool sevenZipMenuCreated = SUCCEEDED(res);
      if (sevenZipMenuCreated)
      {
        // if (res != 0)
        {
          // some "non-good" implementation of QueryContextMenu() could add some items to menu, but it return 0.
          // so we still allow these items
          sevenZipContextMenu = contextMenu;
          #ifdef SHOW_DEBUG_FM_CTX_MENU
          PrintAllContextItems(contextMenu, (unsigned)res);
          #endif
        }
      }
      else
      {
        // MessageBox_Error_HRESULT_Caption(res, L"QueryContextMenu");
      }
      // int code = HRESULT_CODE(res);
      // int nextItemID = code;
    }
  }
}

static bool IsReadOnlyFolder(IFolderFolder *folder)
{
  if (!folder)
    return false;

  bool res = false;
  {
    NCOM::CPropVariant prop;
    if (folder->GetFolderProperty(kpidReadOnly, &prop) == S_OK)
      if (prop.vt == VT_BOOL)
        res = VARIANT_BOOLToBool(prop.boolVal);
  }
  return res;
}

bool CPanel::IsThereReadOnlyFolder() const
{
  if (!_folderOperations)
    return true;
  if (IsReadOnlyFolder(_folder))
    return true;
  FOR_VECTOR (i, _parentFolders)
  {
    if (IsReadOnlyFolder(_parentFolders[i].ParentFolder))
      return true;
  }
  return false;
}

bool CPanel::CheckBeforeUpdate(UINT resourceID)
{
  if (!_folderOperations)
  {
    MessageBox_Error_UnsupportOperation();
    // resourceID = resourceID;
    // MessageBoxErrorForUpdate(E_NOINTERFACE, resourceID);
    return false;
  }

  for (int i = (int)_parentFolders.Size(); i >= 0; i--)
  {
    IFolderFolder *folder;
    if (i == (int)_parentFolders.Size())
      folder = _folder;
    else
      folder = _parentFolders[i].ParentFolder;
    
    if (!IsReadOnlyFolder(folder))
      continue;
    
    UString s;
    AddLangString(s, resourceID);
    s.Add_LF();
    AddLangString(s, IDS_OPERATION_IS_NOT_SUPPORTED);
    s.Add_LF();
    if (i == 0)
      s += GetFolderPath(folder);
    else
      s += _parentFolders[i - 1].VirtualPath;
    s.Add_LF();
    AddLangString(s, IDS_PROP_READ_ONLY);
    MessageBox_Error(s);
    return false;
  }

  return true;
}

void CPanel::CreateFileMenu(HMENU menuSpec,
    CMyComPtr<IContextMenu> &sevenZipContextMenu,
    CMyComPtr<IContextMenu> &systemContextMenu,
    bool programMenu)
{
  sevenZipContextMenu.Release();
  systemContextMenu.Release();

  const bool showExtendedVerbs = IsKeyDown(VK_SHIFT);

  CRecordVector<UInt32> operatedIndices;
  Get_ItemIndices_Operated(operatedIndices);
  const int firstDirIndex = FindDir_InOperatedList(operatedIndices);

  CMenu menu;
  menu.Attach(menuSpec);

  if (!IsArcFolder())
  {
    CreateSevenZipMenu(menu, showExtendedVerbs, operatedIndices, firstDirIndex, sevenZipContextMenu);
    // CreateSystemMenu is very slow if you call it inside ZIP archive with big number of files
    // Windows probably can parse items inside ZIP archive.
    if (g_App.ShowSystemMenu)
      CreateSystemMenu(menu, showExtendedVerbs, operatedIndices, systemContextMenu);
  }

  /*
  if (menu.GetItemCount() > 0)
    menu.AppendItem(MF_SEPARATOR, 0, (LPCTSTR)0);
  */

  CFileMenu fm;
  
  fm.readOnly = IsThereReadOnlyFolder();
  fm.isHashFolder = IsHashFolder();
  fm.isFsFolder = Is_IO_FS_Folder();
  fm.programMenu = programMenu;
  fm.allAreFiles = (firstDirIndex == -1);
  fm.numItems = operatedIndices.Size();

  fm.isAltStreamsSupported = false;
  
  if (fm.numItems == 1)
    fm.FilePath = us2fs(GetItemFullPath(operatedIndices[0]));

  if (_folderAltStreams)
  {
    if (operatedIndices.Size() <= 1)
    {
      UInt32 realIndex = (UInt32)(Int32)-1;
      if (operatedIndices.Size() == 1)
        realIndex = operatedIndices[0];
      Int32 val = 0;
      if (_folderAltStreams->AreAltStreamsSupported(realIndex, &val) == S_OK)
        fm.isAltStreamsSupported = IntToBool(val);
    }
  }
  else
  {
    if (fm.numItems == 0)
      fm.isAltStreamsSupported = IsFSFolder();
    else
      fm.isAltStreamsSupported = IsFolder_with_FsItems();
  }

  fm.Load(menu, (unsigned)menu.GetItemCount());
}

bool CPanel::InvokePluginCommand(unsigned id)
{
  return InvokePluginCommand(id, _sevenZipContextMenu, _systemContextMenu);
}

#if defined(_MSC_VER) && !defined(UNDER_CE)
#define use_CMINVOKECOMMANDINFOEX
/* CMINVOKECOMMANDINFOEX depends from (_WIN32_IE >= 0x0400) */
#endif

bool CPanel::InvokePluginCommand(unsigned id,
    IContextMenu *sevenZipContextMenu, IContextMenu *systemContextMenu)
{
  UInt32 offset;
  const bool isSystemMenu = (id >= kSystemStartMenuID);
  if (isSystemMenu)
  {
    if (!systemContextMenu)
      return false;
    offset = id - kSystemStartMenuID;
  }
  else
  {
    if (!sevenZipContextMenu)
      return false;
    offset = id - kSevenZipStartMenuID;
  }

  #ifdef use_CMINVOKECOMMANDINFOEX
    CMINVOKECOMMANDINFOEX
  #else
    CMINVOKECOMMANDINFO
  #endif
      commandInfo;
  
  memset(&commandInfo, 0, sizeof(commandInfo));
  commandInfo.cbSize = sizeof(commandInfo);
  
  commandInfo.fMask = 0
  #ifdef use_CMINVOKECOMMANDINFOEX
    | CMIC_MASK_UNICODE
  #endif
    ;

  commandInfo.hwnd = GetParent();
  commandInfo.lpVerb = (LPCSTR)(MAKEINTRESOURCE(offset));
  commandInfo.lpParameters = NULL;
  // 19.01: fixed CSysString to AString
  // MSDN suggest to send NULL: lpDirectory: This member is always NULL for menu items inserted by a Shell extension.
  const AString currentFolderA (GetAnsiString(_currentFolderPrefix));
  commandInfo.lpDirectory = (LPCSTR)(currentFolderA);
  commandInfo.nShow = SW_SHOW;
  
  #ifdef use_CMINVOKECOMMANDINFOEX
  
  commandInfo.lpParametersW = NULL;
  commandInfo.lpTitle = "";

  /*
  system ContextMenu handler supports ContextMenu subhandlers.
  so InvokeCommand() converts (command_offset) from global number to subhandler number.
  XP-64 / win10:
      system ContextMenu converts (command_offset) in lpVerb only,
      and it keeps lpVerbW unchanged.
      also explorer.exe sends 0 in lpVerbW.
  We try to keep compatibility with Windows Explorer here.
  */
  commandInfo.lpVerbW = NULL;

  const UString currentFolderUnicode = _currentFolderPrefix;
  commandInfo.lpDirectoryW = currentFolderUnicode;
  commandInfo.lpTitleW = L"";
  // commandInfo.ptInvoke.x = xPos;
  // commandInfo.ptInvoke.y = yPos;
  commandInfo.ptInvoke.x = 0;
  commandInfo.ptInvoke.y = 0;
  
  #endif
  
  HRESULT result;
  if (isSystemMenu)
    result = systemContextMenu->InvokeCommand(LPCMINVOKECOMMANDINFO(&commandInfo));
  else
    result = sevenZipContextMenu->InvokeCommand(LPCMINVOKECOMMANDINFO(&commandInfo));
  if (result == NOERROR)
  {
    KillSelection();
    return true;
  }
  else
    MessageBox_Error_HRESULT_Caption(result, L"InvokeCommand");
  return false;
}

bool CPanel::OnContextMenu(HANDLE windowHandle, int xPos, int yPos)
{
  if (::GetParent((HWND)windowHandle) == _listView)
  {
    ShowColumnsContextMenu(xPos, yPos);
    return true;
  }

  if (windowHandle != _listView)
    return false;
  /*
  POINT point;
  point.x = xPos;
  point.y = yPos;
  if (!_listView.ScreenToClient(&point))
    return false;

  LVHITTESTINFO info;
  info.pt = point;
  int index = _listView.HitTest(&info);
  */

  CRecordVector<UInt32> operatedIndices;
  Get_ItemIndices_Operated(operatedIndices);

  // negative x,y are possible for multi-screen modes.
  // x=-1 && y=-1 for keyboard call (SHIFT+F10 and others).
  if (xPos == -1 && yPos == -1)
  {
    if (operatedIndices.Size() == 0)
    {
      xPos = 0;
      yPos = 0;
    }
    else
    {
      int itemIndex = _listView.GetNextItem(-1, LVNI_FOCUSED);
      if (itemIndex == -1)
        return false;
      RECT rect;
      if (!_listView.GetItemRect(itemIndex, &rect, LVIR_ICON))
        return false;
      xPos = (rect.left + rect.right) / 2;
      yPos = (rect.top + rect.bottom) / 2;
    }
    POINT point = {xPos, yPos};
    _listView.ClientToScreen(&point);
    xPos = point.x;
    yPos = point.y;
  }

  CMenu menu;
  CMenuDestroyer menuDestroyer(menu);
  menu.CreatePopup();

  CMyComPtr<IContextMenu> sevenZipContextMenu;
  CMyComPtr<IContextMenu> systemContextMenu;
  CreateFileMenu(menu, sevenZipContextMenu, systemContextMenu, false); // programMenu

  const unsigned id = (unsigned)menu.Track(TPM_LEFTALIGN
      #ifndef UNDER_CE
      | TPM_RIGHTBUTTON
      #endif
      | TPM_RETURNCMD | TPM_NONOTIFY,
    xPos, yPos, _listView);

  if (id == 0)
    return true;

  if (id >= kMenuCmdID_Plugin_Start)
  {
    InvokePluginCommand(id, sevenZipContextMenu, systemContextMenu);
    return true;
  }
  if (ExecuteFileCommand(id))
    return true;
  return true;
}
