/*
* Copyright (c) 2003-2016 Rony Shapiro <ronys@pwsafe.org>.
* All rights reserved. Use of the code is allowed under the
* Artistic License 2.0 terms, as specified in the LICENSE file
* distributed with this code, or available from
* http://www.opensource.org/licenses/artistic-license-2.0.php
*/
// file MainMenu.cpp
// Menu-related methods of DboxMain
//-----------------------------------------------------------------------------

#include "PasswordSafe.h"
#include "ThisMfcApp.h"
#include "DboxMain.h"

#include "HKModifiers.h"

#include "core/PWSprefs.h"

#include "os/file.h"

#include "resource.h"
#include "resource2.h"  // Menu, Toolbar & Accelerator resources
#include "resource3.h"  // String resources

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// Functor for count_if
struct CountShortcuts {
  bool operator()(const std::pair<const UINT, CMenuShortcut> &p) {
    return (p.second.siVirtKey != 0);
  }
};

// Functor for for_each
struct CreateAccelTable {
  CreateAccelTable(ACCEL *pacceltbl, unsigned char ucAutotypeKey) 
    : m_pacceltbl(pacceltbl), m_ucAutotypeKey(ucAutotypeKey) {}

  void operator()(const std::pair<UINT, CMenuShortcut> &p)
  {
    if (p.second.siVirtKey != 0 && p.second.siVirtKey != m_ucAutotypeKey) {
      m_pacceltbl->fVirt = FVIRTKEY |
               ((p.second.cModifier & HOTKEYF_CONTROL) == HOTKEYF_CONTROL ? FCONTROL : 0) |
               ((p.second.cModifier & HOTKEYF_ALT)     == HOTKEYF_ALT     ? FALT     : 0) |
               ((p.second.cModifier & HOTKEYF_SHIFT)   == HOTKEYF_SHIFT   ? FSHIFT   : 0);
      m_pacceltbl->key = (WORD)p.second.siVirtKey;
      m_pacceltbl->cmd = (WORD)p.first;
      m_pacceltbl++;
    }
  }

private:
  ACCEL *m_pacceltbl;
  unsigned char m_ucAutotypeKey; // AutoType shortcut key
};

static bool IsExtended(int code)
{
  switch (code) {
    case VK_PRIOR:                       // Page Up
    case VK_NEXT:                        // Page Down
    case VK_END:                         // End
    case VK_HOME:                        // Home
    case VK_LEFT:                        // Left arrow cursor
    case VK_UP:                          // Up arrow cursor
    case VK_RIGHT:                       // Right arrow cursor
    case VK_DOWN:                        // Down arrow cursor
    case VK_SNAPSHOT:                    // Print Screen
    case VK_INSERT:                      // Insert
    case VK_DELETE:                      // Delete
    case VK_LWIN:                        // Left Windows Key
    case VK_RWIN:                        // Right Windows Key
    case VK_APPS:                        // Windows Application Key
    case VK_DIVIDE:                      // Numeric pad '/'
    case VK_NUMLOCK:                     // Numlock
      return true;
    default:
      return false;
  }
}

void InsertShortcuts(CMenu *pMenu, MapMenuShortcuts &mms,
                     const unsigned int parentID)
{
  // if parentID == 0, we're processing a toplevel menu,
  // else, we're passed the menu in which the desired
  // submenu resides.
  BOOL brc;
  wchar_t tcMenuString[_MAX_PATH + 1];
  CMenuShortcut mst;
  MENUITEMINFO miteminfo;
 
  CMenu *pSCTMenu = pMenu;
  ASSERT_VALID(pSCTMenu);
  static int iMenuPos(0);

  if (parentID != 0) {
    int isubmenu_pos = app.FindMenuItem(pSCTMenu, parentID);
    ASSERT(isubmenu_pos != -1);
    pSCTMenu = pSCTMenu->GetSubMenu(isubmenu_pos);
    ASSERT_VALID(pSCTMenu);
  }

  mst.uiParentID = parentID;

  UINT uiCount = pSCTMenu->GetMenuItemCount();
  ASSERT((int)uiCount >= 0);

  for (UINT ui = 0; ui < uiCount; ui++) {
    SecureZeroMemory(tcMenuString, sizeof(tcMenuString));
    SecureZeroMemory(&miteminfo, sizeof(miteminfo));
    miteminfo.cbSize = sizeof(miteminfo);
    miteminfo.fMask = MIIM_ID | MIIM_STRING;
    miteminfo.cch = _MAX_PATH;
    miteminfo.dwTypeData = tcMenuString;

    brc = pSCTMenu->GetMenuItemInfo(ui, &miteminfo, TRUE);
    ASSERT(brc != 0);

    if (miteminfo.wID >= 1) {
      std::pair< MapMenuShortcutsIter, bool > pr;
      mst.name = tcMenuString;
      mst.iMenuPosition = iMenuPos;
      pr = mms.insert(MapMenuShortcutsPair(miteminfo.wID, mst));
      ASSERT(pr.second == true);
      iMenuPos++;
    }
  }
}

void DboxMain::SetUpInitialMenuStrings()
{
  CMenu *pMainMenu, *pSubMenu;
  int isubmenu_pos;
  UINT uiCount;

  CString sKeyName;
  BOOL brc;

  // Following are excluded from list of user-configurable
  // shortcuts
  UINT excludedMenuItems[] = {
  // Add user Excluded Menu Items - anything that is a Popup Menu
    ID_FILEMENU, ID_EXPORTMENU, ID_IMPORTMENU, ID_EDITMENU,
    ID_VIEWMENU, ID_FILTERMENU, ID_CHANGEFONTMENU, ID_REPORTSMENU,
    ID_MANAGEMENU, ID_LANGUAGEMENU, ID_HELPMENU, ID_FINDMENU,
    ID_EXPORTENTMENU, ID_EXPORTGROUPMENU,

  // Plus Exit (2 shortcuts Ctrl+Q and Alt+F4) and Help (F1)
    ID_MENUITEM_EXIT, ID_HELP,

  // Plus Expand/Collapse group - function of TreeCtrl and is
  // the Enter key, which cannot be assigned via the Shortcut
  // Property Page
    ID_MENUITEM_GROUPENTER,

  // The following are only in the Menu to get the correct string
  // for the action.  Add here to stop them being in the Shortcut
  // Options CListCtrl.
  // User can alter the function using the base values
  // ID_MENUITEM_EDIT, ID_MENUITEM_DELETE & ID_MENUITEM_RENAME
    ID_MENUITEM_VIEW,
    ID_MENUITEM_DELETEENTRY, ID_MENUITEM_DELETEGROUP,
    ID_MENUITEM_RENAMEENTRY, ID_MENUITEM_RENAMEGROUP,
  };

  m_ExcludedMenuItems.clear();
  m_ExcludedMenuItems.assign(excludedMenuItems,
                             excludedMenuItems + _countof(excludedMenuItems));

  pMainMenu = new CMenu;
  brc = pMainMenu->LoadMenu(IDR_MAINMENU);
  ASSERT(brc != 0);

  wchar_t tcMenuString[_MAX_PATH + 1];

  MENUITEMINFO miteminfo = {0};

  uiCount = pMainMenu->GetMenuItemCount();
  ASSERT((int)uiCount >= 0);

  // Add reserved shortcuts = Alt+'x' for main menu e.g. Alt+F, E, V, M, H
  // Maybe different in other languages
  m_ReservedShortcuts.clear();

  st_MenuShortcut st_mst;
  st_mst.siVirtKey;
  st_mst.cModifier = HOTKEYF_ALT;

  for (UINT ui = 0; ui < uiCount; ui++) {
    SecureZeroMemory(tcMenuString, sizeof(tcMenuString));

    SecureZeroMemory(&miteminfo, sizeof(miteminfo));
    miteminfo.cbSize = sizeof(miteminfo);
    miteminfo.fMask = MIIM_ID | MIIM_STRING;
    miteminfo.dwTypeData = tcMenuString;
    miteminfo.cch = _MAX_PATH;

    brc = pMainMenu->GetMenuItemInfo(ui, &miteminfo, TRUE);
    ASSERT(brc != 0);

    if (miteminfo.wID >= 1) {
      CString csMainMenuItem = tcMenuString;
      int iamp = csMainMenuItem.Find(L'&');
      if (iamp >= 0 && iamp < csMainMenuItem.GetLength() - 1) {
        st_mst.nControlID = miteminfo.wID;
        st_mst.siVirtKey = csMainMenuItem[iamp + 1];
        m_ReservedShortcuts.push_back(st_mst);
      }
    }
  }

  // Add 3 special keys F1 (for Help), Ctrl+Q/Alt+F4 (Exit)
  st_mst.nControlID = ID_MENUITEM_HELP;
  st_mst.siVirtKey = VK_F1;
  st_mst.cModifier = 0;
  m_ReservedShortcuts.push_back(st_mst);

  st_mst.nControlID = ID_MENUITEM_EXIT;
  st_mst.siVirtKey = 'Q';
  st_mst.cModifier = HOTKEYF_CONTROL;
  m_ReservedShortcuts.push_back(st_mst);

  st_mst.nControlID = ID_MENUITEM_EXIT;
  st_mst.siVirtKey = VK_F4;
  st_mst.cModifier = HOTKEYF_ALT;
  m_ReservedShortcuts.push_back(st_mst);

  // Now get all other Menu items
  // (ronys) I think we can do the following properly via recursive descent. Later.

  m_MapMenuShortcuts.clear();

  // Do Main Menu (uiParentID == 0)
  InsertShortcuts(pMainMenu, m_MapMenuShortcuts, 0);

  // Do File Menu
  InsertShortcuts(pMainMenu, m_MapMenuShortcuts, ID_FILEMENU);

  isubmenu_pos = app.FindMenuItem(pMainMenu, ID_FILEMENU);
  ASSERT(isubmenu_pos != -1);
  pSubMenu = pMainMenu->GetSubMenu(isubmenu_pos);

  // Do File Menu Export submenu
  InsertShortcuts(pSubMenu, m_MapMenuShortcuts, ID_EXPORTMENU);

  // Do File Menu Import submenu
  InsertShortcuts(pSubMenu, m_MapMenuShortcuts, ID_IMPORTMENU);

  // Do Edit Menu
  InsertShortcuts(pMainMenu, m_MapMenuShortcuts, ID_EDITMENU);

  // Do Export Entry submenu
  //  InsertShortcuts(pSubMenu, m_MapMenuShortcuts, ID_EXPORTENTMENU);

  // Do Export Group submenu
  //  InsertShortcuts(pSubMenu, m_MapMenuShortcuts, ID_EXPORTGROUPMENU);

  // Do View Menu
  InsertShortcuts(pMainMenu, m_MapMenuShortcuts, ID_VIEWMENU);

  isubmenu_pos = app.FindMenuItem(pMainMenu, ID_VIEWMENU);
  ASSERT(isubmenu_pos != -1);
  pSubMenu = pMainMenu->GetSubMenu(isubmenu_pos);

  // Do View Menu Filter submenu
  InsertShortcuts(pSubMenu, m_MapMenuShortcuts, ID_FILTERMENU);

  // Do View Menu ChangeFont submenu
  InsertShortcuts(pSubMenu, m_MapMenuShortcuts, ID_CHANGEFONTMENU);

  // Do View Menu Reports submenu
  InsertShortcuts(pSubMenu, m_MapMenuShortcuts, ID_REPORTSMENU);

  // Do Manage Menu
  InsertShortcuts(pMainMenu, m_MapMenuShortcuts, ID_MANAGEMENU);

  // No need to do Manage Menu Languages submenu as rebuilt every time!

  // Do Help Menu
  InsertShortcuts(pMainMenu, m_MapMenuShortcuts, ID_HELPMENU);

  // Don't need main menu again here
  brc = pMainMenu->DestroyMenu();
  ASSERT(brc != 0);

  // Do Find toolbar menu items not on a menu!
  brc = pMainMenu->LoadMenu(IDR_POPFIND);
  ASSERT(brc != 0);

  // Again a parent menu (uiParentID == 0)
  InsertShortcuts(pMainMenu, m_MapMenuShortcuts, 0);

  InsertShortcuts(pMainMenu, m_MapMenuShortcuts, ID_FINDMENU);

  // No longer need any menus
  brc = pMainMenu->DestroyMenu();
  ASSERT(brc != 0);

  delete pMainMenu;

  // Now that we have all menu strings - go get current accelerator strings
  // and update Map
  MapMenuShortcutsIter iter, iter_entry, inuse_iter;
  HACCEL curacctbl = app.m_ghAccelTable;
  //if (curacctbl != NULL) {
  //  DestroyAcceleratorTable(app.m_ghAccelTable);
  //  curacctbl = app.m_ghAccelTable = NULL;
  //}

  ACCEL *pacceltbl(NULL), *paccel(NULL);
  int numaccels;

  // Just in case user deletes all shortcuts!
  if (app.m_ghAccelTable != NULL) {
    numaccels = CopyAcceleratorTable(curacctbl, NULL, 0);
    pacceltbl = (LPACCEL)LocalAlloc(LPTR, numaccels * sizeof(ACCEL));
    ASSERT(pacceltbl != NULL);

    CopyAcceleratorTable(curacctbl, pacceltbl, numaccels);

    // Load our map with all the current info
    paccel = pacceltbl;
    for (int i = 0; i < numaccels; i++) {
      /*
        FALT     - The ALT key must be held down when the accelerator key is pressed.
        FCONTROL - The CTRL key must be held down when the accelerator key is pressed.
        FSHIFT   - The SHIFT key must be held down when the accelerator key is pressed.
        FVIRTKEY - The key member specifies a virtual-key code. If this flag is not specified,
                   key is assumed to specify a character code.
      */

      iter = m_MapMenuShortcuts.find((UINT)paccel->cmd);
      if (iter != m_MapMenuShortcuts.end()) {
        iter->second.siDefVirtKey = iter->second.siVirtKey = 
          (unsigned char)paccel->key;
          iter->second.cDefModifier = iter->second.cModifier =
          ((paccel->fVirt & FCONTROL) == FCONTROL ? HOTKEYF_CONTROL : 0) |
          ((paccel->fVirt & FALT)     == FALT     ? HOTKEYF_ALT     : 0) |
          ((paccel->fVirt & FSHIFT)   == FSHIFT   ? HOTKEYF_SHIFT   : 0) |
          (IsExtended((int)paccel->key)           ? HOTKEYF_EXT     : 0);
      }
      paccel++;
    }

    // Don't need copy any more
    LocalFree(pacceltbl);
    pacceltbl = paccel = NULL;
  }

  // Now go through the default table and see if the user has changed anything,
  // change shortcuts as per preferences
  std::vector<st_prefShortcut> vShortcuts(PWSprefs::GetInstance()->GetPrefShortcuts());

  // We need to convert from PWS to Hotkey modifiers
  for (size_t i = 0; i < vShortcuts.size(); i++) {
    WORD wPWSModifiers = vShortcuts[i].cModifier;
    // Translate from CHotKeyCtrl to PWS modifiers
    WORD wHKModifiers = ConvertModifersPWS2MFC(wPWSModifiers);
    vShortcuts[i].cModifier = (unsigned char)wHKModifiers;
  }
      
  size_t N = vShortcuts.size();
  for (size_t i = 0; i < N; i++) {
    const st_prefShortcut &stxst = vShortcuts[i];
    // User should not have these sub-entries in their config file
    if (stxst.id == ID_MENUITEM_GROUPENTER  ||
        stxst.id == ID_MENUITEM_VIEW        ||
        stxst.id == ID_MENUITEM_DELETEENTRY ||
        stxst.id == ID_MENUITEM_DELETEGROUP ||
        stxst.id == ID_MENUITEM_RENAME      ||
        stxst.id == ID_MENUITEM_RENAMEENTRY ||
        stxst.id == ID_MENUITEM_RENAMEGROUP) {
      continue;
    }
    iter = m_MapMenuShortcuts.find(stxst.id);
    if (iter == m_MapMenuShortcuts.end()) {
      // Unknown Control ID - ignore - maybe used by a later version of PWS
      continue;
    }
    // Check not already in use (ignore if deleting current shortcut)
    if (stxst.siVirtKey != 0) {
      st_mst.siVirtKey = stxst.siVirtKey;
      st_mst.cModifier = stxst.cModifier;
      already_inuse inuse(st_mst);
      inuse_iter = std::find_if(m_MapMenuShortcuts.begin(),
                                m_MapMenuShortcuts.end(),
                                inuse);
      if (inuse_iter != m_MapMenuShortcuts.end() && 
          inuse_iter->first != iter->first) {
        // Shortcut in use - ignore entry - duplicates not allowed!
        continue;
      }
    }
    if ((iter->second.siVirtKey  != stxst.siVirtKey  ||
         iter->second.cModifier != stxst.cModifier)) {
      // User changed a added or shortcut
      iter->second.siVirtKey  = stxst.siVirtKey;
      iter->second.cModifier = stxst.cModifier;
    }
  }

  // Set up the shortcuts based on the main entry
  // for View, Delete and Rename
  iter = m_MapMenuShortcuts.find(ID_MENUITEM_EDIT);
  ASSERT(iter != m_MapMenuShortcuts.end());
  iter_entry = m_MapMenuShortcuts.find(ID_MENUITEM_VIEW);
  ASSERT(iter_entry != m_MapMenuShortcuts.end());
  iter_entry->second.SetKeyFlags(iter->second);

  SetupSpecialShortcuts();

  UpdateAccelTable();
}

void DboxMain::UpdateAccelTable()
{
  ACCEL *pacceltbl, *pxatbl;
  int numscs(0);
  CountShortcuts cntscs;

  // Add on space of 3 reserved shortcuts (Ctrl-Q, F4, F1)
  numscs = (int)std::count_if(m_MapMenuShortcuts.begin(), m_MapMenuShortcuts.end(),
                         cntscs) + 3;
  // But take off 1 if there is a shortcut for AutoType
  if (m_wpAutotypeKey != 0)
    numscs--;

  // Get new table space
  pacceltbl = (LPACCEL)LocalAlloc(LPTR, numscs * sizeof(ACCEL));
  ASSERT(pacceltbl != NULL);

  // Populate it
  CreateAccelTable create_accel_table(pacceltbl, (unsigned char)m_wpAutotypeKey);
  for_each(m_MapMenuShortcuts.begin(), m_MapMenuShortcuts.end(), create_accel_table);

  // Add back in the 3 reserved
  pxatbl = pacceltbl + (numscs - 3);
  pxatbl->fVirt = FVIRTKEY | FCONTROL;
  pxatbl->key = (WORD)'Q';
  pxatbl->cmd = (WORD)ID_MENUITEM_EXIT;
  pxatbl++;
  pxatbl->fVirt = FVIRTKEY | FALT;
  pxatbl->key = (WORD)VK_F4;
  pxatbl->cmd = (WORD)ID_MENUITEM_EXIT;
  pxatbl++;
  pxatbl->fVirt = FVIRTKEY;
  pxatbl->key = (WORD)VK_F1;
  pxatbl->cmd = (WORD)ID_HELP;

  // Replace current one
  DestroyAcceleratorTable(app.m_ghAccelTable);
  app.m_ghAccelTable = CreateAcceleratorTable(pacceltbl, numscs);
  app.SetACCELTableCreated();
}

void DboxMain::SetUpMenuStrings(CMenu *pPopupMenu)
{
  // Can't use GetMenuItemID, as it does not understand that with the MENUEX
  // format, Popup menus can have IDs
  ASSERT_VALID(pPopupMenu);

  BOOL brc;
  MENUITEMINFO miteminfo = {0};

  MapMenuShortcutsIter iter;

  UINT uiCount = pPopupMenu->GetMenuItemCount();
  ASSERT((int)uiCount >= 0);

  for (UINT ui = 0; ui < uiCount; ui++) {
    SecureZeroMemory(&miteminfo, sizeof(miteminfo));
    miteminfo.cbSize = sizeof(miteminfo);
    miteminfo.fMask = MIIM_ID | MIIM_STATE;

    brc = pPopupMenu->GetMenuItemInfo(ui, &miteminfo, TRUE);
    ASSERT(brc != 0);

    // Exit & Help never changed and their shortcuts are in the menu text
    if (miteminfo.wID >= 1 &&
        miteminfo.wID != ID_MENUITEM_EXIT && miteminfo.wID != ID_HELP) {
      iter = m_MapMenuShortcuts.find(miteminfo.wID);
      if (iter != m_MapMenuShortcuts.end()) {
        CString str;
        if (iter->second.siVirtKey != 0) {
          str.Format(L"%s\t%s", iter->second.name.c_str(), 
                       CMenuShortcut::FormatShortcut(iter));
        } else {
          str = iter->second.name.c_str();
        }
        pPopupMenu->ModifyMenu(miteminfo.wID, MF_BYCOMMAND | miteminfo.fState, miteminfo.wID, str);
      }
    }
  }
}

void DboxMain::CustomiseMenu(CMenu *pPopupMenu, const UINT uiMenuID, 
                             const bool bDoShortcuts)
{
  // Original OnInitMenu code
  // All main menus are POPUPs (see PasswordSafe2.rc2)

  // This routine changes the text in the menu via "ModifyMenu" and
  // adds the "check" mark via CheckMenuRadioItem for view type and toolbar.
  // It then tailors the Edit menu depending on whether a Group or Entry is selected.
  // "EnableMenuItem" is handled by the UPDATE_UI routines
  bool bGroupSelected(false);
  const bool bTreeView = m_ctlItemTree.IsWindowVisible() == TRUE;
  const bool bItemSelected = (SelItemOk() == TRUE);
  const bool bReadOnly = m_core.IsReadOnly();
  const CItemData *pci(NULL);
  const wchar_t *tc_dummy = L" ";

  ASSERT_VALID(pPopupMenu);

  // Except for Edit & View menus, we only get here if the Shortcuts have changed and
  // we need to rebuild menu item text
  if (bDoShortcuts)
    SetUpMenuStrings(pPopupMenu);

  // Change the 'Change Mode' text as appropriate
  if (uiMenuID == ID_FILEMENU) {
    // Otherwise set the correct menu item text
    pPopupMenu->ModifyMenu(ID_MENUITEM_CHANGEMODE, MF_BYCOMMAND,
      ID_MENUITEM_CHANGEMODE,
      bReadOnly ? CS_READWRITE : CS_READONLY);

    // Disable menu item if no DB or if DB is read-only on disk
    bool bFileIsReadOnly;
    if (!m_bOpen || (pws_os::FileExists(m_core.GetCurFile().c_str(), bFileIsReadOnly) && bFileIsReadOnly)) {
      pPopupMenu->EnableMenuItem(ID_MENUITEM_CHANGEMODE, MF_BYCOMMAND | MF_GRAYED);
    }

    if (app.GetMRU()->GetNumUsed() == 0) {
      pPopupMenu->EnableMenuItem(ID_MENUITEM_CLEAR_MRU, MF_BYCOMMAND | MF_GRAYED);
      pPopupMenu->EnableMenuItem(ID_MENUITEM_MRUENTRY, MF_BYCOMMAND | MF_GRAYED);
    }

    // Remove the corresponding Export V3/V4
    int isubmenu_pos;
    CMenu *pSubMenu;
    isubmenu_pos = app.FindMenuItem(pPopupMenu, ID_EXPORTMENU);
    ASSERT(isubmenu_pos != -1);
    pSubMenu = pPopupMenu->GetSubMenu(isubmenu_pos);
    ASSERT_VALID(pSubMenu);

    const PWSfile::VERSION current_version = m_core.GetReadFileVersion();

    // Delete both menu items and then add the appropriate one
    pSubMenu->DeleteMenu(ID_MENUITEM_EXPORT2V3FORMAT, MF_BYCOMMAND);
    pSubMenu->DeleteMenu(ID_MENUITEM_EXPORT2V4FORMAT, MF_BYCOMMAND);

    switch (current_version) {
      case PWSfile::V30:
      case PWSfile::V40:
      {
        CString tmpStr(MAKEINTRESOURCE((current_version == PWSfile::V40) ?
                         IDS_MENUITEM_EXPORT2V3FORMAT : IDS_MENUITEM_EXPORT2V4FORMAT));
        
        MENUITEMINFO MII = {0};
        MII.cbSize = sizeof(MII);
        MII.fMask = MIIM_STRING | MIIM_ID;
        MII.fType = MFT_STRING;
        MII.fState = MFS_ENABLED;
        MII.wID = (current_version == PWSfile::V40) ? ID_MENUITEM_EXPORT2V3FORMAT : ID_MENUITEM_EXPORT2V4FORMAT;
        MII.hSubMenu = NULL;
        MII.hbmpChecked = NULL;
        MII.hbmpUnchecked = NULL;
        MII.dwTypeData = (LPTSTR)&tmpStr;
        MII.cch = tmpStr.GetLength();

        // Add after export to V2
        pSubMenu->InsertMenuItem(2, &MII, TRUE);
        break;
      }
      default:
        break;
    }

    return;
  }

  // We have done for all except the Edit and View menus
  if (uiMenuID != ID_EDITMENU && uiMenuID != ID_VIEWMENU && uiMenuID != ID_FILTERMENU)
    return;

  // If View menu selected (contains 'Flattened &List' menu item)
  if (uiMenuID == ID_VIEWMENU) {
    pPopupMenu->CheckMenuRadioItem(ID_MENUITEM_LIST_VIEW, ID_MENUITEM_TREE_VIEW,
                                   bTreeView ? ID_MENUITEM_TREE_VIEW : ID_MENUITEM_LIST_VIEW,
                                   MF_BYCOMMAND);

    pPopupMenu->CheckMenuItem(ID_MENUITEM_SHOWHIDE_TOOLBAR, MF_BYCOMMAND |
                              m_MainToolBar.IsWindowVisible() ? MF_CHECKED : MF_UNCHECKED);

    bool bDragBarState = PWSprefs::GetInstance()->GetPref(PWSprefs::ShowDragbar);
    pPopupMenu->CheckMenuItem(ID_MENUITEM_SHOWHIDE_DRAGBAR, MF_BYCOMMAND |
                              bDragBarState ? MF_CHECKED : MF_UNCHECKED);

    pPopupMenu->CheckMenuItem(ID_MENUITEM_SHOWHIDE_UNSAVED, MF_BYCOMMAND |
                              m_bUnsavedDisplayed ? MF_CHECKED : MF_UNCHECKED);

    pPopupMenu->CheckMenuItem(ID_MENUITEM_SHOW_ALL_EXPIRY, MF_BYCOMMAND |
                              m_bExpireDisplayed ? MF_CHECKED : MF_UNCHECKED);

    // Don't show filter menu if "internal" menu active
    pPopupMenu->EnableMenuItem(ID_FILTERMENU, MF_BYCOMMAND |
             (m_bUnsavedDisplayed || m_bExpireDisplayed) ? MF_GRAYED : MF_ENABLED);

    pPopupMenu->CheckMenuRadioItem(ID_MENUITEM_NEW_TOOLBAR,
                                   ID_MENUITEM_OLD_TOOLBAR,
                                   m_toolbarMode, MF_BYCOMMAND);

    // Disable View Reports menu if no database open as we need the full path
    // of the database to get to its reports!
    pPopupMenu->EnableMenuItem(ID_REPORTSMENU, MF_BYCOMMAND | (m_bOpen ? MF_ENABLED : MF_GRAYED));
    goto exit;
  }  // View menu

  if (uiMenuID == ID_FILTERMENU) {
    pPopupMenu->ModifyMenu(1, MF_BYPOSITION,
                           m_bFilterActive ? ID_MENUITEM_CLEARFILTER : ID_MENUITEM_APPLYFILTER,
                           m_bFilterActive ? CS_CLEARFILTERS : CS_SETFILTERS);
    goto exit;
  }

  if (bItemSelected) {
    pci = getSelectedItem();
    ASSERT(pci != NULL);
  }

  // Save original entry type before possibly changing pci
  const CItemData::EntryType etype_original = 
          pci == NULL ? CItemData::ET_INVALID : pci->GetEntryType();

  if (bItemSelected && pci->IsShortcut())
    pci = m_core.GetBaseEntry(pci);

  if (bTreeView) {
    HTREEITEM hi = m_ctlItemTree.GetSelectedItem();
    bGroupSelected = (hi != NULL && !m_ctlItemTree.IsLeaf(hi));
  }

  // If Edit menu selected
  if (uiMenuID == ID_EDITMENU) {
    // Delete all entries and rebuild depending on group/entry selected
    // and, if entry, if fields are non-empty
    UINT uiCount = pPopupMenu->GetMenuItemCount();
    ASSERT((int)uiCount >= 0);

    for (UINT ui = 0; ui < uiCount; ui++) {
      pPopupMenu->RemoveMenu(0, MF_BYPOSITION);
    }

    /*
     Three scenarios:
     Note: If the database is read-only, anything modifying it is not added.
     1. Nothing selected
       Tree View - Add Entry, Sep, Add Group, Sep, Clear Clipboard
       List View - Add Entry, Sep, Clear Clipboard
     2. Group selected (Tree view only)
       Add Entry, Sep, <Group Items>, Sep, Clear Clipboard
       Add Group, Duplicate Group, (Un)Protect entries
     3. Entry selected
       Tree View - <Entry Items>, Sep, Add Group, Sep, Clear Clipboard, Sep
                   Entry functions (copy to clipboard, browse, run etc)
       List View - <Entry Items>, Sep, Clear Clipboard, Sep
                   Entry functions (copy to clipboard, browse, run etc)
    */

    // Scenario 1 - Nothing selected
    //   Tree View - Add Entry, Sep, Add Group, Sep, Clear Clipboard
    //   List View - Add Entry, Sep, Clear Clipboard
    if (pci == NULL && !bGroupSelected) {
      if (m_IsListView) {
        if (!bReadOnly) {
          pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                 ID_MENUITEM_ADD, tc_dummy);
          pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
        }
      } else {
        if (!bReadOnly) {
          pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                 ID_MENUITEM_ADD, tc_dummy);
          pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                 ID_MENUITEM_ADDGROUP, tc_dummy);
          pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
        }
      }
      if (m_core.AnyToUndo() || m_core.AnyToRedo()) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_UNDO, tc_dummy);
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_REDO, tc_dummy);
        pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
      }
      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             ID_MENUITEM_CLEARCLIPBOARD, tc_dummy);
      goto exit;
    }

    // Scenario 2 - Group selected (Tree view only)
    //   Add Entry, Find, Sep, <Group Items>, Sep, Clear Clipboard
    if (!m_IsListView && bGroupSelected) {
      if (!bReadOnly) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_ADD, tc_dummy);
      }

      // Add Find Next/Previous if find still active and entries were found
      if (m_FindToolBar.IsVisible() && m_FindToolBar.EntriesFound()) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_FIND, tc_dummy);
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_FINDUP, tc_dummy);
      } 

      // Always add "Find..."
      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                              ID_MENUITEM_FINDELLIPSIS, tc_dummy);

      pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             ID_MENUITEM_GROUPENTER, tc_dummy);
      
      if (!bReadOnly) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_DELETEGROUP, tc_dummy);
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_RENAMEGROUP, tc_dummy);
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_ADDGROUP, tc_dummy);
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_DUPLICATEGROUP, tc_dummy);
        int numProtected, numUnprotected;
        bool bProtect = GetSubtreeEntriesProtectedStatus(numProtected, numUnprotected);
        if (bProtect) {
          if (numUnprotected > 0)
            pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                   ID_MENUITEM_PROTECTGROUP, tc_dummy);
          if (numProtected > 0)
            pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                   ID_MENUITEM_UNPROTECTGROUP, tc_dummy);
        }
      }

      // Only allow export of a group to anything if non-empty
      if (m_ctlItemTree.CountLeafChildren(m_ctlItemTree.GetSelectedItem()) != 0) {
        CMenu GGsubMenu;
        CString GGstr;
        GGsubMenu.CreatePopupMenu();
        // Re-use entry menu texts
        GGstr.LoadString(IDS_EXPORTENT2PLAINTEXT);
        GGsubMenu.AppendMenu(MF_ENABLED | MF_STRING,
          ID_MENUITEM_EXPORTGRP2PLAINTEXT, GGstr);
        GGstr.LoadString(IDS_EXPORTENT2XML);
        GGsubMenu.AppendMenu(MF_ENABLED | MF_STRING,
          ID_MENUITEM_EXPORTGRP2XML, GGstr);
        GGstr.LoadString(IDS_EXPORTENT2DB);
        GGsubMenu.AppendMenu(MF_ENABLED | MF_STRING,
          ID_MENUITEM_EXPORTGRP2DB, GGstr);
        GGstr.LoadString(IDS_EXPORTGRPMENU);
        pPopupMenu->AppendMenu(MF_POPUP, (UINT)GGsubMenu.Detach(), GGstr);
      }

      pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
      if (m_core.AnyToUndo() || m_core.AnyToRedo()) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_UNDO, tc_dummy);
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_REDO, tc_dummy);
        pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
      }
      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             ID_MENUITEM_CLEARCLIPBOARD, tc_dummy);
      goto exit;
    }

    // Scenario 3 - Entry selected
    //   Tree View - <Entry Items>, Sep, Add Entry, Add Group, Sep, Clear Clipboard, Sep,
    //               Entry functions (copy to clipboard, browse, run etc)
    //   List View - <Entry Items>, Sep, Add Entry, Sep, Clear Clipboard, Sep,
    //               Entry functions (copy to clipboard, browse, run etc)
    if (pci != NULL) {
      // Entry is selected

      // Deal with multi-selection
      // More than 2 is meaningless in List view
      if (m_IsListView && m_ctlItemList.GetSelectedCount() > 2)
        return;

      // If exactly 2 selected - show compare entries menu
      if (m_IsListView  && m_ctlItemList.GetSelectedCount() == 2) {
        CString cs_txt(MAKEINTRESOURCE(ID_MENUITEM_COMPARE_ENTRIES));
        cs_txt.TrimLeft();  // Remove leading newline
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_COMPARE_ENTRIES, cs_txt);
        return;
      }

      if (!bReadOnly) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_ADD, tc_dummy);
      }

      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             m_core.IsReadOnly() ? ID_MENUITEM_VIEW : ID_MENUITEM_EDIT,
                             tc_dummy);
      if (!bReadOnly) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_DELETEENTRY, tc_dummy);
        if (!m_IsListView) {
          // Rename not valid in List View
          pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                 ID_MENUITEM_RENAMEENTRY, tc_dummy);
        }
      }

      // Only have Find Next/Previous if find still active and entries were found
      if (m_FindToolBar.IsVisible() && m_FindToolBar.EntriesFound()) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_FIND, tc_dummy);
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_FINDUP, tc_dummy);
      }

      // Always add "Find..."
      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             ID_MENUITEM_FINDELLIPSIS, tc_dummy);

      if (!bReadOnly) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_DUPLICATEENTRY, tc_dummy);
        if (!m_IsListView) {
          pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
          pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                 ID_MENUITEM_ADDGROUP, tc_dummy);
        }
      }

      pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
      
      if (m_core.AnyToUndo() || m_core.AnyToRedo()) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_UNDO, tc_dummy);
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_REDO, tc_dummy);
        pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
      }

      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             ID_MENUITEM_CLEARCLIPBOARD, tc_dummy);
      pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             ID_MENUITEM_COPYPASSWORD, tc_dummy);
      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             ID_MENUITEM_PASSWORDSUBSET, tc_dummy);

      if (!pci->IsUserEmpty())
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_COPYUSERNAME, tc_dummy);

      if (!pci->IsNotesEmpty())
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_COPYNOTESFLD, tc_dummy);

      /*
      *  Rules:
      *    1. If email field is not empty, add email menuitem.
      *    2. If URL is not empty and is NOT an email address, add browse menuitem
      *    3. If URL is not empty and is an email address, add email menuitem
      *       (if not already added)
      */
      bool bAddCopyEmail = !pci->IsEmailEmpty();
      bool bAddSendEmail = bAddCopyEmail || (!pci->IsURLEmpty() && pci->IsURLEmail());
      bool bAddURL = !pci->IsURLEmpty();

      // Add copies in order
      if (bAddURL) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_COPYURL, tc_dummy);
      }

      if (bAddCopyEmail) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_COPYEMAIL, tc_dummy);
      }

      if (!pci->IsRunCommandEmpty())
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_COPYRUNCOMMAND, tc_dummy);

      pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);

      // Add actions in order
      if (bAddURL && !pci->IsURLEmail()) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_BROWSEURL, tc_dummy);
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_BROWSEURLPLUS, tc_dummy);
      }

      if (bAddSendEmail) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_SENDEMAIL, tc_dummy);
      }

      if (!pci->IsRunCommandEmpty()) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_RUNCOMMAND, tc_dummy);
      }

      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             ID_MENUITEM_AUTOTYPE, tc_dummy);

      switch (etype_original) {
        case CItemData::ET_NORMAL:
        case CItemData::ET_SHORTCUTBASE:
          // Allow creation of a shortcut
          if (!bReadOnly) {
            pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                   ID_MENUITEM_CREATESHORTCUT, tc_dummy);
          }
          break;
        case CItemData::ET_ALIASBASE:
          // Can't have a shortcut to an AliasBase entry + can't goto base
          break;
        case CItemData::ET_ALIAS:
        case CItemData::ET_SHORTCUT:
          // Allow going to/editing the appropriate base entry
          pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                 ID_MENUITEM_GOTOBASEENTRY, tc_dummy); 
          pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                                 ID_MENUITEM_EDITBASEENTRY, tc_dummy);
         break;
        default:
          ASSERT(0);
      }

      if (pci->HasAttRef()) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_VIEWATTACHMENT, tc_dummy);
      }

      CMenu EEsubMenu;
      CString EEstr;
      EEsubMenu.CreatePopupMenu();
      EEstr.LoadString(IDS_EXPORTENT2PLAINTEXT);
      EEsubMenu.AppendMenu(MF_ENABLED | MF_STRING,
                           ID_MENUITEM_EXPORTENT2PLAINTEXT, EEstr); 
      EEstr.LoadString(IDS_EXPORTENT2XML);
      EEsubMenu.AppendMenu(MF_ENABLED | MF_STRING,
                          ID_MENUITEM_EXPORTENT2XML, EEstr);
      EEstr.LoadString(IDS_EXPORTENT2DB);
      EEsubMenu.AppendMenu(MF_ENABLED | MF_STRING,
                           ID_MENUITEM_EXPORTENT2DB, EEstr);
      EEstr.LoadString(IDS_EXPORTENTMENU);
      pPopupMenu->AppendMenu(MF_POPUP, (UINT)EEsubMenu.Detach(), EEstr);

      if (pci->HasAttRef()) {
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
          ID_MENUITEM_EXPORT_ATTACHMENT, tc_dummy);
      }

      if (!bReadOnly && etype_original != CItemData::ET_SHORTCUT)
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               pci->IsProtected() ? ID_MENUITEM_UNPROTECT : ID_MENUITEM_PROTECT,
                               tc_dummy);

      // Tree view and command flag present only
      if (!m_IsListView && m_bCompareEntries &&
           etype_original != CItemData::ET_SHORTCUT)
        pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                               ID_MENUITEM_COMPARE_ENTRIES, tc_dummy);
    } else {
      // Must be List view with no entry selected
      pPopupMenu->InsertMenu((UINT)-1, MF_SEPARATOR);
      pPopupMenu->AppendMenu(MF_ENABLED | MF_STRING,
                             ID_MENUITEM_CLEARCLIPBOARD, tc_dummy);
    }
  }  // Edit menu

exit:
    SetUpMenuStrings(pPopupMenu);
}

// Helps with MRU by allowing ON_UPDATE_COMMAND_UI
void DboxMain::OnInitMenuPopup(CMenu* pPopupMenu, UINT, BOOL)
{
  // Original OnInitMenu code
  // All main menus are POPUPs (see PasswordSafe2.rc2)

  // Don't do the old OnInitMenu processing if the right-click context menu
  // (IDR_POPEDITMENU or IDR_POPEDITGROUP) is being processed. Only for the Main Menu
  // (ID_EDITMENU).
  ASSERT_VALID(pPopupMenu);

  BOOL brc;
  MENUINFO minfo = {0};
  minfo.cbSize = sizeof(MENUINFO);
  minfo.fMask = MIM_MENUDATA;

  brc = pPopupMenu->GetMenuInfo(&minfo);
  ASSERT(brc != 0);

  // Need to update the main menu if the shortcuts have been changed or
  // if the Edit or View menu.  The Edit menu is completely rebuilt each time
  // based on the view and if anything is selected. The View menu has one item
  // (ShowFindToolbar) that is removed if already displayed

  bool bDoShortcuts(false);
  switch (minfo.dwMenuData) {
    case ID_FILEMENU:
    case ID_EXPORTMENU:
    case ID_IMPORTMENU:
    case ID_EDITMENU:
    case ID_VIEWMENU:
    case ID_FILTERMENU:
    case ID_EXPORTENTMENU:
    case ID_EXPORTGROUPMENU:
    case ID_CHANGEFONTMENU:
    case ID_REPORTSMENU:
    case ID_MANAGEMENU:
    case ID_HELPMENU:  //main menu items' shortcut should be always updated because keyboard layout could be changed
      bDoShortcuts = true;
      break;
    default:
      break;
  }

  if (bDoShortcuts ||
      minfo.dwMenuData == ID_FILEMENU ||
      minfo.dwMenuData == ID_EDITMENU ||
      minfo.dwMenuData == ID_VIEWMENU)
    CustomiseMenu(pPopupMenu, (UINT)minfo.dwMenuData, bDoShortcuts);

  if (minfo.dwMenuData == ID_MANAGEMENU) {
    // Process the Change Language sub-menu
    // First get its position and then build it
    // If no entries added (no language DLLs or back level language DLLs),
    // disable the menu item.
    int iLangPos = app.FindMenuItem(pPopupMenu, ID_LANGUAGEMENU);
    if (iLangPos >= 0) {
      CMenu *pSubMenu = pPopupMenu->GetSubMenu(iLangPos);
      brc = ProcessLanguageMenu(pSubMenu);
      pPopupMenu->EnableMenuItem(iLangPos, MF_BYPOSITION | (brc ? MF_ENABLED : MF_GRAYED));
    }
  }

  // http://www4.ncsu.edu:8030/~jgbishop/codetips/dialog/updatecommandui_menu.html
  // This code comes from the MFC Documentation, and is adapted from
  // CFrameWnd::OnInitMenuPopup() in WinFrm.cpp.
  CCmdUI state; // Check the enabled state of various menu items
  state.m_pMenu = pPopupMenu;

  ASSERT(state.m_pOther == NULL);
  ASSERT(state.m_pParentMenu == NULL);

  // Is the menu in question a popup in the top-level menu? If so, set m_pOther
  // to this menu. Note that m_pParentMenu == NULL indicates that the menu is a
  // secondary popup.
  CMenu *hParentMenu;
  if (AfxGetThreadState()->m_hTrackingMenu == pPopupMenu->m_hMenu) {
    state.m_pParentMenu = pPopupMenu; // Parent == child for tracking popup.
  } else
  if ((hParentMenu = this->GetMenu()) != NULL) {
    CWnd* pParent = this;
    // Child windows don't have menus--need to go to the top!
    if (pParent != NULL && (hParentMenu = pParent->GetMenu()) != NULL) {
      int nIndexMax = hParentMenu->GetMenuItemCount();
      for (int nIndex = 0; nIndex < nIndexMax; nIndex++) {
        CMenu *submenu = hParentMenu->GetSubMenu(nIndex);
        if (submenu != NULL && submenu->m_hMenu == pPopupMenu->m_hMenu) {
          // When popup is found, m_pParentMenu is containing menu.
          state.m_pParentMenu = CMenu::FromHandle(hParentMenu->GetSafeHmenu());
          break;
        }
      }
    }
  }

  state.m_nIndexMax = pPopupMenu->GetMenuItemCount();
  for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex++) {
    state.m_nID = pPopupMenu->GetMenuItemID(state.m_nIndex);
    if (state.m_nID == 0)
      continue; // Menu separator or invalid cmd - ignore it.

    ASSERT(state.m_pOther == NULL);
    ASSERT(state.m_pMenu != NULL);
    if (state.m_nID == (UINT)-1) {
      // Possibly a popup menu, route to first item of that popup.
      state.m_pSubMenu = pPopupMenu->GetSubMenu(state.m_nIndex);
      if (state.m_pSubMenu == NULL ||
          (state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||
          state.m_nID == (UINT)-1) {
        continue; // First item of popup can't be routed to.
      }
      state.DoUpdate(this, TRUE); // Popups are never auto disabled.
    } else {
      // Normal menu item.
      // Auto enable/disable if frame window has m_bAutoMenuEnable
      // set and command is _not_ a system command.
      state.m_pSubMenu = NULL;
      state.DoUpdate(this, FALSE);
    }

    // Adjust for menu deletions and additions.
    UINT nCount = pPopupMenu->GetMenuItemCount();
    if (nCount < state.m_nIndexMax) {
      state.m_nIndex -= (state.m_nIndexMax - nCount);
      while(state.m_nIndex < nCount &&
            pPopupMenu->GetMenuItemID(state.m_nIndex) == state.m_nID) {
        state.m_nIndex++;
      }
    }
    state.m_nIndexMax = nCount;
  }

  // Ignore SystemTray popup menu
  if (!m_bImageInLV || 
       (minfo.dwMenuData != IDR_POPTRAY && 
        minfo.dwMenuData != IDR_POPCOPYTOORIGINAL && 
        minfo.dwMenuData != IDR_POPEDITVIEWORIGINAL))
    return;

  // System Tray Popup menu processing only
  SecureZeroMemory(&minfo, sizeof(minfo));
  minfo.cbSize = sizeof(minfo);
  minfo.fMask = MIM_STYLE;
  minfo.dwStyle = MNS_CHECKORBMP | MNS_AUTODISMISS;

  brc = pPopupMenu->SetMenuInfo(&minfo);
  ASSERT(brc != 0);

  MENUITEMINFO miteminfo = {0};
  CRUEItemData *pmd;

  UINT uiCount = pPopupMenu->GetMenuItemCount();
  ASSERT((int)uiCount >= 0);

  for (UINT pos = 0; pos < uiCount; pos++) {
    SecureZeroMemory(&miteminfo, sizeof(miteminfo));
    miteminfo.cbSize = sizeof(miteminfo);
    miteminfo.fMask = MIIM_FTYPE | MIIM_DATA;

    brc = pPopupMenu->GetMenuItemInfo(pos, &miteminfo, TRUE);
    ASSERT(brc != 0);

    pmd = (CRUEItemData *)miteminfo.dwItemData;
    if (pmd && pmd->IsRUEID() && !(miteminfo.fType & MFT_OWNERDRAW) &&
        pmd->nImage >= 0) {
      SecureZeroMemory(&miteminfo, sizeof(miteminfo));
      miteminfo.cbSize = sizeof(miteminfo);
      miteminfo.fMask = MIIM_FTYPE | MIIM_BITMAP;
      miteminfo.hbmpItem = HBMMENU_CALLBACK;
      miteminfo.fType = MFT_STRING;

      brc = pPopupMenu->SetMenuItemInfo(pos, &miteminfo, TRUE);
      ASSERT(brc != 0);
    }
  }
}

// Called when right-click is invoked in the client area of the window.
void DboxMain::OnContextMenu(CWnd * /* pWnd */, CPoint screen)
{
  const DWORD dwTrackPopupFlags = TPM_LEFTALIGN | TPM_RIGHTBUTTON;

  BOOL brc;
  CPoint client;
  int item = -1;
  const CItemData *pci = NULL;
  CMenu menu;

  MENUINFO minfo ={0};
  minfo.cbSize = sizeof(MENUINFO);
  minfo.fMask = MIM_MENUDATA;

  // Note if point = (-1, -1) then invoked via keyboard.
  // Need coordinates of current selected item instead of mouse position when message sent
  bool bKeyboard = (screen.x == -1 && screen.y == -1);

  CPoint mp; // Screen coords (from "message point" or via Shift+F10 selected item
  CRect rect, appl_rect;

  // Get client window position
  if (bKeyboard) {
    CRect r;
    if (m_ctlItemList.IsWindowVisible()) {
      POSITION pos = m_ctlItemList.GetFirstSelectedItemPosition();
      if (pos == NULL)
        return;  // Nothing selected!

      int nIndex = m_ctlItemList.GetNextSelectedItem(pos);
      m_ctlItemList.GetItemRect(nIndex, &r, LVIR_LABEL);
      m_ctlItemList.ClientToScreen(&r);
    } else
    if (m_ctlItemTree.IsWindowVisible()) {
      HTREEITEM hItem = m_ctlItemTree.GetSelectedItem();
      if (hItem == NULL)
        return;  // Nothing selected!

      m_ctlItemTree.GetItemRect(hItem, &r, TRUE);
      m_ctlItemTree.ClientToScreen(&r);
    }
    mp.x = (r.left + r.right) / 2;
    mp.y = (r.top + r.bottom) / 2;
    screen = mp;  // In screen coords
  } else {
    mp = ::GetMessagePos();
  }
  GetWindowRect(&appl_rect);
  m_MainToolBar.GetWindowRect(&rect);

  // RClick over Main Toolbar - allow Customize Main toolbar
  if (m_MainToolBar.IsVisible() &&
      mp.x > appl_rect.left && mp.x < appl_rect.right &&
      mp.y > rect.top && mp.y < rect.bottom) {
    if (menu.LoadMenu(IDR_POPCUSTOMIZETOOLBAR)) {
      minfo.dwMenuData = IDR_POPCUSTOMIZETOOLBAR;
      brc = menu.SetMenuInfo(&minfo);
      ASSERT(brc != 0);

      CMenu* pPopup = menu.GetSubMenu(0);
      ASSERT_VALID(pPopup);

      // Use this DboxMain for commands
      pPopup->TrackPopupMenu(dwTrackPopupFlags, screen.x, screen.y, this);
    }
    return;
  }

  if (!m_bOpen)
    return;

  client = screen;
  // RClick over ListView
  if (m_ctlItemList.IsWindowVisible()) {
    // currently in flattened list view.
    m_ctlItemList.GetWindowRect(&rect);
    if (mp.x < rect.left || mp.x > appl_rect.right ||
        mp.y < rect.top || mp.y > rect.bottom) {
      // But not in the window
      return;
    }

    m_ctlItemList.ScreenToClient(&client);
    item = m_ctlItemList.HitTest(client);
    if (item < 0)
      return; // right click on empty list

    pci = (CItemData *)m_ctlItemList.GetItemData(item);
    UpdateToolBarForSelectedItem(pci);
    if (SelectEntry(item) == 0) {
      return;
    }
    m_ctlItemList.SetFocus();
  }

  // RClick over TreeView
  if (m_ctlItemTree.IsWindowVisible()) {
    // currently in tree view
    m_ctlItemTree.GetWindowRect(&rect);
    if (mp.x < rect.left || mp.x > appl_rect.right ||
        mp.y < rect.top || mp.y > rect.bottom) {
      // But not in the window
      return;
    }
    ASSERT(m_ctlItemTree.IsWindowVisible());

    m_ctlItemTree.ScreenToClient(&client);
    HTREEITEM ti = m_ctlItemTree.HitTest(client);
    if (ti != NULL) {
      pci = (CItemData *)m_ctlItemTree.GetItemData(ti);
      UpdateToolBarForSelectedItem(pci);
      if (pci != NULL) {
        // right-click was on an item (LEAF of some kind: normal, alias, shortcut)
        DisplayInfo *pdi = (DisplayInfo *)pci->GetDisplayInfo();
        ASSERT(pdi != NULL);
        ASSERT(pdi->tree_item == ti);
        item = pdi->list_index;
        m_ctlItemTree.SelectItem(ti); // So that OnEdit gets the right one
      } else {
        // right-click was on a group (GROUP)
        m_ctlItemTree.SelectItem(ti);
        if (menu.LoadMenu(IDR_POPEDITGROUP)) {
          minfo.dwMenuData = IDR_POPEDITGROUP;
          menu.SetMenuInfo(&minfo);

          CMenu *pPopup = menu.GetSubMenu(0);
          ASSERT_VALID(pPopup);
          m_TreeViewGroup = m_mapTreeItemToGroup[ti];

          // Deal with empty groups before removing protect menu items
          if (m_ctlItemTree.CountLeafChildren(ti) == 0) {
            // This is an empty group or, if it has sub-groups, they are
            // all empty too - so remove export to anything
            CMenu *pExportPopup = pPopup->GetSubMenu(7);
            pExportPopup->RemoveMenu(ID_MENUITEM_EXPORTGRP2PLAINTEXT, MF_BYCOMMAND);
            pExportPopup->RemoveMenu(ID_MENUITEM_EXPORTGRP2XML, MF_BYCOMMAND);
            pExportPopup->RemoveMenu(ID_MENUITEM_EXPORTGRP2DB, MF_BYCOMMAND);

            // Since nothing left, remove this pop completely
            pPopup->RemoveMenu(7, MF_BYPOSITION);
          }

          int numProtected, numUnprotected;
          bool bProtect = GetSubtreeEntriesProtectedStatus(numProtected, numUnprotected);

          if (!bProtect || numUnprotected == 0)
            pPopup->RemoveMenu(ID_MENUITEM_PROTECTGROUP, MF_BYCOMMAND);
          if (!bProtect || numProtected == 0)
            pPopup->RemoveMenu(ID_MENUITEM_UNPROTECTGROUP, MF_BYCOMMAND);

          // Use this DboxMain for commands
          pPopup->TrackPopupMenu(dwTrackPopupFlags, screen.x, screen.y, this);
        }
      }
    } else {
      // not over anything
      if (menu.LoadMenu(IDR_POPTREE)) {  // "Add Group"
        minfo.dwMenuData = IDR_POPTREE;
        brc = menu.SetMenuInfo(&minfo);
        ASSERT(brc != 0);
        CMenu* pPopup = menu.GetSubMenu(0);
        ASSERT_VALID(pPopup);
        
        ti = m_ctlItemTree.GetSelectedItem();
        if (ti == NULL || (ti != NULL && (CItemData *)m_ctlItemTree.GetItemData(ti) != NULL))
          pPopup->RemoveMenu(ID_MENUITEM_DUPLICATEGROUP, MF_BYCOMMAND);

        // Use this DboxMain for commands
        m_bWhitespaceRightClick = true;
        pPopup->TrackPopupMenu(dwTrackPopupFlags, screen.x, screen.y, this);
      }
    }
    m_ctlItemTree.SetFocus();
  } // tree view handling

  // RClick over an entry
  if (item >= 0) {
    CMenu *pPopup(NULL);

    // More than 2 is meaningless in List view
    if (m_IsListView && m_ctlItemList.GetSelectedCount() > 2)
      return;

    // Only compare entries if 2 entries are selected - List view only
    if (m_IsListView && m_ctlItemList.GetSelectedCount() == 2) {
      brc = menu.LoadMenu(IDR_POPCOMPAREENTRIES);
      ASSERT(brc != 0);

      minfo.dwMenuData = IDR_POPCOMPAREENTRIES;
      brc = menu.SetMenuInfo(&minfo);
      ASSERT(brc != 0);

      pPopup = menu.GetSubMenu(0);
      ASSERT_VALID(pPopup);

      // Use this DboxMain for commands
      pPopup->TrackPopupMenu(dwTrackPopupFlags, screen.x, screen.y, this);
      return;
    }

    ASSERT(pci != NULL);
    brc = menu.LoadMenu(IDR_POPEDITMENU);
    ASSERT(brc != 0);

    minfo.dwMenuData = IDR_POPEDITMENU;
    brc = menu.SetMenuInfo(&minfo);
    ASSERT(brc != 0);

    pPopup = menu.GetSubMenu(0);
    ASSERT_VALID(pPopup);

    const CItemData::EntryType etype_original = pci->GetEntryType();
    switch (etype_original) {
      case CItemData::ET_NORMAL:
      case CItemData::ET_SHORTCUTBASE:
        pPopup->RemoveMenu(ID_MENUITEM_GOTOBASEENTRY, MF_BYCOMMAND);
        pPopup->RemoveMenu(ID_MENUITEM_EDITBASEENTRY, MF_BYCOMMAND);
        break;
      case CItemData::ET_ALIASBASE:
        pPopup->RemoveMenu(ID_MENUITEM_CREATESHORTCUT, MF_BYCOMMAND);
        pPopup->RemoveMenu(ID_MENUITEM_GOTOBASEENTRY, MF_BYCOMMAND);
        pPopup->RemoveMenu(ID_MENUITEM_EDITBASEENTRY, MF_BYCOMMAND);
        break;
      case CItemData::ET_ALIAS:
      case CItemData::ET_SHORTCUT:
        pPopup->RemoveMenu(ID_MENUITEM_CREATESHORTCUT, MF_BYCOMMAND);
        break;
      default:
        ASSERT(0);
    }

    if (m_core.IsReadOnly() || pci->IsShortcut()) {
      pPopup->RemoveMenu(ID_MENUITEM_PROTECT, MF_BYCOMMAND);
      pPopup->RemoveMenu(ID_MENUITEM_UNPROTECT, MF_BYCOMMAND);
    } else {
      pPopup->RemoveMenu(pci->IsProtected() ? ID_MENUITEM_PROTECT : ID_MENUITEM_UNPROTECT, MF_BYCOMMAND);
    }

    // NOTE: after here, if an entry is a Shortcut, pci points to its base entry!
    if (pci->IsShortcut()) {
      pci = m_core.GetBaseEntry(pci);
    }

    bool bCopyEmail = !pci->IsEmailEmpty();
    bool bSendEmail = bCopyEmail || (!pci->IsURLEmpty() && pci->IsURLEmail());
    bool bUseURL = !pci->IsURLEmpty() && !pci->IsURLEmail();

    if (pci->IsUserEmpty())
      pPopup->RemoveMenu(ID_MENUITEM_COPYUSERNAME, MF_BYCOMMAND);

    if (pci->IsNotesEmpty())
      pPopup->RemoveMenu(ID_MENUITEM_COPYNOTESFLD, MF_BYCOMMAND);

    if (!bCopyEmail)
      pPopup->RemoveMenu(ID_MENUITEM_COPYEMAIL, MF_BYCOMMAND);

    if (!bSendEmail)
      pPopup->RemoveMenu(ID_MENUITEM_SENDEMAIL, MF_BYCOMMAND);

    if (pci->IsURLEmpty())
      pPopup->RemoveMenu(ID_MENUITEM_COPYURL, MF_BYCOMMAND);

    if (!bUseURL) {
      pPopup->RemoveMenu(ID_MENUITEM_BROWSEURL, MF_BYCOMMAND);
      pPopup->RemoveMenu(ID_MENUITEM_BROWSEURLPLUS, MF_BYCOMMAND);
    }

    if (pci->IsRunCommandEmpty()) {
      pPopup->RemoveMenu(ID_MENUITEM_COPYRUNCOMMAND, MF_BYCOMMAND);
      pPopup->RemoveMenu(ID_MENUITEM_RUNCOMMAND, MF_BYCOMMAND);
    }

    if (!pci->HasAttRef()) {
      pPopup->RemoveMenu(ID_MENUITEM_EXPORT_ATTACHMENT, MF_BYCOMMAND);
      pPopup->RemoveMenu(ID_MENUITEM_VIEWATTACHMENT, MF_BYCOMMAND);
    }

    if (m_IsListView) {
      // Rename not valid in List View
      pPopup->RemoveMenu(ID_MENUITEM_RENAMEENTRY, MF_BYCOMMAND);
    }

    // Since an entry - remove Duplicate Group
    pPopup->RemoveMenu(ID_MENUITEM_DUPLICATEGROUP, MF_BYCOMMAND);

    // Remove if in List View, not allowed in Tree view or a shortcut
    if (m_IsListView || !m_bCompareEntries ||
        etype_original == CItemData::ET_SHORTCUT)
        pPopup->RemoveMenu(ID_MENUITEM_COMPARE_ENTRIES, MF_BYCOMMAND);

    // Use this DboxMain for commands
    pPopup->TrackPopupMenu(dwTrackPopupFlags, screen.x, screen.y, this);
  } // if (item >= 0)
}

void DboxMain::SetupSpecialShortcuts()
{
  MapMenuShortcutsIter iter, iter_entry, iter_group;

  // Find Delete Shortcut
  iter = m_MapMenuShortcuts.find(ID_MENUITEM_DELETE);

  // Save for CTreeCtrl & CListCtrl PreTranslateMessage
  if (iter != m_MapMenuShortcuts.end()) {
    iter_entry = m_MapMenuShortcuts.find(ID_MENUITEM_DELETEENTRY);
    iter_entry->second.SetKeyFlags(iter->second);
    iter_group = m_MapMenuShortcuts.find(ID_MENUITEM_DELETEGROUP);
    iter_group->second.SetKeyFlags(iter->second);

    m_wpDeleteMsg = ((iter->second.cModifier & HOTKEYF_ALT) == HOTKEYF_ALT) ?
                           WM_SYSKEYDOWN : WM_KEYDOWN;
    m_wpDeleteKey = iter->second.siVirtKey;
    m_bDeleteCtrl = (iter->second.cModifier  & HOTKEYF_CONTROL) == HOTKEYF_CONTROL;
    m_bDeleteShift = (iter->second.cModifier & HOTKEYF_SHIFT) == HOTKEYF_SHIFT;
  } else {
    m_wpDeleteKey = 0;
  }

  // Find Rename Shortcut
  iter = m_MapMenuShortcuts.find(ID_MENUITEM_RENAME);
  
  // Save for CTreeCtrl::PreTranslateMessage (not CListCtrl)
  if (iter != m_MapMenuShortcuts.end()) {
    iter_entry = m_MapMenuShortcuts.find(ID_MENUITEM_RENAMEENTRY);
    iter_entry->second.SetKeyFlags(iter->second);
    iter_group = m_MapMenuShortcuts.find(ID_MENUITEM_RENAMEGROUP);
    iter_group->second.SetKeyFlags(iter->second);

    m_wpRenameMsg = ((iter->second.cModifier & HOTKEYF_ALT) == HOTKEYF_ALT) ?
                           WM_SYSKEYDOWN : WM_KEYDOWN;
    m_wpRenameKey = iter->second.siVirtKey;
    m_bRenameCtrl = (iter->second.cModifier & HOTKEYF_CONTROL) == HOTKEYF_CONTROL;
    m_bRenameShift = (iter->second.cModifier & HOTKEYF_SHIFT) == HOTKEYF_SHIFT;
  } else {
    m_wpRenameKey = 0;
  }

  // Find Autotype Shortcut
  iter = m_MapMenuShortcuts.find(ID_MENUITEM_AUTOTYPE);
  
  // Save for CTreeCtrl & CListCtrl PreTranslateMessage
  if (iter != m_MapMenuShortcuts.end()) {
    m_wpAutotypeDNMsg = ((iter->second.cModifier & HOTKEYF_ALT) == HOTKEYF_ALT) ?
                               WM_SYSKEYDOWN : WM_KEYDOWN;
    m_wpAutotypeUPMsg = ((iter->second.cModifier & HOTKEYF_ALT) == HOTKEYF_ALT) ?
                               WM_SYSKEYUP : WM_KEYUP;
    m_wpAutotypeKey = iter->second.siVirtKey;
    m_bAutotypeCtrl = (iter->second.cModifier & HOTKEYF_CONTROL) == HOTKEYF_CONTROL;
    m_bAutotypeShift = (iter->second.cModifier & HOTKEYF_SHIFT) == HOTKEYF_SHIFT;
  } else {
    m_wpAutotypeKey = 0;
  }
}

bool DboxMain::ProcessLanguageMenu(CMenu *pPopupMenu)
{
  app.GetLanguageFiles();

  // Should always be 1 entry (for the embedded resources)
  // Can't do anything if no other DLLs available
  // This can also happen if they are back-level resoure only DLLs who have
  // a language ID the same as the application.
  if (app.m_vlanguagefiles.size() < 2)
    return false;

  size_t i;
  const UINT numItems = pPopupMenu->GetMenuItemCount();

  // Remove any existing menu items in case user has done something!
  for (i = 0; i < numItems; i++) {
    pPopupMenu->RemoveMenu(0, MF_BYPOSITION);
  }

  // Add new ones
  UINT nID = ID_LANGUAGES;

  // Add languages
  for (i = 0; i < app.m_vlanguagefiles.size(); i++) {
    UINT uiFlags = MF_STRING | MF_ENABLED | 
       ((app.m_vlanguagefiles[i].xFlags & 0x80) == 0x80) ? MF_CHECKED : MF_UNCHECKED;
    pPopupMenu->AppendMenu(uiFlags, nID++, app.m_vlanguagefiles[i].wsLanguage.c_str());
  }

  return true;
}

const unsigned int DboxMain::GetMenuShortcut(const unsigned short int &siVirtKey,
                                             const unsigned char &cModifier,
                                             StringX &sxMenuItemName)
{
  unsigned int nControlID(0);
  sxMenuItemName.empty();
  MapMenuShortcutsIter inuse_iter;

  st_MenuShortcut st_mst;
  st_mst.siVirtKey = siVirtKey;
  st_mst.cModifier = cModifier;
  
  inuse_iter = std::find_if(m_MapMenuShortcuts.begin(),
                            m_MapMenuShortcuts.end(),
                            already_inuse(st_mst));

  if (inuse_iter != m_MapMenuShortcuts.end()) {
    nControlID = inuse_iter->first;
    sxMenuItemName = inuse_iter->second.name.c_str();
  }

  // std::vector<st_MenuShortcut> m_ReservedShortcuts
  if (nControlID == 0) {
    std::vector<st_MenuShortcut>::iterator iter;
    iter = std::find_if(m_ReservedShortcuts.begin(), m_ReservedShortcuts.end(),
                        reserved(st_mst));

    if (iter != m_ReservedShortcuts.end()) {
      nControlID = iter->nControlID;
      LoadAString(sxMenuItemName, nControlID);
    }
  }

  if (!sxMenuItemName.empty()) {
    // These may have the shortcut hardcoded after a tab character e.g. "\tF1" for Help
    // Remove the tab and shortcut
    size_t found = sxMenuItemName.find_first_of(L"\t");
    if (found != StringX::npos)
      sxMenuItemName.erase(found);
  }

  return nControlID;
}
