//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1995, 1996 by Borland International, All Rights Reserved
//
/// \file
/// Implementation of class TRecentFiles
//----------------------------------------------------------------------------
#include <owl/pch.h>
 
//#if !defined(__GNUC__) //JJH added removal of pragma hdrstop for gcc
#pragma hdrstop
//#endif
 
#include <owl/framewin.h>
#include <owl/rcntfile.h>
#include <owl/pointer.h>
#include <owl/profile.h>
#include <owl/registry.h>
#include <owl/window.rh>
#include <owl/editfile.rh>
#include <stdio.h>
 
#include <owl/shellitm.h>
#include <algorithm>
 
#include <owl/rcntfile.rh>  //for CM_MRU_FIRST
 
namespace owl {
 
OWL_DIAGINFO;
 
const int CM_MRU_LAST       = CM_MRU_FIRST + TRecentFiles::MaxMenuItems;
const uint FullPathName      = TFileName::Server|TFileName::Device|TFileName::Path;
const uint FullFileName     = TFileName::Server|TFileName::Device|TFileName::Path|TFileName::File|TFileName::Ext;
 
#if defined(WINELIB) //JJH's work
static const tchar* CountKey        = _T("Count");
static const tchar* MruPrefix      = _T("File");
static const tchar* MenuItemDefault= _T("Default");
static const tchar* Section        = _T("Recent File List");
static const tchar* MaxCountKey    = _T("MaxCount");
#else
const tchar* CountKey        = _T("Count");
const tchar* MruPrefix      = _T("File");
const tchar* MenuItemDefault= _T("Default");
const tchar* Section        = _T("Recent File List");
const tchar* MaxCountKey    = _T("MaxCount");
#endif
const int    MaxMenuItemLen  = 255;
const int   MaxRegValueLen  = MaxMenuItemLen;
uint TRecentFiles::MruMessage = 0;
 
//
// Response Table to catch the items selected from the menu.
//
DEFINE_RESPONSE_TABLE1(TRecentFiles, TEventHandler)
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 0, CmFile),
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 1, CmFile),
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 2, CmFile),
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 3, CmFile),
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 4, CmFile),
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 5, CmFile),
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 6, CmFile),
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 7, CmFile),
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 8, CmFile),
  EV_COMMAND_AND_ID(CM_MRU_FIRST + 9, CmFile),
  EV_COMMAND_ENABLE(CM_EXIT, CeExit),
END_RESPONSE_TABLE;
 
 
static int
TRecentFiles__ReadMaxNum(bool useRegistry, const tstring& mruName,
                         int defaultCount)
{
  const auto getMaxCount = [&]() -> int
  {
    if (useRegistry)
    {
      if (const auto k = TRegKey::GetCurrentUser().GetSubkey(mruName))
        if (const auto v = k->GetValue(MaxCountKey))
          return static_cast<int>(static_cast<uint32>(*v));
      return defaultCount;
    }
    else
      return TProfile{Section, mruName}.GetInt(MaxCountKey, defaultCount);
  };
  return std::min(getMaxCount(), static_cast<int>(TRecentFiles::MaxMenuItems));
}
 
 
 
//
/// Constructor to initialize the external storage and the maximum number of items
/// to save in the most-recently-used (MRU) list.
//
TRecentFiles::TRecentFiles(const tstring& iniName, int numSavedFiles,
                           int namelen, bool useRegistry, bool keepFullNameInMenu)
:
  MaxFilesToSave(0),
  MaxDispLen(namelen),
  AddedSeparator(false),
  UseRegistry(useRegistry),
  LastHMenu(0),
  MruCount(0),
  keepFullNameInMenu(keepFullNameInMenu)
{
  int filestosave = std::min(numSavedFiles, (int)MaxMenuItems);
  MruName = iniName;
  if(UseRegistry){
    MruName += _T("\\");
    MruName += Section;
  }
  else
    MruName = TFileName(iniName).Canonical();
 
  if(filestosave < 0)
    filestosave = TRecentFiles__ReadMaxNum(UseRegistry, MruName, filestosave);
 
  SetMaxMruItems(filestosave);
  MruMessage = ::RegisterWindowMessage(MruFileMessage);
 
  Read();
}
 
//
/// Deletes the allocated profile.
//
TRecentFiles::~TRecentFiles()
{
  Write();
}
 
//
/// Sets the maximum number of items that can be saved with this MRU.
//
void
TRecentFiles::SetMaxMruItems(int numItems)
{
  if(numItems > MaxFilesToSave){
    TFileName* items = new TFileName[numItems];
    for(int i = 0; i < MaxFilesToSave && i < numItems; i++)
      items[i] = MruNames[i];
    MruNames = items;
  }
  MaxFilesToSave = numItems;
}
 
//
/// Responds to a menu item selection.
//
void
TRecentFiles::CmFile(uint id)
{
  TApplication* app    = TYPESAFE_DOWNCAST(this, TApplication);
  TFrameWindow* window = app ? app->GetMainWindow() : 0;
 
  if (window) {
    // Foward menu selection command to specified target
    //
    window->SendMessage(MruMessage, id, 0);
  }
}
 
//
/// Searches the menu to find the position of a menu item.
//
int
TRecentFiles::GetMenuPos(HMENU menu, uint id)
{
  for (int i = ::GetMenuItemCount(menu) - 1; i >= 0; i--)
    if (::GetMenuItemID(menu, i) == id)
      return i;
  return -1;
}
 
//
/// Retrieves the menu position of the CM_EXIT menu item. Returns -1 if not found.
//
int
TRecentFiles::GetExitMenuPos(HMENU hMenu)
{
  int exitPos = GetMenuPos(hMenu, CM_EXIT);
  return exitPos;
}
 
//
/// Returns true if the menu has any MRU items in it.
//
bool
TRecentFiles::MruItemsInsertedIntoMenu(HMENU hMenu)
{
  int menuPos = GetMenuPos(hMenu, CM_MRU_FIRST + 0);
  return menuPos != -1;
}
 
//
/// Removes the MRU items from the menu.
//
void
TRecentFiles::RemoveMruItemsFromMenu(HMENU hMenu)
{
  int exitPos = GetExitMenuPos(hMenu);
  if (exitPos > 1)
    if (::GetMenuItemID(hMenu, exitPos - 1) == 0 && AddedSeparator) {
      AddedSeparator = false;
      if (LastHMenu == hMenu)
        ::RemoveMenu(hMenu, exitPos - 1, MF_BYPOSITION);
    }
 
  // remove MRU items
  //
  if (MruItemsInsertedIntoMenu(hMenu)) {
    for (int i = CM_MRU_FIRST; i < CM_MRU_LAST; i++) {
      int menuPos = GetMenuPos(hMenu, i);
      if (menuPos != -1) {
        ::RemoveMenu(hMenu, i, MF_BYCOMMAND);
      }
    }
  }
}
 
//
/// Reads external information and adds the MRU items into the menu. Adds a
/// separator between the MRU items and the exit menu item.
//
void
TRecentFiles::InsertMruItemsToMenu(HMENU hMenu)
{
  TAPointer<tchar> menuItemBuffer(new tchar[MaxMenuItemLen+4]);
 
  TMenu menu(hMenu);
  int exitPos       = GetExitMenuPos(hMenu);
  //int count         = GetMruCount();
  int i;
  int numItemsAdded = 0;
 
  tstring curdir = TFileName(TFileName::CurrentDir).GetParts(FullPathName);
 
  for (i = 0; i < MruCount; i++) {
    //if(!MruNames[i].IsValid()) //?????????????
    //  break;
 
    numItemsAdded++;
 
    tstring curname = MruNames[i].GetParts(FullFileName);
    if(curdir == MruNames[i].GetParts(FullPathName))
      curname = MruNames[i].GetParts(TFileName::File|TFileName::Ext);
    curname = TFileName(curname).Squeezed(MaxDispLen, keepFullNameInMenu);
    // insert mnemonic + the file name
    if((i+1) >= 10)
      wsprintf(menuItemBuffer, _T("%d&%d %s"), (i+1)/10, (i+1)%10, curname.c_str());
    else
      wsprintf(menuItemBuffer, _T("&%d %s"), (i+1), curname.c_str());
 
    menu.InsertMenu(exitPos + i, MF_BYPOSITION | MF_STRING, CM_MRU_FIRST + i, menuItemBuffer);
  }
 
  if (numItemsAdded > 0) {
    if (AddedSeparator == false) {
      LastHMenu = hMenu;
      AddedSeparator = true;
      menuItemBuffer[0] = 0;
      ::InsertMenu(hMenu, exitPos + numItemsAdded, MF_BYPOSITION|MF_SEPARATOR, 0, menuItemBuffer);
    }
  }
}
 
//
/// Reads information in the TProfile to display the menu choices.
//
void
TRecentFiles::CeExit(TCommandEnabler& ce)
{
  ce.Enable(true);
 
  TMenuItemEnabler* me = TYPESAFE_DOWNCAST(&ce, TMenuItemEnabler);
  if (me == 0)
    return;
 
  HMENU hMenu = me->GetMenu();
  RemoveMruItemsFromMenu(hMenu);
  InsertMruItemsToMenu(hMenu);
}
 
/// Retrieves the text of the choice based on the ID.
tstring
TRecentFiles::GetMenuText(int id)
{
  id -= CM_MRU_FIRST;
  if (id < 0 || MruCount <= id)
    return tstring(_T(""));
  return MruNames[id].GetParts(FullFileName);
}
 
//
/// Retrieves the text of the choice based on the ID.
//
bool
TRecentFiles::GetMenuText(int id, LPTSTR text, int maxTextLen)
{
  id -= CM_MRU_FIRST;
  //int count = GetMruCount();
  if (id < 0 || MruCount <= id)
    return false;
  tstring menuText = MruNames[id].GetParts(FullFileName);
  if(::_tcslen(menuText.c_str()) > (size_t)maxTextLen){
    _tcsncpy(text, menuText.c_str(), maxTextLen-1);
    text[maxTextLen] = _T('\0');
  }
  else
    ::_tcscpy(text, menuText.c_str());
  return true;
}
 
//
/// Registers the given file in the MRU list.
/// The file name should be specified with full absolute path.
///
/// If valid, the file is also registered as a recently used item by notifying the Windows Shell.
///
/// \sa <a href="https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shaddtorecentdocs">
/// SHAddToRecentDocs</a> in the Windows API.
//
void
TRecentFiles::SaveMenuChoice(LPCTSTR file)
{
  int index = GetMruItemIndex(file);
  RemoveMruIndex(index);
  AddMruItem(file);
  const auto i = TShellItem{file, false};
  if (i.Valid())
    i.AddToRecentDocs();
}
 
//
// Unregisters the given file from the MRU list.
// The file name should be specified with full absolute path.
//
void
TRecentFiles::RemoveMenuChoice(LPCTSTR file)
{
  RemoveMruIndex(GetMruItemIndex(file));
}
 
//
/// Removes the MRU item at index. Shuffles the items below index up.
//
void
TRecentFiles::RemoveMruIndex(int index)
{
  //int count = GetMruCount();
  if (index < 0 || MruCount <= index)
    return;
 
  if (index != MruCount - 1) {
    // shuffle so the item to be deleted is at index count-1
    //
    for(int i = index; i < MruCount - 1; i++)
      MruNames[i] = MruNames[i+1];
  }
 
  // delete the last item
  //
  MruNames[MruCount-1] = _T("");
  MruCount--; // decrease MRUCount
}
 
//
/// Adds an item to the top of the MRU list. If there is a duplicate, the item is
/// moved from its current position to the top of the list.
//
void
TRecentFiles::AddMruItem(LPCTSTR text)
{
  if (MaxFilesToSave <= 0)
    return;
  tstring sFullFilePath = TFileName(text).Canonical();
  if (sFullFilePath.empty())
    return;
  if(MruCount < MaxFilesToSave)
    MruCount++;
  if (MruCount > 1){
    // Shuffle items down to make room at index 0
    //
    for (int i = MruCount - 2; i >= 0; i--)
       MruNames[i+1] = MruNames[i];
  }
 
  // Add item to the top of the list
  //
   MruNames[0] = sFullFilePath;
}
 
//
/// Returns true if there are any items in the MRU list that match the text.
//
bool
TRecentFiles::ExistMruItem(LPCTSTR text)
{
  return GetMruItemIndex(text) != -1;
}
 
//
/// Returns the index of the MRU item containing text. Returns -1 if not found.
//
int
TRecentFiles::GetMruItemIndex(LPCTSTR text)
{
  TFileName filename(TFileName(text).Canonical());
  for (int i = 0; i < MruCount; i++) {
    if(MruNames[i] == filename)
      return i;
  }
  return -1;
}
//
// Read Data from registry or file
//
void
TRecentFiles::Read()
{
  if (UseRegistry)
  {
    const auto k = TRegKey::GetCurrentUser().GetSubkey(MruName);
    WARN(!k, _T("Registry key is missing for MRU: HKEY_CURRENT_USER\\") << MruName);
    if (!k) return;
    const auto c = k->GetValue(CountKey);
    WARN(!c, _T("Registry value is missing for MRU: ") << CountKey);
    if (!c) return;
    MruCount = std::min(static_cast<int>(static_cast<uint32>(*c)), MaxFilesToSave);
    for (auto i = 0; i != MruCount; ++i)
    {
      const auto f = k->GetValue(MruPrefix + to_tstring(i));
      WARN(!f, _T("Registry value is missing for MRU: " << MruPrefix << i));
      if (!f) return;
      MruNames[i] = *f;
    }
  }
  else
  {
    TAPointer<tchar> srcKeyBuffer(new tchar[80]);
    TAPointer<tchar> menuText(new tchar[MaxRegValueLen]);
    TProfile profile(Section, MruName.c_str());
    MruCount = profile.GetInt(CountKey, 0);
    if (MruCount > MaxFilesToSave)
      MruCount = MaxFilesToSave;
    for (int i = 0; i < MruCount; i++){
      wsprintf(srcKeyBuffer, _T("%s%d"), MruPrefix, i);
      profile.GetString(srcKeyBuffer, menuText, MaxMenuItemLen, MenuItemDefault);
      MruNames[i] = (tchar*)menuText;
    }
  }
}
//
// Write data to registry or file
//
void
TRecentFiles::Write()
{
  if (UseRegistry)
  {
    auto& c = TRegKey::GetCurrentUser();
    const auto r = c.DeleteKey(MruName);
    CHECK(r == ERROR_SUCCESS || r == ERROR_FILE_NOT_FOUND || r == ERROR_PATH_NOT_FOUND);
    auto k = TRegKey{c, MruName}; CHECK(k.GetHandle());
    TRegValue{k, CountKey} = static_cast<uint32>(MruCount);
    for (auto i = 0; i != MruCount; ++i)
      TRegValue{k, MruPrefix + to_tstring(i)} = MruNames[i].GetParts(FullFileName);
  }
  else
  {
    TAPointer<tchar> dstKeyBuffer(new tchar[80]);
    TProfile profile(Section, MruName.c_str());
    profile.WriteInt(CountKey, MruCount);
    for (int i = 0; i < MruCount; i++){
      wsprintf(dstKeyBuffer, _T("%s%d"), MruPrefix, i);
      profile.WriteString(dstKeyBuffer, MruNames[i].GetParts(FullFileName));
    }
  }
}
 
} // OWL namespace
 
/* ========================================================================== */
 

V560 A part of conditional expression is always true: i < numItems.

V806 Decreased performance. The expression of strlen(MyStr.c_str()) kind can be rewritten as MyStr.length().

V818 It is more efficient to use an initialization list 'MruName(iniName)' rather than an assignment operator.