// This file is part of Notepad++ project
// Copyright (C)2021 Don HO <don.h@free.fr>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.



// 引入LastRecentFileList类的头文件  
#include "lastRecentFileList.h"  
// 引入菜单命令ID的头文件  
#include "menuCmdID.h"  
// 引入本地化的头文件，可能用于支持多语言  
#include "localization.h"  

// LastRecentFileList类的initMenu函数  
void LastRecentFileList::initMenu(HMENU hMenu, int idBase, int posBase, Accelerator* pAccelerator, bool doSubMenu)
{
    // 如果doSubMenu为真，表示需要创建一个弹出菜单  
    if (doSubMenu)
    {
        // 设置父菜单句柄  
        _hParentMenu = hMenu;
        // 创建一个新的弹出菜单  
        _hMenu = ::CreatePopupMenu();
    }
    // 如果doSubMenu为假，表示直接使用传入的菜单句柄  
    else
    {
        // 设置父菜单句柄为NULL，表示主菜单  
        _hParentMenu = NULL;
        // 使用传入的菜单句柄作为当前菜单  
        _hMenu = hMenu;
    }

    // 设置ID的基础值  
    _idBase = idBase;
    // 设置位置的基础值  
    _posBase = posBase;
    // 设置指向Accelerator对象的指针，用于处理键盘快捷键等加速功能  
    _pAccelerator = pAccelerator;
    // 设置本地化语言编码为Windows的1252编码，这是一种西欧语言编码  
    _nativeLangEncoding = NPP_CP_WIN_1252;

    // 初始化idFreeArray数组，所有元素都设置为true，表示ID可用  
    for (size_t i = 0; i < sizeof(_idFreeArray); ++i)
        _idFreeArray[i] = true;
}

// LastRecentFileList类的switchMode函数，用于切换模式  
void LastRecentFileList::switchMode()
{
    // 从菜单中移除所有相关菜单项  
    ::RemoveMenu(_hMenu, IDM_FILE_RESTORELASTCLOSEDFILE, MF_BYCOMMAND);
    ::RemoveMenu(_hMenu, IDM_OPEN_ALL_RECENT_FILE, MF_BYCOMMAND);
    ::RemoveMenu(_hMenu, IDM_CLEAN_RECENT_FILE_LIST, MF_BYCOMMAND);

    // 遍历最近打开文件列表，移除所有菜单项  
    for (int i = 0; i < _size; ++i)
    {
        ::RemoveMenu(_hMenu, _lrfl.at(i)._id, MF_BYCOMMAND);
    }

    // 根据_hParentMenu的值判断当前模式，主菜单模式或子菜单模式  
    if (_hParentMenu == NULL) // mode main menu（主菜单模式）  
    {
        if (_size > 0) // 如果最近打开文件列表不为空  
        {
            // 从主菜单中移除一些特定的菜单项（具体移除哪些项未给出）  
            ::RemoveMenu(_hMenu, _posBase, MF_BYPOSITION);
            ::RemoveMenu(_hMenu, _posBase, MF_BYPOSITION);
        }
        // 切换到子菜单模式，创建一个新的弹出菜单作为当前菜单，并设置_hParentMenu为当前菜单（即新创建的弹出菜单）  
        // 同时移除一些特定的菜单项（具体移除哪些项未给出）  
        _hParentMenu = _hMenu;
        _hMenu = ::CreatePopupMenu();
        ::RemoveMenu(_hMenu, _posBase + 1, MF_BYPOSITION);
    }
    else // mode sub-menu（子菜单模式）  
    {
        if (_size > 0) // 如果最近打开文件列表不为空  
        {
            // 从子菜单中移除一些特定的菜单项（具体移除哪些项未给出）  
            ::RemoveMenu(_hParentMenu, _posBase, MF_BYPOSITION);
            ::RemoveMenu(_hParentMenu, _posBase, MF_BYPOSITION);
        }
        // switch to main menu mode
        ::DestroyMenu(_hMenu);
        _hMenu = _hParentMenu;
        _hParentMenu = NULL;
    }
    _hasSeparators = false;
}

void LastRecentFileList::updateMenu()
{
    // 获取 NppParameters 的单例实例  
    NppParameters& nppParam = NppParameters::getInstance();

    // 检查是否没有分隔符且大小大于0，满足条件则进入下面的 if 语句块  
    if (!_hasSeparators && _size > 0)
    {
        // 获取本地化语言的发言人对象  
        NativeLangSpeaker* pNativeLangSpeaker = nppParam.getNativeLangSpeaker();

        // 从发言人对象中获取一些本地化菜单字符串  
        generic_string recentFileList = pNativeLangSpeaker->getSubMenuEntryName("file-recentFiles");
        generic_string openRecentClosedFile = pNativeLangSpeaker->getNativeLangMenuString(IDM_FILE_RESTORELASTCLOSEDFILE);
        generic_string openAllFiles = pNativeLangSpeaker->getNativeLangMenuString(IDM_OPEN_ALL_RECENT_FILE);
        generic_string cleanFileList = pNativeLangSpeaker->getNativeLangMenuString(IDM_CLEAN_RECENT_FILE_LIST);

        // 如果获取的字符串为空，则使用默认的字符串进行初始化  
        if (recentFileList == TEXT(""))
            recentFileList = TEXT("&Recent Files");
        if (openRecentClosedFile == TEXT(""))
            openRecentClosedFile = TEXT("Restore Recent Closed File");
        if (openAllFiles == TEXT(""))
            openAllFiles = TEXT("Open All Recent Files");
        if (cleanFileList == TEXT(""))
            cleanFileList = TEXT("Empty Recent Files List");

        // 如果当前不是子菜单模式，则在菜单中插入一个分隔符  
        if (!isSubMenuMode())
            ::InsertMenu(_hMenu, _posBase + 0, MF_BYPOSITION, static_cast<UINT_PTR>(-1), 0);

        // 在菜单中插入四个子菜单项，分别是恢复最近关闭的文件、打开所有最近文件、清空最近文件列表和另一个分隔符  
        ::InsertMenu(_hMenu, _posBase + 1, MF_BYPOSITION, IDM_FILE_RESTORELASTCLOSEDFILE, openRecentClosedFile.c_str());
        ::InsertMenu(_hMenu, _posBase + 2, MF_BYPOSITION, IDM_OPEN_ALL_RECENT_FILE, openAllFiles.c_str());
        ::InsertMenu(_hMenu, _posBase + 3, MF_BYPOSITION, IDM_CLEAN_RECENT_FILE_LIST, cleanFileList.c_str());
        ::InsertMenu(_hMenu, _posBase + 4, MF_BYPOSITION, static_cast<UINT_PTR>(-1), 0); // 插入分隔符  
        // 设置标志表示已有分隔符  
        _hasSeparators = true;

        // 如果当前是子菜单模式，则在父菜单中插入子菜单和分隔符  
        if (isSubMenuMode())
        {
            ::InsertMenu(_hParentMenu, _posBase + 0, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(_hMenu), (LPCTSTR)recentFileList.c_str());
            ::InsertMenu(_hParentMenu, _posBase + 1, MF_BYPOSITION, static_cast<UINT_PTR>(-1), 0); // 插入分隔符  
        }
    }
    else if (_hasSeparators && _size == 0)  // 当已有分隔符且大小为0时，进入下面的 if 语句块，用于移除分隔符和菜单项  
    {
        // 从菜单中移除一个分隔符和清空最近文件列表的菜单项  
        ::RemoveMenu(_hMenu, _posBase + 4, MF_BYPOSITION);
        ::RemoveMenu(_hMenu, IDM_CLEAN_RECENT_FILE_LIST, MF_BYCOMMAND);
        ::RemoveMenu(_hMenu, IDM_OPEN_ALL_RECENT_FILE, MF_BYCOMMAND);
        ::RemoveMenu(_hMenu, IDM_FILE_RESTORELASTCLOSEDFILE, MF_BYCOMMAND);
        ::RemoveMenu(_hMenu, _posBase + 0, MF_BYPOSITION);
        _hasSeparators = false;

        if (isSubMenuMode())
        {
            // Remove "Recent Files" Entry and the separator from the main menu
            ::RemoveMenu(_hParentMenu, _posBase + 1, MF_BYPOSITION);
            ::RemoveMenu(_hParentMenu, _posBase + 0, MF_BYPOSITION);

            // Remove the last left separator from the submenu
            ::RemoveMenu(_hMenu, 0, MF_BYPOSITION);
        }
    }

    _pAccelerator->updateFullMenu();

    //Remove all menu items
    for (int i = 0; i < _size; ++i)
    {
        ::RemoveMenu(_hMenu, _lrfl.at(i)._id, MF_BYCOMMAND);
    }
    //Then readd them, so everything stays in sync
    for (int j = 0; j < _size; ++j)
    {
        generic_string strBuffer(BuildMenuFileName(nppParam.getRecentFileCustomLength(), j, _lrfl.at(j)._name));
        ::InsertMenu(_hMenu, _posBase + j, MF_BYPOSITION, _lrfl.at(j)._id, strBuffer.c_str());
    }

}
// 向最近的文件列表中添加一个文件名。如果达到用户定义的最大限制或列表被锁定，则不会添加。  
// 如果文件名已存在，则会先将其移除再添加。  
void LastRecentFileList::add(const TCHAR* fn)
{
    if (_userMax == 0 || _locked) // 如果用户定义的最大数量为0或列表被锁定，直接返回，不进行添加  
        return;

    RecentItem itemToAdd(fn); // 创建一个新的RecentItem对象，使用传入的文件名初始化  

    int index = find(fn); // 查找该文件名在列表中的索引  
    if (index != -1) // 如果找到  
    {
        // 如果文件名已存在，先将其移除，然后重新添加  
        remove(index);
    }

    if (_size == _userMax) // 如果列表已满  
    {
        itemToAdd._id = _lrfl.back()._id; // 使用最后一个元素的ID初始化新元素的ID  
        _lrfl.pop_back(); // 移除列表中的最后一个元素（即最旧的一个）  
    }
    else // 如果列表未满  
    {
        itemToAdd._id = popFirstAvailableID(); // 获取第一个可用的ID并初始化新元素的ID  
        ++_size; // 增加列表的大小  
    }
    _lrfl.push_front(itemToAdd); // 在列表的前面添加新元素  
    updateMenu(); // 更新菜单  
}

// 根据文件名从最近的文件列表中移除一个文件。如果找到，则移除；否则什么也不做。  
void LastRecentFileList::remove(const TCHAR* fn)
{
    int index = find(fn); // 查找文件名在列表中的索引  
    if (index != -1) // 如果找到  
        remove(index); // 移除该元素  
}

// 根据索引从最近的文件列表中移除一个文件。如果索引有效，则移除；否则什么也不做。  
void LastRecentFileList::remove(size_t index)
{
    if (_size == 0 || _locked) // 如果列表为空或被锁定，直接返回，不进行移除操作  
        return;
    if (index < _lrfl.size()) // 如果索引有效  
    {
        ::RemoveMenu(_hMenu, _lrfl.at(index)._id, MF_BYCOMMAND); // 从菜单中移除该ID对应的菜单项  
        setAvailable(_lrfl.at(index)._id); // 将该ID标记为可用（可能是为了下次添加时重复使用）  
        _lrfl.erase(_lrfl.begin() + index); // 从列表中移除该元素  
        --_size; // 减少列表的大小  
        updateMenu(); // 更新菜单  
    }
}

// 清空最近的文件列表。所有文件名和ID都会被移除，菜单也会相应地更新。  
void LastRecentFileList::clear()
{
    if (_size == 0) // 如果列表为空，直接返回，不进行任何操作  
        return;
    for (int i = (_size - 1); i >= 0; i--) // 从后向前遍历列表，因为移除元素会影响后续元素的索引  
    {
        ::RemoveMenu(_hMenu, _lrfl.at(i)._id, MF_BYCOMMAND); // 从菜单中移除该ID对应的菜单项  
        setAvailable(_lrfl.at(i)._id); // 将该ID标记为可用（可能是为了下次添加时重复使用）  
        _lrfl.erase(_lrfl.begin() + i); // 从列表中移除该元素  
    }
    _size = 0; // 将列表大小设置为0  
    updateMenu(); // 更新菜单  
}

这段代码定义了一个名为LastRecentFileList的类，该类用于管理最近打开的文件列表。以下是详细的中文注释：

cpp
// 根据ID从最近的文件列表中获取一个文件名。如果找到，则返回对应的文件名；否则返回列表中的第一个文件名。  
generic_string& LastRecentFileList::getItem(int id)
{
    int i = 0; // 从索引0开始查找，因为如果ID不存在，可能会返回第一个文件名作为默认值。  
    for (; i < _size; ++i) // 遍历整个列表查找匹配的ID。如果找不到，i会等于_size，此时i会被重新设置为0。这是一个后备计划，以防ID不存在于列表  
    {
        if (_lrfl.at(i)._id == id) // 如果找到匹配的ID  
            break;
    }
    if (i == _size) // 如果遍历完整个列表都没有找到匹配的ID  
        i = 0; // 将索引重置为0，这样可以从列表的第一个元素开始返回文件名作为默认值  
    return _lrfl.at(i)._name; // 返回找到的文件名或默认文件名  
}

// 根据索引从最近的文件列表中获取一个文件名。如果索引有效，则返回对应的文件名；否则返回列表中的第一个文件名。  
generic_string& LastRecentFileList::getIndex(int index)
{
    return _lrfl.at(index)._name; // 返回找到的文件名或默认文件名  
}

// 设置用户定义的最大最近文件列表大小。如果当前列表大小大于新设置的大小，则移除最旧的文件直到列表大小等于新设置的大小。  
void LastRecentFileList::setUserMaxNbLRF(int size)
{
    _userMax = size; // 设置用户定义的最大列表大小  
    if (_size > _userMax) // 如果当前列表大小大于新设置的大小  
    {
        int toPop = _size - _userMax; // 计算需要移除的文件数量  
        while (toPop > 0) // 当还有文件需要移除时  
        {
            ::RemoveMenu(_hMenu, _lrfl.back()._id, MF_BYCOMMAND); // 从菜单中移除该ID对应的菜单项  
            setAvailable(_lrfl.back()._id); // 将该ID标记为可用（可能是为了下次添加时重复使用）  
            _lrfl.pop_back(); // 从列表中移除该元素  
            toPop--; // 减少剩余需要移除的文件数量  
            _size--; // 减少当前列表大小  
        }
        updateMenu(); // 更新菜单  
        _size = _userMax; // 将当前列表大小设置为新设置的大小  
    }
}

// 将最近的文件列表保存到配置文件中。保存的文件名顺序与加载时相同。  
void LastRecentFileList::saveLRFL()
{
    NppParameters& nppParams = NppParameters::getInstance(); // 获取NppParameters的实例引用  
    if (nppParams.writeRecentFileHistorySettings(_userMax)) // 如果成功写入最近的文件历史设置  
    {
        for (int i = _size - 1; i >= 0; i--) // 按照从后往前的顺序保存文件名，确保加载时的顺序正确  
        {
            nppParams.writeHistory(_lrfl.at(i)._name.c_str()); // 将文件名写入配置文件的历史记录部分  
        }
    }
}




// 在最近的文件列表中查找指定的文件名。  
int LastRecentFileList::find(const TCHAR* fn)
{
    for (int i = 0; i < _size; ++i) // 遍历整个列表  
    {
        if (OrdinalIgnoreCaseCompareStrings(_lrfl.at(i)._name.c_str(), fn) == 0) // 如果找到匹配的文件名（忽略大小写）  
        {
            return i; // 返回该文件在列表中的索引  
        }
    }
    return -1; // 如果未找到，返回-1  
}

// 弹出第一个可用的ID。  
int LastRecentFileList::popFirstAvailableID()
{
    for (int i = 0; i < NB_MAX_LRF_FILE; ++i) // 遍历所有可能的ID  
    {
        if (_idFreeArray[i]) // 如果该ID可用（未被使用）  
        {
            _idFreeArray[i] = false; // 标记该ID为已使用  
            return i + _idBase; // 返回该ID的值（从_idBase开始计算）  
        }
    }
    return 0; // 如果所有ID都已使用，返回0（或默认值）  
}

// 设置指定的ID为可用。  
void LastRecentFileList::setAvailable(int id)
{
    int index = id - _idBase; // 根据ID计算其在_idFreeArray中的索引  
    _idFreeArray[index] = true; // 将该索引对应的值设置为true，表示该ID可用  
}
