Notepad3/grepWinNP3/src/SearchDlg.cpp

4493 lines
192 KiB
C++

// grepWin - regex search and replace for Windows
// Copyright (C) 2007-2021 - Stefan Kueng
// 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 2
// 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, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "stdafx.h"
#include "strsafe.h"
#include "resource.h"
#include "SearchDlg.h"
#include "Registry.h"
#include "DirFileEnum.h"
#include "TextFile.h"
#include "SearchInfo.h"
#include "UnicodeUtils.h"
#include "StringUtils.h"
#include "BrowseFolder.h"
#include "SysImageList.h"
#include "ShellContextMenu.h"
#include "RegexTestDlg.h"
#include "NameDlg.h"
#include "BookmarksDlg.h"
#include "MultiLineEditDlg.h"
#include "AboutDlg.h"
#include "InfoDlg.h"
#include "DropFiles.h"
#include "RegexReplaceFormatter.h"
#include "LineData.h"
#include "Settings.h"
#include "ResString.h"
#include "Language.h"
#include "Monitor.h"
#include "SmartHandle.h"
#include "PathUtils.h"
#include "DebugOutput.h"
#include "Theme.h"
#include "DarkModeHelper.h"
#include "SearchMT.h" // multi-threading helper
#include "OnOutOfScope.h"
#include "COMPtrs.h"
#include "PreserveChdir.h"
#ifdef NP3_ALLOW_UPDATE
#include "TempFile.h"
#include "Monitor.h"
#include "version.h"
#endif
#include <string>
#include <map>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <numeric>
#include <Commdlg.h>
#pragma warning(push)
#pragma warning(disable: 4996) // warning STL4010: Various members of std::allocator are deprecated in C++17
#include <boost/regex.hpp>
#include <boost/spirit/include/classic_file_iterator.hpp>
#include <boost/iostreams/device/mapped_file.hpp>
#pragma warning(pop)
#define GREPWIN_DATEBUFFER 100
#define LABELUPDATETIMER 10
DWORD WINAPI SearchThreadEntry(LPVOID lpParam);
DWORD WINAPI EvaluationThreadEntry(LPVOID lpParam);
// ReSharper disable once CppInconsistentNaming
UINT CSearchDlg::m_grepwinStartupmsg = RegisterWindowMessage(L"grepWinNP3_StartupMessage");
std::map<size_t, DWORD> linePositions;
extern ULONGLONG g_startTime;
extern HANDLE hInitProtection;
static volatile LONG s_SearchThreadRunning = FALSE;
static volatile LONG s_EvaluationThreadRunning = FALSE;
static volatile LONG s_Cancelled = FALSE;
static volatile LONG s_NOTSearch = FALSE;
inline bool IsSearchThreadRunning() { return InterlockedAnd(&s_SearchThreadRunning, TRUE); }
inline bool IsEvaluationThreadRunning() { return InterlockedAnd(&s_EvaluationThreadRunning, TRUE); }
inline bool IsCancelled() { return InterlockedAnd(&s_Cancelled, TRUE); }
inline bool IsNOTSearch() { return InterlockedAnd(&s_NOTSearch, TRUE); }
static BackupAndTempFilesLog s_BackupAndTmpFiles;
static SearchThreadMap s_SearchThreadMap;
// ==================================================================================
CSearchDlg::CSearchDlg(HWND hParent)
: m_hParent(hParent)
//, m_dwThreadRunning(FALSE)
//, m_cancelled(FALSE)
, m_bookmarksDlg(nullptr)
, m_bUseRegex(false)
, m_bUseRegexForPaths(false)
, m_bAllSize(false)
, m_lSize(0)
, m_sizeCmp(0)
, m_bIncludeSystem(false)
, m_bIncludeSystemC(false)
, m_bIncludeHidden(false)
, m_bIncludeHiddenC(false)
, m_bIncludeSubfolders(false)
, m_bIncludeSubfoldersC(false)
, m_bIncludeBinary(false)
, m_bIncludeBinaryC(false)
, m_bCreateBackup(false)
, m_bCreateBackupC(false)
, m_bCreateBackupInFolders(false)
, m_bCreateBackupInFoldersC(false)
, m_bWholeWords(false)
, m_bWholeWordsC(false)
, m_bUTF8(false)
, m_bUTF8C(false)
, m_bForceBinary(false)
, m_bCaseSensitive(false)
, m_bCaseSensitiveC(false)
, m_bDotMatchesNewline(false)
, m_bDotMatchesNewlineC(false)
//, m_bNotSearch(false)
, m_bCaptureSearch(false)
, m_bSizeC(false)
, m_endDialog(false)
, m_executeImmediately(ExecuteAction::None)
, m_dateLimit(0)
, m_bDateLimitC(false)
, m_date1({0})
, m_date2({0})
, m_bNoSaveSettings(false)
, m_bReplace(false)
, m_bConfirmationOnReplace(true)
, m_showContent(false)
, m_showContentSet(false)
, m_totalItems(0)
, m_searchedItems(0)
, m_totalMatches(0)
, m_selectedItems(0)
, m_bAscending(true)
, m_themeCallbackId(0)
, m_pDropTarget(nullptr)
, m_autoCompleteFilePatterns(bPortable ? &g_iniFile : nullptr)
, m_autoCompleteExcludeDirsPatterns(bPortable ? &g_iniFile : nullptr)
, m_autoCompleteSearchPatterns(bPortable ? &g_iniFile : nullptr)
, m_autoCompleteReplacePatterns(bPortable ? &g_iniFile : nullptr)
, m_autoCompleteSearchPaths(bPortable ? &g_iniFile : nullptr)
, m_regUseRegex(L"Software\\grepWinNP3\\UseRegex", 1)
, m_regAllSize(L"Software\\grepWinNP3\\AllSize")
, m_regSize(L"Software\\grepWinNP3\\Size", L"2000")
, m_regSizeCombo(L"Software\\grepWinNP3\\SizeCombo", 0)
, m_regIncludeSystem(L"Software\\grepWinNP3\\IncludeSystem")
, m_regIncludeHidden(L"Software\\grepWinNP3\\IncludeHidden")
, m_regIncludeSubfolders(L"Software\\grepWinNP3\\IncludeSubfolders", 1)
, m_regIncludeBinary(L"Software\\grepWinNP3\\IncludeBinary", 1)
, m_regCreateBackup(L"Software\\grepWinNP3\\CreateBackup")
, m_regWholeWords(L"Software\\grepWinNP3\\WholeWords")
, m_regUTF8(L"Software\\grepWinNP3\\UTF8")
, m_regCaseSensitive(L"Software\\grepWinNP3\\CaseSensitive")
, m_regDotMatchesNewline(L"Software\\grepWinNP3\\DotMatchesNewline")
, m_regUseRegexForPaths(L"Software\\grepWinNP3\\UseFileMatchRegex")
, m_regPattern(L"Software\\grepWinNP3\\pattern")
, m_regExcludeDirsPattern(L"Software\\grepWinNP3\\ExcludeDirsPattern")
, m_regSearchPath(L"Software\\grepWinNP3\\searchpath")
, m_regEditorCmd(L"Software\\grepWinNP3\\editorcmd")
, m_regBackupInFolder(L"Software\\grepWinNP3\\backupinfolder", FALSE)
, m_regDateLimit(L"Software\\grepWinNP3\\DateLimit", 0)
, m_regDate1Low(L"Software\\grepWinNP3\\Date1Low", 0)
, m_regDate1High(L"Software\\grepWinNP3\\Date1High", 0)
, m_regDate2Low(L"Software\\grepWinNP3\\Date2Low", 0)
, m_regDate2High(L"Software\\grepWinNP3\\Date2High", 0)
, m_regShowContent(L"Software\\grepWinNP3\\ShowContent", 0)
, m_regOpacityNoFocus(L"Software\\grepWinNP3\\OpacityNoFocus", 100)
, m_regStayOnTop(L"Software\\grepWinNP3\\StayOnTop", 0)
, m_OpacityNoFocus(100)
, m_bStayOnTop(false)
{
if (FAILED(CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(m_pTaskbarList.GetAddressOf()))))
m_pTaskbarList = nullptr;
else if (m_pTaskbarList)
m_pTaskbarList->HrInit();
}
CSearchDlg::~CSearchDlg()
{
if (m_pDropTarget)
delete m_pDropTarget;
}
LRESULT CSearchDlg::DlgFunc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
if (uMsg == m_grepwinStartupmsg)
{
if ((GetTickCount64() - 4000) < g_startTime)
{
if (wParam == 0)
g_startTime = GetTickCount64();
return TRUE;
}
if (wParam == 0)
g_startTime = GetTickCount64();
return FALSE;
}
switch (uMsg)
{
case WM_INITDIALOG:
{
SetTransparency(ALPHA_OPAQUE);
SHAutoComplete(GetDlgItem(*this, IDC_SEARCHPATH), SHACF_FILESYSTEM|SHACF_AUTOSUGGEST_FORCE_ON);
m_autoCompleteFilePatterns.Load(L"Software\\grepWinNP3\\History", L"FilePattern");
m_autoCompleteFilePatterns.Init(GetDlgItem(hwndDlg, IDC_PATTERN));
m_autoCompleteExcludeDirsPatterns.Load(L"Software\\grepWinNP3\\History", L"ExcludeDirsPattern");
m_autoCompleteExcludeDirsPatterns.Init(GetDlgItem(hwndDlg, IDC_EXCLUDEDIRSPATTERN));
m_autoCompleteSearchPatterns.Load(L"Software\\grepWinNP3\\History", L"SearchPattern");
m_autoCompleteSearchPatterns.Init(GetDlgItem(hwndDlg, IDC_SEARCHTEXT));
m_autoCompleteReplacePatterns.Load(L"Software\\grepWinNP3\\History", L"ReplacePattern");
m_autoCompleteReplacePatterns.Init(GetDlgItem(hwndDlg, IDC_REPLACETEXT));
m_autoCompleteSearchPaths.Load(L"Software\\grepWinNP3\\History", L"SearchPaths");
m_autoCompleteSearchPaths.Init(GetDlgItem(hwndDlg, IDC_SEARCHPATH));
m_themeCallbackId = CTheme::Instance().RegisterThemeChangeCallback(
[this]() {
auto bDark = CTheme::Instance().IsDarkTheme();
DarkModeHelper::Instance().AllowDarkModeForApp(bDark);
CTheme::Instance().SetThemeForDialog(*this, bDark);
CTheme::Instance().SetFontForDialog(*this, CTheme::Instance().GetDlgFontFaceName(), CTheme::Instance().GetDlgFontSize());
DarkModeHelper::Instance().AllowDarkModeForWindow(GetToolTipHWND(), bDark);
DarkModeHelper::Instance().RefreshTitleBarThemeColor(*this, bDark);
});
auto bDark = CTheme::Instance().IsDarkTheme();
if (bDark)
DarkModeHelper::Instance().AllowDarkModeForApp(bDark);
CTheme::Instance().SetThemeForDialog(*this, CTheme::Instance().IsDarkTheme());
CTheme::Instance().SetFontForDialog(*this, CTheme::Instance().GetDlgFontFaceName(), CTheme::Instance().GetDlgFontSize());
DarkModeHelper::Instance().AllowDarkModeForWindow(GetToolTipHWND(), bDark);
if (!bDark)
DarkModeHelper::Instance().AllowDarkModeForApp(bDark);
SetWindowTheme(GetToolTipHWND(), L"Explorer", nullptr);
CLanguage::Instance().TranslateWindow(*this);
AddToolTip(IDC_NEWINSTANCE, TranslatedString(hResource, IDS_NEWINSTANCE_TT).c_str());
AddToolTip(IDC_PATTERN, TranslatedString(hResource, IDS_PATTERN_TT).c_str());
AddToolTip(IDC_EXCLUDEDIRSPATTERN, TranslatedString(hResource, IDS_EXCLUDEDIR_TT).c_str());
AddToolTip(IDC_SEARCHPATH, TranslatedString(hResource, IDS_SEARCHPATH_TT).c_str());
AddToolTip(IDC_DOTMATCHNEWLINE, TranslatedString(hResource, IDS_DOTMATCHNEWLINE_TT).c_str());
AddToolTip(IDC_SEARCHTEXT, TranslatedString(hResource, IDS_SEARCHTEXT_TT).c_str());
AddToolTip(IDC_EDITMULTILINE1, TranslatedString(hResource, IDS_EDITMULTILINE_TT).c_str());
AddToolTip(IDC_EDITMULTILINE2, TranslatedString(hResource, IDS_EDITMULTILINE_TT).c_str());
AddToolTip(IDC_EXPORT, TranslatedString(hResource, IDS_EXPORT_TT).c_str());
AddToolTip(IDOK, TranslatedString(hResource, IDS_SHIFT_NOTSEARCH).c_str());
AddToolTip(IDC_REPLACETEXT, LPSTR_TEXTCALLBACK);
if (m_searchPath.empty())
{
if (bPortable)
m_searchPath = g_iniFile.GetValue(L"global", L"searchpath", L"");
else
m_searchPath = std::wstring(m_regSearchPath);
}
else
{
// expand a possible 'short' path
DWORD ret = 0;
ret = ::GetLongPathName(m_searchPath.c_str(), nullptr, 0);
if (ret)
{
auto pathBuf = std::make_unique<wchar_t[]>(ret + 2LL);
ret = ::GetLongPathName(m_searchPath.c_str(), pathBuf.get(), ret + 1);
m_searchPath = std::wstring(pathBuf.get(), ret);
}
}
if (m_patternRegex.empty())
{
if (bPortable)
{
m_patternRegex = g_iniFile.GetValue(L"global", L"pattern", L"");
m_bUseRegexForPaths = g_iniFile.GetBoolValue(L"global", L"UseFileMatchRegex", false);
}
else
{
m_patternRegex = std::wstring(m_regPattern);
m_bUseRegexForPaths = !!static_cast<DWORD>(m_regUseRegexForPaths);
}
}
if (m_excludeDirsPatternRegex.empty())
{
if (bPortable)
m_excludeDirsPatternRegex = g_iniFile.GetValue(L"global", L"ExcludeDirsPattern", L"");
else
m_excludeDirsPatternRegex = std::wstring(m_regExcludeDirsPattern);
}
// initialize the controls
SetDlgItemText(hwndDlg, IDC_SEARCHPATH, m_searchPath.c_str());
SetDlgItemText(hwndDlg, IDC_SEARCHTEXT, m_searchString.c_str());
SetDlgItemText(hwndDlg, IDC_EXCLUDEDIRSPATTERN, m_excludeDirsPatternRegex.c_str());
SetDlgItemText(hwndDlg, IDC_PATTERN, m_patternRegex.c_str());
SetDlgItemText(hwndDlg, IDC_REPLACETEXT, m_replaceString.c_str());
// the path edit control should work as a drop target for files and folders
HWND hSearchPath = GetDlgItem(hwndDlg, IDC_SEARCHPATH);
m_pDropTarget = new CFileDropTarget(hSearchPath);
RegisterDragDrop(hSearchPath, m_pDropTarget);
// create the supported formats:
FORMATETC ftEtc = {0};
ftEtc.cfFormat = CF_TEXT;
ftEtc.dwAspect = DVASPECT_CONTENT;
ftEtc.lindex = -1;
ftEtc.tymed = TYMED_HGLOBAL;
m_pDropTarget->AddSuportedFormat(ftEtc);
ftEtc.cfFormat = CF_HDROP;
m_pDropTarget->AddSuportedFormat(ftEtc);
m_pDropTarget->SetMultipathConcatenate('|');
m_editFilePatterns.Subclass(hwndDlg, IDC_PATTERN);
m_editExcludeDirsPatterns.Subclass(hwndDlg, IDC_EXCLUDEDIRSPATTERN);
m_editSearchPatterns.Subclass(hwndDlg, IDC_SEARCHTEXT);
m_editReplacePatterns.Subclass(hwndDlg, IDC_REPLACETEXT);
m_editSearchPaths.Subclass(hwndDlg, IDC_SEARCHPATH);
// add an "About" entry to the system menu
HMENU hSysMenu = GetSystemMenu(hwndDlg, FALSE);
if (hSysMenu)
{
int menuItemsCount = GetMenuItemCount(hSysMenu);
if (menuItemsCount > 2)
{
InsertMenu(hSysMenu, menuItemsCount - 2, MF_STRING | MF_BYPOSITION, ID_STAY_ON_TOP, TranslatedString(hResource, IDS_STAY_ON_TOP).c_str());
InsertMenu(hSysMenu, menuItemsCount - 2, MF_SEPARATOR | MF_BYPOSITION, NULL, NULL);
InsertMenu(hSysMenu, menuItemsCount - 2, MF_STRING | MF_BYPOSITION, ID_ABOUTBOX, TranslatedString(hResource, IDS_ABOUT).c_str());
InsertMenu(hSysMenu, menuItemsCount - 2, MF_STRING | MF_BYPOSITION, ID_CLONE, TranslatedString(hResource, IDS_CLONE).c_str());
InsertMenu(hSysMenu, menuItemsCount - 2, MF_SEPARATOR | MF_BYPOSITION, NULL, nullptr);
}
else
{
AppendMenu(hSysMenu, MF_SEPARATOR, NULL, nullptr);
AppendMenu(hSysMenu, MF_STRING, ID_CLONE, TranslatedString(hResource, IDS_CLONE).c_str());
AppendMenu(hSysMenu, MF_STRING, ID_ABOUTBOX, TranslatedString(hResource, IDS_ABOUT).c_str());
AppendMenu(hSysMenu, MF_SEPARATOR, NULL, NULL);
AppendMenu(hSysMenu, MF_STRING, ID_STAY_ON_TOP, TranslatedString(hResource, IDS_STAY_ON_TOP).c_str());
}
}
wchar_t buf[MAX_PATH] = {0};
if (m_bSizeC && (m_lSize != static_cast<uint64_t>(-1)))
{
swprintf_s(buf, _countof(buf), L"%I64u", m_lSize);
SetDlgItemText(hwndDlg, IDC_SIZEEDIT, buf);
}
else
{
uint64_t s = _wtoll(std::wstring(m_regSize).c_str());
if (bPortable)
s = g_iniFile.GetLongValue(L"global", L"size", 2000);
swprintf_s(buf, _countof(buf), L"%I64u", s);
SetDlgItemText(hwndDlg, IDC_SIZEEDIT, buf);
}
SendDlgItemMessage(hwndDlg, IDC_SIZECOMBO, CB_INSERTSTRING, static_cast<WPARAM>(-1), reinterpret_cast<LPARAM>(static_cast<LPCWSTR>(TranslatedString(hResource, IDS_LESSTHAN).c_str())));
SendDlgItemMessage(hwndDlg, IDC_SIZECOMBO, CB_INSERTSTRING, static_cast<WPARAM>(-1), reinterpret_cast<LPARAM>(static_cast<LPCWSTR>(TranslatedString(hResource, IDS_EQUALTO).c_str())));
SendDlgItemMessage(hwndDlg, IDC_SIZECOMBO, CB_INSERTSTRING, static_cast<WPARAM>(-1), reinterpret_cast<LPARAM>(static_cast<LPCWSTR>(TranslatedString(hResource, IDS_GREATERTHAN).c_str())));
if (!m_bIncludeSubfoldersC)
m_bIncludeSubfolders = bPortable ? g_iniFile.GetBoolValue(L"global", L"IncludeSubfolders", true) : !!static_cast<DWORD>(m_regIncludeSubfolders);
if (!m_bIncludeSystemC)
m_bIncludeSystem = bPortable ? g_iniFile.GetBoolValue(L"global", L"IncludeSystem", true) : !!static_cast<DWORD>(m_regIncludeSystem);
if (!m_bIncludeHiddenC)
m_bIncludeHidden = bPortable ? g_iniFile.GetBoolValue(L"global", L"IncludeHidden", false) : !!static_cast<DWORD>(m_regIncludeHidden);
if (!m_bIncludeBinaryC)
m_bIncludeBinaryC = bPortable ? g_iniFile.GetBoolValue(L"global", L"IncludeBinary", false) : !!static_cast<DWORD>(m_regIncludeBinary);
if (!m_bCaseSensitiveC)
m_bCaseSensitive = bPortable ? g_iniFile.GetBoolValue(L"global", L"CaseSensitive", false) : !!static_cast<DWORD>(m_regCaseSensitive);
if (!m_bDotMatchesNewlineC)
m_bDotMatchesNewline = bPortable ? g_iniFile.GetBoolValue(L"global", L"DotMatchesNewline", false) : !!static_cast<DWORD>(m_regDotMatchesNewline);
if (!m_bCreateBackupC)
m_bCreateBackup = bPortable ? g_iniFile.GetBoolValue(L"global", L"CreateBackup", false) : !!static_cast<DWORD>(m_regCreateBackup);
if (!m_bCreateBackupInFoldersC)
m_bCreateBackupInFolders = bPortable ? g_iniFile.GetBoolValue(L"settings", L"backupinfolder", false) : !!static_cast<DWORD>(m_regBackupInFolder);
if (!m_bWholeWordsC)
m_bWholeWords = bPortable ? g_iniFile.GetBoolValue(L"global", L"WholeWords", false) : !!static_cast<DWORD>(m_regWholeWords);
if (!m_bUTF8C)
{
m_bUTF8 = bPortable ? g_iniFile.GetBoolValue(L"global", L"UTF8", false) : !!static_cast<DWORD>(m_regUTF8);
m_bForceBinary = bPortable ? !!g_iniFile.GetBoolValue(L"global", L"Binary", false) : !!static_cast<DWORD>(m_regBinary);
}
if (!m_bDotMatchesNewlineC)
m_bDotMatchesNewline = bPortable ? g_iniFile.GetBoolValue(L"global", L"DotMatchesNewline", false) : !!static_cast<DWORD>(m_regDotMatchesNewline);
if (!m_bSizeC)
{
m_bAllSize = bPortable ? g_iniFile.GetBoolValue(L"global", L"AllSize", false) : !!static_cast<DWORD>(m_regAllSize);
m_sizeCmp = bPortable ? g_iniFile.GetLongValue(L"global", L"SizeCombo", 0) : (int)static_cast<DWORD>(m_regSizeCombo);
}
if (!m_bDateLimitC)
{
m_dateLimit = bPortable ? g_iniFile.GetLongValue(L"global", L"DateLimit", 0) : (int)static_cast<DWORD>(m_regDateLimit);
m_date1.dwLowDateTime = bPortable ? g_iniFile.GetLongValue(L"global", L"Date1Low", 0) : static_cast<DWORD>(m_regDate1Low);
m_date1.dwHighDateTime = bPortable ? g_iniFile.GetLongValue(L"global", L"Date1High", 0) : static_cast<DWORD>(m_regDate1High);
m_date2.dwLowDateTime = bPortable ? g_iniFile.GetLongValue(L"global", L"Date2Low", 0) : static_cast<DWORD>(m_regDate2Low);
m_date2.dwHighDateTime = bPortable ? g_iniFile.GetLongValue(L"global", L"Date2High", 0) : static_cast<DWORD>(m_regDate2High);
}
m_bUseRegex = (bPortable ? g_iniFile.GetBoolValue(L"global", L"UseRegex", false) : static_cast<DWORD>(m_regUseRegex));
m_bStayOnTop = (BYTE)(bPortable ? g_iniFile.GetBoolValue(L"global", L"StayOnTop", false) : static_cast<DWORD>(m_regStayOnTop));
m_OpacityNoFocus = (BYTE)(bPortable ? g_iniFile.GetLongValue(L"global", L"OpacityNoFocus", 100) : static_cast<DWORD>(m_regOpacityNoFocus));
m_OpacityNoFocus = (m_OpacityNoFocus > 100) ? 100 : m_OpacityNoFocus;
SendDlgItemMessage(hwndDlg, IDC_SIZECOMBO, CB_SETCURSEL, m_sizeCmp, 0);
SendDlgItemMessage(hwndDlg, IDC_INCLUDESUBFOLDERS, BM_SETCHECK, m_bIncludeSubfolders ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(hwndDlg, IDC_CREATEBACKUP, BM_SETCHECK, m_bCreateBackup ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(hwndDlg, IDC_UTF8, BM_SETCHECK, m_bUTF8 ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(hwndDlg, IDC_BINARY, BM_SETCHECK, m_bForceBinary ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(hwndDlg, IDC_INCLUDESYSTEM, BM_SETCHECK, m_bIncludeSystem ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(hwndDlg, IDC_INCLUDEHIDDEN, BM_SETCHECK, m_bIncludeHidden ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(hwndDlg, IDC_INCLUDEBINARY, BM_SETCHECK, m_bIncludeBinary ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(hwndDlg, IDC_CASE_SENSITIVE, BM_SETCHECK, m_bCaseSensitive ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(hwndDlg, IDC_DOTMATCHNEWLINE, BM_SETCHECK, m_bDotMatchesNewline ? BST_CHECKED : BST_UNCHECKED, 0);
CheckRadioButton(hwndDlg, IDC_REGEXRADIO, IDC_TEXTRADIO, (bPortable ? g_iniFile.GetLongValue(L"global", L"UseRegex", 0) : static_cast<DWORD>(m_regUseRegex)) ? IDC_REGEXRADIO : IDC_TEXTRADIO);
CheckRadioButton(hwndDlg, IDC_ALLSIZERADIO, IDC_SIZERADIO, m_bAllSize ? IDC_ALLSIZERADIO : IDC_SIZERADIO);
CheckRadioButton(hwndDlg, IDC_FILEPATTERNREGEX, IDC_FILEPATTERNTEXT, m_bUseRegexForPaths ? IDC_FILEPATTERNREGEX : IDC_FILEPATTERNTEXT);
SendDlgItemMessage(hwndDlg, IDC_WHOLEWORDS, BM_SETCHECK, m_bWholeWords ? BST_CHECKED : BST_UNCHECKED, 0);
DialogEnableWindow(IDC_WHOLEWORDS, IsDlgButtonChecked(hwndDlg, IDC_TEXTRADIO));
if (!m_searchString.empty())
CheckRadioButton(*this, IDC_REGEXRADIO, IDC_TEXTRADIO, m_bUseRegex ? IDC_REGEXRADIO : IDC_TEXTRADIO);
DialogEnableWindow(IDC_TESTREGEX, !IsDlgButtonChecked(*this, IDC_TEXTRADIO));
DialogEnableWindow(IDC_ADDTOBOOKMARKS, FALSE);
DialogEnableWindow(IDC_EXCLUDEDIRSPATTERN, !!m_bIncludeSubfolders);
::SetDlgItemText(*this, IDOK, TranslatedString(hResource, IDS_SEARCH).c_str());
if (!m_showContentSet)
{
if (bPortable)
{
m_showContent = g_iniFile.GetBoolValue(L"global", L"showcontent", false);
}
else
{
m_showContent = static_cast<DWORD>(m_regShowContent) != 0;
}
}
CheckRadioButton(*this, IDC_RESULTFILES, IDC_RESULTCONTENT, m_showContent ? IDC_RESULTCONTENT : IDC_RESULTFILES);
CheckRadioButton(hwndDlg, IDC_RADIO_DATE_ALL, IDC_RADIO_DATE_BETWEEN, m_dateLimit + IDC_RADIO_DATE_ALL);
SYSTEMTIME sysTime{};
auto hTime1 = GetDlgItem(hwndDlg, IDC_DATEPICK1);
FileTimeToSystemTime(&m_date1, &sysTime);
DateTime_SetSystemtime(hTime1, GDT_VALID, &sysTime);
auto hTime2 = GetDlgItem(hwndDlg, IDC_DATEPICK2);
FileTimeToSystemTime(&m_date2, &sysTime);
DateTime_SetSystemtime(hTime2, GDT_VALID, &sysTime);
ShowWindow(GetDlgItem(*this, IDC_DATEPICK2), (m_dateLimit == IDC_RADIO_DATE_BETWEEN - IDC_RADIO_DATE_ALL) ? SW_SHOW : SW_HIDE);
ShowWindow(GetDlgItem(*this, IDC_DATEPICK1), (m_dateLimit != 0) ? SW_SHOW : SW_HIDE);
SetFocus(GetDlgItem(hwndDlg, IDC_SEARCHTEXT));
m_resizer.Init(hwndDlg);
m_resizer.UseSizeGrip(!CTheme::Instance().IsDarkTheme());
m_resizer.AddControl(hwndDlg, IDC_HELPLABEL, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_ABOUTLINK, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_GROUPSEARCHIN, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_PATHMRU, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_SEARCHPATH, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_NEWINSTANCE, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_SEARCHPATHBROWSE, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_GROUPSEARCHFOR, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_REGEXRADIO, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_TEXTRADIO, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_WHOLEWORDS, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_SEARCHFORLABEL, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_SEARCHTEXT, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_EDITMULTILINE1, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_REPLACEWITHLABEL, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_REPLACETEXT, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_EDITMULTILINE2, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_CASE_SENSITIVE, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_DOTMATCHNEWLINE, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_REGEXOKLABEL, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_CREATEBACKUP, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_UTF8, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_BINARY, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_TESTREGEX, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_ADDTOBOOKMARKS, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_BOOKMARKS, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_UPDATELINK, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_GROUPLIMITSEARCH, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_ALLSIZERADIO, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_SIZERADIO, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_SIZECOMBO, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_SIZEEDIT, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_KBTEXT, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_RADIO_DATE_ALL, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_RADIO_DATE_NEWER, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_RADIO_DATE_OLDER, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_RADIO_DATE_BETWEEN, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_DATEPICK1, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_DATEPICK2, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_INCLUDESYSTEM, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_INCLUDEHIDDEN, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_INCLUDESUBFOLDERS, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_INCLUDEBINARY, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_PATTERNLABEL, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_PATTERN, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_PATTERNMRU, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_EXCLUDE_DIRS_PATTERNLABEL, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_EXCLUDEDIRSPATTERN, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_EXCLUDEDIRMRU, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_FILEPATTERNREGEX, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_FILEPATTERNTEXT, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_SETTINGSBUTTON, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_PROGRESS, RESIZER_TOPLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_REPLACE, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDOK, RESIZER_TOPRIGHT);
m_resizer.AddControl(hwndDlg, IDC_GROUPSEARCHRESULTS, RESIZER_TOPLEFTBOTTOMRIGHT);
m_resizer.AddControl(hwndDlg, IDC_RESULTLIST, RESIZER_TOPLEFTBOTTOMRIGHT);
m_resizer.AddControl(hwndDlg, IDC_SEARCHINFOLABEL, RESIZER_BOTTOMLEFTRIGHT);
m_resizer.AddControl(hwndDlg, IDC_RESULTFILES, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_RESULTCONTENT, RESIZER_TOPLEFT);
m_resizer.AddControl(hwndDlg, IDC_EXPORT, RESIZER_TOPLEFT);
InitDialog(hwndDlg, IDI_GREPWIN);
WINDOWPLACEMENT wpl = {0};
DWORD size = sizeof(wpl);
std::wstring winPosKey = L"windowpos_" + GetMonitorSetupHash();
if (bPortable)
{
std::wstring sPos = g_iniFile.GetValue(L"global", winPosKey.c_str(), L"");
if (!sPos.empty())
{
auto read = swscanf_s(sPos.c_str(), L"%d;%d;%d;%d;%d;%d;%d;%d;%d;%d",
&wpl.flags, &wpl.showCmd,
&wpl.ptMinPosition.x, &wpl.ptMinPosition.y,
&wpl.ptMaxPosition.x, &wpl.ptMaxPosition.y,
&wpl.rcNormalPosition.left, &wpl.rcNormalPosition.top,
&wpl.rcNormalPosition.right, &wpl.rcNormalPosition.bottom);
wpl.length = sizeof(wpl);
if (read == 10)
SetWindowPlacement(*this, &wpl);
else
ShowWindow(*this, SW_SHOW);
}
else
ShowWindow(*this, SW_SHOW);
}
else
{
if (SHGetValue(HKEY_CURRENT_USER, L"Software\\grepWinNP3", winPosKey.c_str(), REG_NONE, &wpl, &size) == ERROR_SUCCESS)
SetWindowPlacement(*this, &wpl);
else
ShowWindow(*this, SW_SHOW);
}
SetWindowPos(*this, m_bStayOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
CheckMenuItem(GetSystemMenu(*this, FALSE), ID_STAY_ON_TOP, m_bStayOnTop ? MF_BYCOMMAND | MF_CHECKED : MF_BYCOMMAND | MF_UNCHECKED);
InitResultList();
#ifdef NP3_ALLOW_UPDATE
bool doCheck = true;
if (bPortable)
doCheck = g_iniFile.GetBoolValue(L"global", L"CheckForUpdates", true));
else
doCheck = !!static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWin\\CheckForUpdates", 1));
if (doCheck)
{
m_updateCheckThread = std::move(std::thread([&]() { CheckForUpdates(); }));
ShowUpdateAvailable();
}
#endif
if (hInitProtection)
CloseHandle(hInitProtection);
hInitProtection = nullptr;
switch (m_executeImmediately)
{
case ExecuteAction::Search:
DoCommand(IDOK, 0);
break;
case ExecuteAction::Replace:
DoCommand(IDC_REPLACE, 0);
break;
case ExecuteAction::Capture:
DoCommand(IDC_CAPTURESEARCH, 0);
break;
case ExecuteAction::None:
default:
break;
}
}
return FALSE;
case WM_CLOSE:
{
#ifdef NP3_ALLOW_UPDATE
if (m_updateCheckThread.joinable())
m_updateCheckThread.join();
#endif
if (IsEvaluationThreadRunning())
InterlockedExchange(&s_Cancelled, TRUE);
else
{
SaveSettings();
if (!m_bNoSaveSettings)
{
m_autoCompleteFilePatterns.Save();
m_autoCompleteSearchPatterns.Save();
m_autoCompleteReplacePatterns.Save();
m_autoCompleteSearchPaths.Save();
}
EndDialog(*this, IDCANCEL);
}
}
break;
case WM_DESTROY:
CTheme::Instance().RemoveRegisteredCallback(m_themeCallbackId);
break;
case WM_COMMAND:
return DoCommand(LOWORD(wParam), HIWORD(wParam));
case WM_CONTEXTMENU:
{
if (reinterpret_cast<HWND>(wParam) == GetDlgItem(*this, IDC_RESULTLIST))
{
ShowContextMenu(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
}
}
break;
case WM_NOTIFY:
{
if (reinterpret_cast<LPNMHDR>(lParam)->code == TTN_GETDISPINFO)
{
auto lpnmtdi = reinterpret_cast<LPNMTTDISPINFOW>(lParam);
auto buf = GetDlgItemText(IDC_REPLACETEXT);
m_toolTipReplaceString = ExpandString(buf.get());
lpnmtdi->lpszText = m_toolTipReplaceString.data();
}
switch (wParam)
{
case IDC_RESULTLIST:
DoListNotify(reinterpret_cast<LPNMITEMACTIVATE>(lParam));
break;
case IDOK:
switch (reinterpret_cast<LPNMHDR>(lParam)->code)
{
case BCN_DROPDOWN:
{
const NMBCDROPDOWN* pDropDown = reinterpret_cast<NMBCDROPDOWN*>(lParam);
// Get screen coordinates of the button.
POINT pt;
pt.x = pDropDown->rcButton.left;
pt.y = pDropDown->rcButton.bottom;
ClientToScreen(pDropDown->hdr.hwndFrom, &pt);
// Create a menu and add items.
HMENU hSplitMenu = CreatePopupMenu();
if (!hSplitMenu)
break;
OnOutOfScope(DestroyMenu(hSplitMenu));
if (pDropDown->hdr.hwndFrom == GetDlgItem(*this, IDOK))
{
auto buf = GetDlgItemText(IDC_SEARCHPATH);
bool bIsDir = !!PathIsDirectory(buf.get());
if ((!bIsDir) && wcschr(buf.get(), '|'))
bIsDir = true; // assume directories in case of multiple paths
auto sInverseSearch = TranslatedString(hResource, IDS_INVERSESEARCH);
auto sSearchInFoundFiles = TranslatedString(hResource, IDS_SEARCHINFOUNDFILES);
auto sCaptureSearch = TranslatedString(hResource, IDS_CAPTURESEARCH);
AppendMenu(hSplitMenu, bIsDir ? MF_STRING : MF_STRING | MF_DISABLED, IDC_INVERSESEARCH, sInverseSearch.c_str());
AppendMenu(hSplitMenu, m_items.empty() ? MF_STRING | MF_DISABLED : MF_STRING, IDC_SEARCHINFOUNDFILES, sSearchInFoundFiles.c_str());
AppendMenu(hSplitMenu, GetDlgItemTextLength(IDC_REPLACETEXT) ? MF_STRING : MF_STRING | MF_DISABLED, IDC_CAPTURESEARCH, sCaptureSearch.c_str());
}
// Display the menu.
TrackPopupMenu(hSplitMenu, TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, *this, nullptr);
return TRUE;
}
default:
break;
}
break;
#ifdef NP3_ALLOW_UPDATE
case IDC_UPDATELINK:
switch (reinterpret_cast<LPNMHDR>(lParam)->code)
{
case NM_CLICK:
case NM_RETURN:
{
PNMLINK pNMLink = reinterpret_cast<PNMLINK>(lParam);
LITEM item = pNMLink->item;
if (item.iLink == 0)
{
ShellExecute(*this, L"open", item.szUrl, nullptr, nullptr, SW_SHOW);
}
break;
}
default:
break;
}
break;
#endif
case IDC_ABOUTLINK:
switch (reinterpret_cast<LPNMHDR>(lParam)->code)
{
case NM_CLICK:
case NM_RETURN:
{
PNMLINK pNMLink = reinterpret_cast<PNMLINK>(lParam);
LITEM item = pNMLink->item;
if (item.iLink == 0)
{
CAboutDlg dlgAbout(*this);
dlgAbout.DoModal(hResource, IDD_ABOUT, *this);
}
break;
}
default:
break;
}
break;
}
}
break;
case WM_SIZE:
{
m_resizer.DoResize(LOWORD(lParam), HIWORD(lParam));
}
break;
case WM_GETMINMAXINFO:
{
MINMAXINFO* mmi = reinterpret_cast<MINMAXINFO*>(lParam);
mmi->ptMinTrackSize.x = m_resizer.GetDlgRect()->right;
mmi->ptMinTrackSize.y = m_resizer.GetDlgRect()->bottom;
return 0;
}
case WM_DPICHANGED:
{
const RECT* rect = reinterpret_cast<RECT*>(lParam);
SetWindowPos(*this, nullptr, rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
::RedrawWindow(*this, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE | RDW_ERASE | RDW_INTERNALPAINT | RDW_ALLCHILDREN | RDW_UPDATENOW);
}
break;
case WM_SETCURSOR:
{
if (IsEvaluationThreadRunning())
{
SetCursor(LoadCursor(nullptr, IDC_APPSTARTING));
return TRUE;
}
return FALSE;
}
case SEARCH_START:
{
m_totalItems = 0;
m_searchedItems = 0;
m_totalMatches = 0;
m_selectedItems = 0;
UpdateInfoLabel();
SetTimer(*this, LABELUPDATETIMER, 200, nullptr);
SendDlgItemMessage(*this, IDC_PROGRESS, PBM_SETSTATE, PBST_NORMAL, 0);
SendDlgItemMessage(*this, IDC_PROGRESS, PBM_SETMARQUEE, 1, 0);
}
break;
//case SEARCH_FOUND:
case SEARCH_PROGRESS:
{
const CSearchInfo* const sInfo = (const CSearchInfo* const)(wParam);
int const nFound = static_cast<int>(lParam);
m_totalMatches += static_cast<int>(sInfo->matchCount);
if ((nFound > 0) || (m_searchString.empty()) || (sInfo->readError) || IsNOTSearch())
{
AddFoundEntry(sInfo);
UpdateInfoLabel();
}
if (nFound >= 0)
m_searchedItems++;
m_totalItems++;
}
break;
case SEARCH_END:
{
AddFoundEntry(nullptr, true);
AutoSizeAllColumns();
UpdateInfoLabel();
::SetDlgItemText(*this, IDOK, TranslatedString(hResource, IDS_SEARCH).c_str());
DialogEnableWindow(IDC_RESULTFILES, true);
DialogEnableWindow(IDC_RESULTCONTENT, true);
ShowWindow(GetDlgItem(*this, IDC_PROGRESS), SW_HIDE);
SendDlgItemMessage(*this, IDC_PROGRESS, PBM_SETMARQUEE, 0, 0);
if (m_pTaskbarList)
m_pTaskbarList->SetProgressState(*this, TBPF_NOPROGRESS);
ShowWindow(GetDlgItem(*this, IDC_EXPORT), m_items.empty() ? SW_HIDE : SW_SHOW);
KillTimer(*this, LABELUPDATETIMER);
}
break;
case WM_TIMER:
{
if (wParam == LABELUPDATETIMER)
{
AddFoundEntry(nullptr, true);
UpdateInfoLabel();
}
}
break;
case WM_HELP:
{
CInfoDlg::ShowDialog(*this, IDR_INFODLG, hResource);
}
break;
case WM_SYSCOMMAND:
{
switch (wParam & 0xFFFF)
{
case ID_ABOUTBOX:
{
CAboutDlg dlgAbout(*this);
dlgAbout.DoModal(hResource, IDD_ABOUT, *this);
}
break;
case ID_CLONE:
{
CloneWindow();
}
break;
case ID_STAY_ON_TOP:
{
m_bStayOnTop = !m_bStayOnTop; // toggle
SetWindowPos(*this, m_bStayOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
CheckMenuItem(GetSystemMenu(*this, FALSE), ID_STAY_ON_TOP, m_bStayOnTop ? MF_BYCOMMAND | MF_CHECKED : MF_BYCOMMAND | MF_UNCHECKED);
}
break;
default:
break;
}
}
break;
case WM_COPYDATA:
{
if (lParam)
{
auto pCopyData = (PCOPYDATASTRUCT)lParam;
switch(pCopyData->dwData)
{
case GREPWINNP3_CPYDAT:
{
const wchar_t* const searchPath = ((CopyData_t*)(pCopyData->lpData))->searchPath;
if (searchPath && (searchPath[0] != L'\0'))
{
m_searchPath.clear();
m_searchPath = searchPath;
SetDlgItemText(hwndDlg, IDC_SEARCHPATH, m_searchPath.c_str());
}
const wchar_t* const searchFor = ((CopyData_t*)(pCopyData->lpData))->searchFor;
if (searchFor && (searchFor[0] != L'\0'))
{
m_searchString.clear();
m_searchString = searchFor;
SetDlgItemText(hwndDlg, IDC_SEARCHTEXT, m_searchString.c_str());
}
g_startTime = GetTickCount64();
}
break;
default:
break;
}
}
return TRUE;
}
case WM_EDITDBLCLICK:
{
switch (wParam)
{
case IDC_PATTERN:
{
m_autoCompleteFilePatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST | ACO_NOPREFIXFILTERING);
::SetFocus(GetDlgItem(*this, IDC_PATTERN));
SendDlgItemMessage(*this, IDC_PATTERN, WM_KEYDOWN, VK_DOWN, 0);
}
break;
case IDC_EXCLUDEDIRSPATTERN:
{
m_autoCompleteExcludeDirsPatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST | ACO_NOPREFIXFILTERING);
::SetFocus(GetDlgItem(*this, IDC_EXCLUDEDIRSPATTERN));
SendDlgItemMessage(*this, IDC_EXCLUDEDIRSPATTERN, WM_KEYDOWN, VK_DOWN, 0);
}
break;
case IDC_SEARCHTEXT:
{
m_autoCompleteSearchPatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST | ACO_NOPREFIXFILTERING);
::SetFocus(GetDlgItem(*this, IDC_SEARCHTEXT));
SendDlgItemMessage(*this, IDC_SEARCHTEXT, WM_KEYDOWN, VK_DOWN, 0);
}
break;
case IDC_REPLACETEXT:
{
m_autoCompleteReplacePatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST | ACO_NOPREFIXFILTERING);
::SetFocus(GetDlgItem(*this, IDC_REPLACETEXT));
SendDlgItemMessage(*this, IDC_REPLACETEXT, WM_KEYDOWN, VK_DOWN, 0);
}
break;
case IDC_SEARCHPATH:
{
m_autoCompleteSearchPaths.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST | ACO_NOPREFIXFILTERING);
::SetFocus(GetDlgItem(*this, IDC_SEARCHPATH));
SendDlgItemMessage(*this, IDC_SEARCHPATH, WM_KEYDOWN, VK_DOWN, 0);
}
break;
default:
break;
}
return TRUE;
}
case WM_GREPWIN_THREADEND:
{
if (m_endDialog)
EndDialog(m_hwnd, IDOK);
}
break;
case WM_BOOKMARK:
{
if (m_bookmarksDlg)
{
m_searchString = m_bookmarksDlg->GetSelectedSearchString();
m_replaceString = m_bookmarksDlg->GetSelectedReplaceString();
m_bUseRegex = m_bookmarksDlg->GetSelectedUseRegex();
m_bCaseSensitive = m_bookmarksDlg->GetSelectedSearchCase();
m_bDotMatchesNewline = m_bookmarksDlg->GetSelectedDotMatchNewline();
m_bCreateBackup = m_bookmarksDlg->GetSelectedBackup();
m_bWholeWords = m_bookmarksDlg->GetSelectedWholeWords();
m_bUTF8 = m_bookmarksDlg->GetSelectedTreatAsUtf8();
m_bForceBinary = m_bookmarksDlg->GetSelectedTreatAsBinary();
m_bIncludeSystem = m_bookmarksDlg->GetSelectedIncludeSystem();
m_bIncludeSubfolders = m_bookmarksDlg->GetSelectedIncludeFolder();
m_bIncludeHidden = m_bookmarksDlg->GetSelectedIncludeHidden();
m_bIncludeBinary = m_bookmarksDlg->GetSelectedIncludeBinary();
m_excludeDirsPatternRegex = m_bookmarksDlg->GetSelectedExcludeDirs();
m_patternRegex = m_bookmarksDlg->GetSelectedFileMatch();
m_bUseRegexForPaths = m_bookmarksDlg->GetSelectedFileMatchRegex();
if (!m_bookmarksDlg->GetPath().empty())
{
m_searchPath = m_bookmarksDlg->GetPath();
SetDlgItemText(*this, IDC_SEARCHPATH, m_searchPath.c_str());
}
SetDlgItemText(*this, IDC_SEARCHTEXT, m_searchString.c_str());
SetDlgItemText(*this, IDC_REPLACETEXT, m_replaceString.c_str());
CheckRadioButton(*this, IDC_REGEXRADIO, IDC_TEXTRADIO, m_bUseRegex ? IDC_REGEXRADIO : IDC_TEXTRADIO);
DialogEnableWindow(IDC_TESTREGEX, !IsDlgButtonChecked(*this, IDC_TEXTRADIO));
SendDlgItemMessage(*this, IDC_INCLUDESUBFOLDERS, BM_SETCHECK, m_bIncludeSubfolders ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(*this, IDC_CREATEBACKUP, BM_SETCHECK, m_bCreateBackup ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(*this, IDC_UTF8, BM_SETCHECK, m_bUTF8 ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(*this, IDC_BINARY, BM_SETCHECK, m_bForceBinary ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(*this, IDC_INCLUDESYSTEM, BM_SETCHECK, m_bIncludeSystem ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(*this, IDC_INCLUDEHIDDEN, BM_SETCHECK, m_bIncludeHidden ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(*this, IDC_INCLUDEBINARY, BM_SETCHECK, m_bIncludeBinary ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(*this, IDC_CASE_SENSITIVE, BM_SETCHECK, m_bCaseSensitive ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(*this, IDC_DOTMATCHNEWLINE, BM_SETCHECK, m_bDotMatchesNewline ? BST_CHECKED : BST_UNCHECKED, 0);
SendDlgItemMessage(*this, IDC_WHOLEWORDS, BM_SETCHECK, m_bWholeWords ? BST_CHECKED : BST_UNCHECKED, 0);
CheckRadioButton(*this, IDC_FILEPATTERNREGEX, IDC_FILEPATTERNTEXT, m_bUseRegexForPaths ? IDC_FILEPATTERNREGEX : IDC_FILEPATTERNTEXT);
SetDlgItemText(*this, IDC_EXCLUDEDIRSPATTERN, m_excludeDirsPatternRegex.c_str());
SetDlgItemText(*this, IDC_PATTERN, m_patternRegex.c_str());
DialogEnableWindow(IDC_WHOLEWORDS, IsDlgButtonChecked(hwndDlg, IDC_TEXTRADIO));
}
}
break;
case WM_ACTIVATE:
switch (LOWORD(wParam))
{
case WA_INACTIVE:
SetTransparency((BYTE)MulDiv(m_OpacityNoFocus, 255, 100));
break;
case WA_CLICKACTIVE:
// mouse click activation
case WA_ACTIVE:
SetTransparency(ALPHA_OPAQUE);
break;
}
break;
default:
return FALSE;
}
return FALSE;
}
LRESULT CSearchDlg::DoCommand(int id, int msg)
{
switch (id)
{
case IDC_REPLACE:
case IDOK:
case IDC_INVERSESEARCH:
case IDC_SEARCHINFOUNDFILES:
case IDC_CAPTURESEARCH:
{
if (IsSearchThreadRunning())
{
InterlockedExchange(&s_Cancelled, TRUE);
SendDlgItemMessage(*this, IDC_PROGRESS, PBM_SETSTATE, PBST_PAUSED, 0);
}
else
{
::SetFocus(GetDlgItem(*this, IDOK));
if (!SaveSettings())
break;
CStringUtils::rtrim(m_searchPath, L"\\/");
if (PathIsRelative(m_searchPath.c_str()))
{
ShowEditBalloon(IDC_SEARCHPATH, TranslatedString(hResource, IDS_ERR_INVALID_PATH).c_str(), TranslatedString(hResource, IDS_ERR_RELATIVEPATH).c_str());
break;
}
std::vector<std::wstring> searchPaths;
stringtok(searchPaths, m_searchPath, true);
for (const auto& sp : searchPaths)
{
if (!PathFileExists(sp.c_str()))
{
ShowEditBalloon(IDC_SEARCHPATH, TranslatedString(hResource, IDS_ERR_INVALID_PATH).c_str(), TranslatedString(hResource, IDS_ERR_PATHNOTEXIST).c_str());
break;
}
}
if ((id == IDC_SEARCHINFOUNDFILES) && (!m_items.empty()))
{
m_searchPath.clear();
for (const auto& item : m_items)
{
if (!m_searchPath.empty())
m_searchPath += L"|";
m_searchPath += item.filePath;
}
}
std::vector<std::wstring> searchpaths;
stringtok(searchpaths, m_searchPath, true);
bool ok = true;
for (const auto& sp : searchpaths)
{
if (!PathFileExists(sp.c_str()))
{
ShowEditBalloon(IDC_SEARCHPATH, TranslatedString(hResource, IDS_ERR_INVALID_PATH).c_str(), TranslatedString(hResource, IDS_ERR_PATHNOTEXIST).c_str());
ok = false;
break;
}
}
if (!ok)
break;
m_searchedItems = 0;
m_totalItems = 0;
ShowWindow(GetDlgItem(*this, IDC_EXPORT), SW_HIDE);
m_items.clear();
m_listItems.clear();
m_listItems.reserve(500000);
s_BackupAndTmpFiles.clear();
if (m_searchString.empty())
{
// switch to file view
CheckRadioButton(*this, IDC_RESULTFILES, IDC_RESULTCONTENT, IDC_RESULTFILES);
m_showContent = false;
InitResultList();
}
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
ListView_SetItemCount(hListControl, 0);
DialogEnableWindow(IDC_RESULTFILES, false);
DialogEnableWindow(IDC_RESULTCONTENT, false);
m_autoCompleteFilePatterns.AddEntry(m_patternRegex.c_str());
m_autoCompleteExcludeDirsPatterns.AddEntry(m_excludeDirsPatternRegex.c_str());
m_autoCompleteSearchPatterns.AddEntry(m_searchString.c_str());
m_autoCompleteReplacePatterns.AddEntry(m_replaceString.c_str());
m_autoCompleteSearchPaths.AddEntry(m_searchPath.c_str());
if (!m_bNoSaveSettings)
{
m_autoCompleteFilePatterns.Save();
m_autoCompleteSearchPatterns.Save();
m_autoCompleteReplacePatterns.Save();
m_autoCompleteSearchPaths.Save();
}
m_bReplace = id == IDC_REPLACE;
if (m_bReplace && !m_bCreateBackup && m_bConfirmationOnReplace)
{
auto nowarnifnobackup = bPortable ? g_iniFile.GetBoolValue(L"settings", L"nowarnifnobackup", false) : static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWinNP3\\nowarnifnobackup", FALSE));
if (!nowarnifnobackup)
{
auto msgText = CStringUtils::Format(static_cast<LPCWSTR>(TranslatedString(hResource, IDS_REPLACECONFIRM).c_str()),
m_searchString.c_str(),
m_replaceString.empty() ? static_cast<LPCWSTR>(TranslatedString(hResource, IDS_ANEMPTYSTRING).c_str()) : m_replaceString.c_str());
if (::MessageBox(*this, msgText.c_str(), L"grepWinNP3", MB_ICONQUESTION | MB_YESNO) != IDYES)
{
break;
}
}
}
if (m_bReplace && m_bUTF8)
{
auto utf8OptionText = GetDlgItemText(IDC_UTF8);
auto msgText = CStringUtils::Format(TranslatedString(hResource, IDS_REPLACEUTF8).c_str(),
utf8OptionText.get());
if (::MessageBox(*this, msgText.c_str(), L"grepWinNP3", MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2) != IDYES)
{
break;
}
}
m_bConfirmationOnReplace = true;
if (id == IDC_INVERSESEARCH)
InterlockedExchange(&s_NOTSearch, TRUE);
else
InterlockedExchange(&s_NOTSearch, ((GetKeyState(VK_SHIFT) & 0x8000) != 0) ? TRUE : FALSE);
if (id == IDC_CAPTURESEARCH)
{
m_bCaptureSearch = true;
InterlockedExchange(&s_NOTSearch, FALSE);
m_bReplace = false;
}
if (m_bReplace)
{
m_replaceString = ExpandString(m_replaceString);
}
InterlockedExchange(&s_SearchThreadRunning, TRUE);
InterlockedExchange(&s_Cancelled, FALSE);
SetDlgItemText(*this, IDOK, TranslatedString(hResource, IDS_STOP).c_str());
SetDlgItemText(*this, IDOK, TranslatedString(hResource, IDS_STOP).c_str());
ShowWindow(GetDlgItem(*this, IDC_PROGRESS), SW_SHOW);
SendDlgItemMessage(*this, IDC_PROGRESS, PBM_SETMARQUEE, 1, 0);
if (m_pTaskbarList)
m_pTaskbarList->SetProgressState(*this, TBPF_INDETERMINATE);
// now start the thread which does the searching
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(nullptr, // no security attribute
0, // default stack size
SearchThreadEntry,
static_cast<LPVOID>(this), // thread parameter
0, // not suspended
&dwThreadId); // returns thread ID
if (hThread != nullptr)
{
// Closing the handle of a running thread just decreases
// the ref count for the thread object.
CloseHandle(hThread);
}
else
{
InterlockedExchange(&s_Cancelled, TRUE);
InterlockedExchange(&s_SearchThreadRunning, FALSE);
ShowWindow(GetDlgItem(*this, IDC_PROGRESS), SW_HIDE);
EnableWindow(GetDlgItem(*this, IDC_SETTINGSBUTTON), TRUE);
}
DWORD const nMax = std::thread::hardware_concurrency() << 2;
DWORD const nOfWorker = bPortable ? g_iniFile.GetLongValue(L"global", L"MaxNumOfWorker", nMax) :
static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWinNP3\\MaxNumOfWorker", nMax));
if (nOfWorker > 1)
{
InterlockedExchange(&s_EvaluationThreadRunning, TRUE);
// now start the thread which does result evaluation
dwThreadId = 0;
hThread = CreateThread(
nullptr, // no security attribute
0, // default stack size
EvaluationThreadEntry,
(LPVOID)this, // thread parameter
0, // not suspended
&dwThreadId); // returns thread ID
if (hThread != nullptr)
{
// Closing the handle of a running thread just decreases
// the ref count for the thread object.
CloseHandle(hThread);
}
else
{
InterlockedExchange(&s_Cancelled, TRUE);
SendMessage(*this, SEARCH_END, 0, 0);
InterlockedExchange(&s_EvaluationThreadRunning, FALSE);
ShowWindow(GetDlgItem(*this, IDC_PROGRESS), SW_HIDE);
EnableWindow(GetDlgItem(*this, IDC_SETTINGSBUTTON), TRUE);
}
}
}
}
break;
case IDCANCEL:
{
#ifdef NP3_ALLOW_UPDATE
if (m_updateCheckThread.joinable())
m_updateCheckThread.join();
#endif
bool const escClose = bPortable ? g_iniFile.GetBoolValue(L"settings", L"escclose", false) :
!!static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWinNP3\\escclose", FALSE));
if (escClose)
{
if (IsEvaluationThreadRunning())
InterlockedExchange(&s_Cancelled, TRUE);
else
{
SaveSettings();
if (!m_bNoSaveSettings)
{
m_autoCompleteFilePatterns.Save();
m_autoCompleteSearchPatterns.Save();
m_autoCompleteReplacePatterns.Save();
m_autoCompleteSearchPaths.Save();
}
EndDialog(*this, IDCANCEL);
}
}
}
break;
case IDC_RADIO_DATE_ALL:
case IDC_RADIO_DATE_NEWER:
case IDC_RADIO_DATE_OLDER:
case IDC_RADIO_DATE_BETWEEN:
{
auto isBetween = IsDlgButtonChecked(*this, IDC_RADIO_DATE_BETWEEN) == BST_CHECKED;
ShowWindow(GetDlgItem(*this, IDC_DATEPICK2), isBetween ? SW_SHOW : SW_HIDE);
ShowWindow(GetDlgItem(*this, IDC_DATEPICK1), IsDlgButtonChecked(*this, IDC_RADIO_DATE_ALL) ? SW_HIDE : SW_SHOW);
}
break;
case IDC_TESTREGEX:
{
// get all the information we need from the dialog
auto buf = GetDlgItemText(IDC_SEARCHTEXT);
m_searchString = buf.get();
buf = GetDlgItemText(IDC_REPLACETEXT);
m_replaceString = buf.get();
SaveSettings();
CRegexTestDlg dlg(*this);
dlg.bCaseSensitive = m_bCaseSensitive;
dlg.bDotMatchesNewline = m_bDotMatchesNewline;
dlg.SetStrings(m_searchString, m_replaceString);
if (dlg.DoModal(hResource, IDD_REGEXTEST, *this) == IDOK)
{
m_searchString = dlg.GetSearchString();
m_replaceString = dlg.GetReplaceString();
SetDlgItemText(*this, IDC_SEARCHTEXT, m_searchString.c_str());
SetDlgItemText(*this, IDC_REPLACETEXT, m_replaceString.c_str());
}
}
break;
case IDC_NEWINSTANCE:
CloneWindow();
break;
case IDC_SEARCHPATHBROWSE:
{
CBrowseFolder browse;
auto path = GetDlgItemText(IDC_SEARCHPATH);
if (!PathFileExists(path.get()))
{
auto ptr = wcsstr(path.get(), L"|");
if (ptr)
*ptr = 0;
else
path.get()[0] = 0;
}
if (wcsstr(path.get(), L"..") != nullptr)
{
ShowEditBalloon(IDC_SEARCHPATH, TranslatedString(hResource, IDS_ERR_INVALID_PATH).c_str(), TranslatedString(hResource, IDS_ERR_RELATIVEPATH).c_str());
break;
}
std::vector<std::wstring> paths;
if (browse.Show(*this, paths, m_searchPath) == CBrowseFolder::RetVal::Ok)
{
std::wstring pathString;
for (const auto& selPath : paths)
{
if (pathString.empty())
pathString = selPath;
else
{
pathString += L"|";
pathString += selPath;
}
}
SetDlgItemText(*this, IDC_SEARCHPATH, pathString.c_str());
m_searchPath = pathString;
}
}
break;
case IDC_SEARCHPATH:
{
if (msg == EN_CHANGE)
{
if (m_autoCompleteSearchPaths.GetOptions() & ACO_NOPREFIXFILTERING)
m_autoCompleteSearchPaths.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST);
int len = GetDlgItemTextLength(IDC_SEARCHTEXT);
auto buf = GetDlgItemText(IDC_SEARCHPATH);
bool bIsDir = !!PathIsDirectory(buf.get());
if ((!bIsDir) && wcschr(buf.get(), '|'))
bIsDir = true; // assume directories in case of multiple paths
bool bIncludeSubfolders = (IsDlgButtonChecked(*this, IDC_INCLUDESUBFOLDERS) == BST_CHECKED);
DialogEnableWindow(IDC_ALLSIZERADIO, bIsDir);
DialogEnableWindow(IDC_SIZERADIO, bIsDir);
DialogEnableWindow(IDC_SIZECOMBO, bIsDir);
DialogEnableWindow(IDC_SIZEEDIT, bIsDir);
DialogEnableWindow(IDC_INCLUDESYSTEM, bIsDir);
DialogEnableWindow(IDC_INCLUDEHIDDEN, bIsDir);
DialogEnableWindow(IDC_INCLUDESUBFOLDERS, bIsDir);
DialogEnableWindow(IDC_INCLUDEBINARY, bIsDir && len > 0);
DialogEnableWindow(IDC_PATTERN, bIsDir);
DialogEnableWindow(IDC_EXCLUDEDIRSPATTERN, bIsDir || bIncludeSubfolders);
DialogEnableWindow(IDC_FILEPATTERNREGEX, bIsDir);
DialogEnableWindow(IDC_FILEPATTERNTEXT, bIsDir);
// change the dialog title to "grepWinNP3 : search/path"
wchar_t compactPath[100] = {0};
PathCompactPathEx(compactPath, buf.get(), 40, 0);
wchar_t titleBuf[MAX_PATH] = {0};
swprintf_s(titleBuf, _countof(titleBuf), L"grepWinNP3 : %s", compactPath);
SetWindowText(*this, titleBuf);
}
}
break;
case IDC_INCLUDESUBFOLDERS:
{
if (msg == BN_CLICKED)
{
auto buf = GetDlgItemText(IDC_SEARCHPATH);
bool bIncludeSubfolders = (IsDlgButtonChecked(*this, IDC_INCLUDESUBFOLDERS) == BST_CHECKED);
bool bIsDir = !!PathIsDirectory(buf.get());
if ((!bIsDir) && wcschr(buf.get(), '|'))
bIsDir = true; // assume directories in case of multiple paths
DialogEnableWindow(IDC_EXCLUDEDIRSPATTERN, bIsDir || bIncludeSubfolders);
}
}
break;
case IDC_SEARCHTEXT:
{
if (msg == EN_CHANGE)
{
if (m_autoCompleteSearchPatterns.GetOptions() & ACO_NOPREFIXFILTERING)
m_autoCompleteSearchPatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST);
int len = CheckRegex();
DialogEnableWindow(IDC_ADDTOBOOKMARKS, len > 0);
DialogEnableWindow(IDC_INCLUDEBINARY, len > 0);
}
}
break;
case IDC_SIZEEDIT:
{
if (msg == EN_CHANGE)
{
wchar_t buf[20] = {0};
::GetDlgItemText(*this, IDC_SIZEEDIT, buf, _countof(buf));
if (wcslen(buf))
{
if (IsDlgButtonChecked(*this, IDC_ALLSIZERADIO) == BST_CHECKED)
{
CheckRadioButton(*this, IDC_ALLSIZERADIO, IDC_SIZERADIO, IDC_SIZERADIO);
}
}
else
{
if (IsDlgButtonChecked(*this, IDC_SIZERADIO) == BST_CHECKED)
{
CheckRadioButton(*this, IDC_ALLSIZERADIO, IDC_SIZERADIO, IDC_ALLSIZERADIO);
}
}
}
}
break;
case IDC_REGEXRADIO:
case IDC_TEXTRADIO:
{
CheckRegex();
DialogEnableWindow(IDC_TESTREGEX, !IsDlgButtonChecked(*this, IDC_TEXTRADIO));
DialogEnableWindow(IDC_WHOLEWORDS, IsDlgButtonChecked(*this, IDC_TEXTRADIO));
}
break;
case IDC_FILEPATTERNTEXT:
case IDC_FILEPATTERNREGEX:
{
m_bUseRegexForPaths = (IsDlgButtonChecked(*this, IDC_FILEPATTERNREGEX) == BST_CHECKED);
}
break;
case IDC_ADDTOBOOKMARKS:
{
auto buf = GetDlgItemText(IDC_SEARCHTEXT);
m_searchString = buf.get();
buf = GetDlgItemText(IDC_REPLACETEXT);
m_replaceString = buf.get();
buf = GetDlgItemText(IDC_EXCLUDEDIRSPATTERN);
m_excludeDirsPatternRegex = buf.get();
buf = GetDlgItemText(IDC_PATTERN);
m_patternRegex = buf.get();
m_bUseRegex = (IsDlgButtonChecked(*this, IDC_REGEXRADIO) == BST_CHECKED);
CNameDlg nameDlg(*this);
if (nameDlg.DoModal(hResource, IDD_NAME, *this) == IDOK)
{
// add the bookmark
CBookmarks bks;
Bookmark bk;
bk.Name = nameDlg.GetName();
bk.Path = nameDlg.IncludePath() ? m_searchPath : L"";
bk.Search = m_searchString;
bk.Replace = m_replaceString;
bk.UseRegex = m_bUseRegex;
bk.CaseSensitive = (IsDlgButtonChecked(*this, IDC_CASE_SENSITIVE) == BST_CHECKED);
bk.DotMatchesNewline = (IsDlgButtonChecked(*this, IDC_DOTMATCHNEWLINE) == BST_CHECKED);
bk.Backup = (IsDlgButtonChecked(*this, IDC_CREATEBACKUP) == BST_CHECKED);
bk.Utf8 = (IsDlgButtonChecked(*this, IDC_UTF8) == BST_CHECKED);
bk.IncludeSystem = (IsDlgButtonChecked(*this, IDC_INCLUDESYSTEM) == BST_CHECKED);
bk.IncludeFolder = (IsDlgButtonChecked(*this, IDC_INCLUDESUBFOLDERS) == BST_CHECKED);
bk.IncludeHidden = (IsDlgButtonChecked(*this, IDC_INCLUDEHIDDEN) == BST_CHECKED);
bk.IncludeBinary = (IsDlgButtonChecked(*this, IDC_INCLUDEBINARY) == BST_CHECKED);
bk.ExcludeDirs = m_excludeDirsPatternRegex;
bk.FileMatch = m_patternRegex;
bk.FileMatchRegex = (IsDlgButtonChecked(*this, IDC_FILEPATTERNREGEX) == BST_CHECKED);
bks.Load();
bks.AddBookmark(bk);
bks.Save();
}
}
break;
case IDC_BOOKMARKS:
{
if (!m_bookmarksDlg)
m_bookmarksDlg = std::make_unique<CBookmarksDlg>(*this);
else
m_bookmarksDlg->InitBookmarks();
m_bookmarksDlg->ShowModeless(hResource, IDD_BOOKMARKS, *this);
}
break;
case IDC_RESULTFILES:
case IDC_RESULTCONTENT:
{
InitResultList();
FillResultList();
}
break;
case IDC_SETTINGSBUTTON:
{
CSettingsDlg dlgSettings(*this);
dlgSettings.DoModal(hResource, IDD_SETTINGS, *this);
if (bPortable)
m_bCreateBackupInFolders = g_iniFile.GetBoolValue(L"settings", L"backupinfolder", false);
else {
m_regBackupInFolder.read();
m_bCreateBackupInFolders = !!static_cast<DWORD>(m_regBackupInFolder);
}
CTheme::Instance().SetFontForDialog(*this, CTheme::Instance().GetDlgFontFaceName(), CTheme::Instance().GetDlgFontSize());
CLanguage::Instance().TranslateWindow(*this); // re-apply, cause update problems?
}
break;
case IDC_EDITMULTILINE1:
case IDC_EDITMULTILINE2:
{
int uID = (id == IDC_EDITMULTILINE1 ? IDC_SEARCHTEXT : IDC_REPLACETEXT);
auto buf = GetDlgItemText(static_cast<int>(uID));
std::wstring ctrlText = buf.get();
// replace all \r\n strings with real CRLFs
try
{
int ft = boost::regex::normal;
boost::wregex expression = boost::wregex(L"\\\\r\\\\n", ft);
boost::match_results<std::wstring::const_iterator> whatC;
boost::match_flag_type rFlags = boost::match_default | boost::format_all;
ctrlText = regex_replace(ctrlText, expression, L"\\r\\n", rFlags);
}
catch (const std::exception&)
{
}
CMultiLineEditDlg editDlg(*this);
editDlg.SetString(ctrlText);
if (editDlg.DoModal(hResource, IDD_MULTILINEEDIT, *this) == IDOK)
{
std::wstring text = editDlg.GetSearchString();
// replace all CRLFs with \r\n strings (literal)
try
{
int ft = boost::regex::normal;
boost::wregex expression = boost::wregex(L"\\r\\n", ft);
boost::match_results<std::wstring::const_iterator> whatC;
boost::match_flag_type rFlags = boost::match_default | boost::format_all;
text = regex_replace(text, expression, L"\\\\r\\\\n", rFlags);
}
catch (const std::exception&)
{
}
SetDlgItemText(*this, static_cast<int>(uID), text.c_str());
}
::SetFocus(GetDlgItem(*this, uID));
}
break;
case IDC_PATHMRU:
{
m_autoCompleteSearchPaths.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST | ACO_NOPREFIXFILTERING);
::SetFocus(GetDlgItem(*this, IDC_SEARCHPATH));
SendDlgItemMessage(*this, IDC_SEARCHPATH, WM_KEYDOWN, VK_DOWN, 0);
}
break;
case IDC_EXCLUDEDIRMRU:
{
m_autoCompleteExcludeDirsPatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST | ACO_NOPREFIXFILTERING);
::SetFocus(GetDlgItem(*this, IDC_EXCLUDEDIRSPATTERN));
SendDlgItemMessage(*this, IDC_EXCLUDEDIRSPATTERN, WM_KEYDOWN, VK_DOWN, 0);
}
break;
case IDC_PATTERNMRU:
{
m_autoCompleteFilePatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST | ACO_NOPREFIXFILTERING);
::SetFocus(GetDlgItem(*this, IDC_PATTERN));
SendDlgItemMessage(*this, IDC_PATTERN, WM_KEYDOWN, VK_DOWN, 0);
}
break;
case IDC_PATTERN:
{
if (msg == EN_CHANGE)
{
if (m_autoCompleteFilePatterns.GetOptions() & ACO_NOPREFIXFILTERING)
m_autoCompleteFilePatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST);
}
}
break;
case IDC_EXCLUDEDIRSPATTERN:
{
if (msg == EN_CHANGE)
{
if (m_autoCompleteExcludeDirsPatterns.GetOptions() & ACO_NOPREFIXFILTERING)
m_autoCompleteExcludeDirsPatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST);
}
}
break;
case IDC_REPLACETEXT:
{
if (msg == EN_CHANGE)
{
if (m_autoCompleteReplacePatterns.GetOptions() & ACO_NOPREFIXFILTERING)
m_autoCompleteReplacePatterns.SetOptions(ACO_UPDOWNKEYDROPSLIST | ACO_AUTOSUGGEST);
}
}
break;
case IDC_EXPORT:
{
PreserveChdir keepCwd;
IFileSaveDialogPtr pfd;
HRESULT hr = pfd.CreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC_SERVER);
if (FailedShowMessage(hr))
break;
// Set the dialog options
DWORD dwOptions;
hr = pfd->GetOptions(&dwOptions);
if (FailedShowMessage(hr))
break;
hr = pfd->SetOptions(dwOptions | FOS_FORCEFILESYSTEM | FOS_OVERWRITEPROMPT);
if (FailedShowMessage(hr))
break;
hr = pfd->SetTitle(TranslatedString(hResource, IDS_EXPORTTITLE).c_str());
if (FailedShowMessage(hr))
break;
COMDLG_FILTERSPEC const aFileTypes[] = { {L"Text files", L"*.txt; *.lst"}, {L"All types", L"*.*"} };
hr = pfd->SetFileTypes(_countof(aFileTypes), aFileTypes);
if (FailedShowMessage(hr))
break;
hr = pfd->SetDefaultExtension(L"txt");
if (FailedShowMessage(hr))
break;
hr = pfd->SetFileName(L"gw_search_results.txt");
if (FailedShowMessage(hr))
break;
IFileDialogCustomizePtr pfdCustomize;
hr = pfd.QueryInterface(IID_PPV_ARGS(&pfdCustomize));
if (SUCCEEDED(hr))
{
bool exportpaths = bPortable ? g_iniFile.GetBoolValue(L"export", L"paths", false) :
static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWinNP3\\export_paths")) != 0;
bool const exportlinenumbers = bPortable ? g_iniFile.GetBoolValue(L"export", L"linenumbers", false) :
static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWinNP3\\export_linenumbers")) != 0;
bool const exportlinecontent = bPortable ? g_iniFile.GetBoolValue(L"export", L"linecontent", false) :
static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWinNP3\\export_linecontent")) != 0;
if (!exportpaths && !exportlinenumbers && !exportlinecontent)
exportpaths = true;
pfdCustomize->AddCheckButton(101, TranslatedString(hResource, IDS_EXPORTPATHS).c_str(), exportpaths);
pfdCustomize->AddCheckButton(102, TranslatedString(hResource, IDS_EXPORTMATCHLINENUMBER).c_str(), exportlinenumbers);
pfdCustomize->AddCheckButton(103, TranslatedString(hResource, IDS_EXPORTMATCHLINECONTENT).c_str(), exportlinecontent);
}
// Show the save file dialog
hr = pfd->Show(*this);
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
break;
if (FailedShowMessage(hr))
break;
IShellItemPtr psiResult = nullptr;
hr = pfd->GetResult(&psiResult);
if (FailedShowMessage(hr))
break;
PWSTR pszPath = nullptr;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
if (FailedShowMessage(hr))
break;
std::wstring path = pszPath;
CoTaskMemFree(pszPath);
bool includePaths = true;
bool includeMatchLineNumbers = false;
bool includeMatchLineTexts = false;
IFileDialogCustomizePtr pfdCustomizeRet;
hr = pfd.QueryInterface(IID_PPV_ARGS(&pfdCustomizeRet));
if (SUCCEEDED(hr))
{
BOOL bChecked = FALSE;
pfdCustomizeRet->GetCheckButtonState(101, &bChecked);
includePaths = (bChecked != 0);
pfdCustomizeRet->GetCheckButtonState(102, &bChecked);
includeMatchLineNumbers = (bChecked != 0);
pfdCustomizeRet->GetCheckButtonState(103, &bChecked);
includeMatchLineTexts = (bChecked != 0);
}
if (!includePaths && !includeMatchLineNumbers && !includeMatchLineTexts)
includePaths = true;
bool onlyPaths = !includeMatchLineNumbers && !includeMatchLineTexts;
if (!path.empty())
{
std::ofstream file;
file.open(path);
if (file.is_open())
{
if (onlyPaths)
{
for (const auto& item : m_items)
{
file << CUnicodeUtils::StdGetUTF8(item.filePath) << std::endl;
}
}
else
{
constexpr char separator = '*';
for (const auto& item : m_items)
{
for (size_t i = 0; i < item.matchLinesNumbers.size(); ++i)
{
bool needSeparator = false;
if (includePaths)
{
std::string fpath = CUnicodeUtils::StdGetUTF8(item.filePath);
std::transform(fpath.begin(), fpath.end(), fpath.begin(), [](char c) { return (c == '\\' ? '/' : c); });
file << std::string("file://");
file << fpath;
needSeparator = true;
}
if (includeMatchLineNumbers)
{
if (needSeparator)
file << separator;
file << CStringUtils::Format("%lu", item.matchLinesNumbers[i]);
needSeparator = true;
}
if (includeMatchLineTexts)
{
if (needSeparator)
file << separator;
auto line = item.matchLines[i];
CStringUtils::rtrim(line, L"\r\n");
file << CUnicodeUtils::StdGetUTF8(line);
}
file << std::endl;
}
}
}
file.close();
if (bPortable)
{
g_iniFile.SetBoolValue(L"export", L"paths", includePaths);
g_iniFile.SetBoolValue(L"export", L"linenumbers", includeMatchLineNumbers);
g_iniFile.SetBoolValue(L"export", L"linecontent", includeMatchLineTexts);
}
else
{
// ReSharper disable CppEntityAssignedButNoRead
auto exportpaths = CRegStdDWORD(L"Software\\grepWinNP3\\export_paths");
auto exportlinenumbers = CRegStdDWORD(L"Software\\grepWinNP3\\export_linenumbers");
auto exportlinecontent = CRegStdDWORD(L"Software\\grepWinNP3\\export_linecontent");
// ReSharper restore CppEntityAssignedButNoRead
exportpaths = includePaths ? 1 : 0;
exportlinenumbers = includeMatchLineNumbers ? 1 : 0;
exportlinecontent = includeMatchLineTexts ? 1 : 0;
}
// open file
std::wstring cmd = bPortable ? g_iniFile.GetValue(L"global", L"editorcmd", L"") :
(std::wstring)CRegStdString(L"Software\\grepWinNP3\\editorcmd", L"");
if (!cmd.empty())
{
SearchReplace(cmd, L"%mode%", L"mb");
SearchReplace(cmd, L"%pattern%", L"");
SearchReplace(cmd, L"%line%", L"0");
SearchReplace(cmd, L"%path%", path.c_str());
STARTUPINFO startupInfo;
PROCESS_INFORMATION processInfo;
SecureZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(STARTUPINFO);
SecureZeroMemory(&processInfo, sizeof(processInfo));
CreateProcess(NULL, const_cast<wchar_t*>(cmd.c_str()), NULL, NULL, FALSE, 0, 0, NULL, &startupInfo, &processInfo);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
}
else
{
SHELLEXECUTEINFO sei = {0};
sei.cbSize = sizeof(SHELLEXECUTEINFO);
sei.lpVerb = TEXT("open");
sei.lpFile = path.c_str();
sei.nShow = SW_SHOWNORMAL;
ShellExecuteEx(&sei);
}
}
}
}
break;
case IDC_UTF8:
{
if (IsDlgButtonChecked(*this, IDC_UTF8))
CheckDlgButton(*this, IDC_BINARY, BST_UNCHECKED);
}
break;
case IDC_BINARY:
{
if (IsDlgButtonChecked(*this, IDC_BINARY))
CheckDlgButton(*this, IDC_UTF8, BST_UNCHECKED);
}
break;
default:
break;
}
return 1;
}
void CSearchDlg::SaveWndPosition()
{
WINDOWPLACEMENT wpl = {0};
wpl.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(*this, &wpl);
std::wstring winPosKey = L"windowpos_" + GetMonitorSetupHash();
if (bPortable)
{
auto sPos = CStringUtils::Format(L"%d;%d;%d;%d;%d;%d;%d;%d;%d;%d",
wpl.flags, wpl.showCmd,
wpl.ptMinPosition.x, wpl.ptMinPosition.y,
wpl.ptMaxPosition.x, wpl.ptMaxPosition.y,
wpl.rcNormalPosition.left, wpl.rcNormalPosition.top, wpl.rcNormalPosition.right, wpl.rcNormalPosition.bottom);
g_iniFile.SetValue(L"global", winPosKey.c_str(), sPos.c_str());
}
else
{
SHSetValue(HKEY_CURRENT_USER, L"Software\\grepWinNP3", winPosKey.c_str(), REG_NONE, &wpl, sizeof(wpl));
}
}
void CSearchDlg::UpdateInfoLabel()
{
std::wstring sText;
wchar_t buf[1024] = {0};
if (m_selectedItems)
swprintf_s(buf, _countof(buf), TranslatedString(hResource, IDS_INFOLABELSEL).c_str(),
m_searchedItems, m_totalItems - m_searchedItems, m_totalMatches, m_items.size(), m_selectedItems);
else
swprintf_s(buf, _countof(buf), TranslatedString(hResource, IDS_INFOLABEL).c_str(),
m_searchedItems, m_totalItems - m_searchedItems, m_totalMatches, m_items.size());
sText = buf;
SetDlgItemText(*this, IDC_SEARCHINFOLABEL, sText.c_str());
}
bool CSearchDlg::InitResultList()
{
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
bool filelist = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
DWORD exStyle = LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT;
SetWindowTheme(hListControl, L"Explorer", nullptr);
ListView_SetItemCount(hListControl, 0);
int c = Header_GetItemCount(ListView_GetHeader(hListControl)) - 1;
while (c >= 0)
ListView_DeleteColumn(hListControl, c--);
ListView_SetExtendedListViewStyle(hListControl, exStyle);
ListView_SetImageList(hListControl, reinterpret_cast<WPARAM>(static_cast<HIMAGELIST>(CSysImageList::GetInstance())), LVSIL_SMALL);
std::wstring sName = TranslatedString(hResource, IDS_NAME);
std::wstring sSize = TranslatedString(hResource, IDS_SIZE);
std::wstring sLine = TranslatedString(hResource, IDS_LINE);
std::wstring sMatches = TranslatedString(hResource, IDS_MATCHES);
std::wstring sText = TranslatedString(hResource, IDS_TEXT);
std::wstring sPath = TranslatedString(hResource, IDS_PATH);
std::wstring sEncoding = TranslatedString(hResource, IDS_ENCODING);
std::wstring sDateModified = TranslatedString(hResource, IDS_DATEMODIFIED);
std::wstring sExtension = TranslatedString(hResource, IDS_FILEEXT);
LVCOLUMN lvc = {0};
lvc.mask = LVCF_TEXT | LVCF_FMT;
lvc.fmt = LVCFMT_LEFT;
lvc.cx = -1;
lvc.pszText = const_cast<LPWSTR>(static_cast<LPCWSTR>(sName.c_str()));
ListView_InsertColumn(hListControl, 0, &lvc);
lvc.pszText = filelist ? const_cast<LPWSTR>(static_cast<LPCWSTR>(sSize.c_str())) : const_cast<LPWSTR>(static_cast<LPCWSTR>(sLine.c_str()));
lvc.fmt = filelist ? LVCFMT_RIGHT : LVCFMT_LEFT;
ListView_InsertColumn(hListControl, 1, &lvc);
lvc.fmt = LVCFMT_LEFT;
lvc.pszText = filelist ? const_cast<LPWSTR>(static_cast<LPCWSTR>(sMatches.c_str())) : const_cast<LPWSTR>(static_cast<LPCWSTR>(sText.c_str()));
ListView_InsertColumn(hListControl, 2, &lvc);
lvc.pszText = const_cast<LPWSTR>(static_cast<LPCWSTR>(sPath.c_str()));
ListView_InsertColumn(hListControl, 3, &lvc);
if (filelist)
{
lvc.pszText = const_cast<LPWSTR>(static_cast<LPCWSTR>(sExtension.c_str()));
ListView_InsertColumn(hListControl, 4, &lvc);
lvc.pszText = const_cast<LPWSTR>(static_cast<LPCWSTR>(sEncoding.c_str()));
ListView_InsertColumn(hListControl, 5, &lvc);
lvc.pszText = const_cast<LPWSTR>(static_cast<LPCWSTR>(sDateModified.c_str()));
ListView_InsertColumn(hListControl, 6, &lvc);
}
ListView_SetColumnWidth(hListControl, 0, 300);
ListView_SetColumnWidth(hListControl, 1, 50);
ListView_SetColumnWidth(hListControl, 2, LVSCW_AUTOSIZE_USEHEADER);
ListView_SetColumnWidth(hListControl, 3, LVSCW_AUTOSIZE_USEHEADER);
ListView_SetColumnWidth(hListControl, 4, LVSCW_AUTOSIZE_USEHEADER);
ListView_SetColumnWidth(hListControl, 5, LVSCW_AUTOSIZE_USEHEADER);
ListView_SetColumnWidth(hListControl, 6, LVSCW_AUTOSIZE_USEHEADER);
SendMessage(ListView_GetToolTips(hListControl), TTM_SETDELAYTIME, TTDT_AUTOPOP, SHRT_MAX);
m_selectedItems = 0;
return true;
}
bool CSearchDlg::AddFoundEntry(const CSearchInfo* pInfo, bool bOnlyListControl)
{
if (!bOnlyListControl)
{
m_items.push_back(*pInfo);
int index = static_cast<int>(m_items.size() - 1);
int subIndex = 0;
for (const auto& lineNumber : pInfo->matchLinesNumbers)
{
UNREFERENCED_PARAMETER(lineNumber);
m_listItems.emplace_back(index, subIndex);
++subIndex;
}
}
else
{
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
bool fileList = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
auto count = static_cast<size_t>(ListView_GetItemCount(hListControl));
if (count != (fileList ? m_items.size() : m_listItems.size()))
ListView_SetItemCountEx(hListControl, fileList ? m_items.size() : m_listItems.size(), LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
}
return true;
}
void CSearchDlg::FillResultList()
{
ProfileTimer profile(L"FillResultList");
SetCursor(LoadCursor(nullptr, IDC_APPSTARTING));
// refresh cursor
POINT pt;
GetCursorPos(&pt);
SetCursorPos(pt.x, pt.y);
bool filelist = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
SendMessage(hListControl, WM_SETREDRAW, FALSE, 0);
ListView_SetItemCountEx(hListControl, filelist ? m_items.size() : m_listItems.size(), LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
AutoSizeAllColumns();
SendMessage(hListControl, WM_SETREDRAW, TRUE, 0);
SetCursor(LoadCursor(nullptr, IDC_ARROW));
// refresh cursor
GetCursorPos(&pt);
SetCursorPos(pt.x, pt.y);
RedrawWindow(hListControl, nullptr, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
}
void CSearchDlg::ShowContextMenu(int x, int y)
{
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
int nCount = ListView_GetItemCount(hListControl);
if (nCount == 0)
return;
CShellContextMenu shellMenu;
int iItem = -1;
std::vector<CSearchInfo> paths;
while ((iItem = ListView_GetNextItem(hListControl, iItem, LVNI_SELECTED)) != (-1))
{
int selIndex = GetSelectedListIndex(iItem);
if ((selIndex < 0) || (selIndex >= static_cast<int>(m_items.size())))
continue;
paths.push_back(m_items[selIndex]);
}
if (paths.empty())
return;
std::vector<LineData> lines;
bool fileList = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
if (!fileList)
{
WCHAR numBuf[40] = {0};
while ((iItem = ListView_GetNextItem(hListControl, iItem, LVNI_SELECTED)) != (-1))
{
ListView_GetItemText(hListControl, iItem, 1, numBuf, 40);
DWORD line = _wtoi(numBuf);
if (line)
{
LineData data;
const CSearchInfo info = m_items[GetSelectedListIndex(iItem)];
data.path = info.filePath;
const auto matchLinesNumbers = info.matchLinesNumbers;
size_t lineIndex = 0;
for (unsigned long matchlinesnumber : matchLinesNumbers)
{
if (matchlinesnumber == line)
{
LineDataLine dataLine;
dataLine.number = info.matchLinesNumbers[lineIndex];
if (info.matchLines.size() > lineIndex)
dataLine.text = info.matchLines[lineIndex];
data.lines.push_back(dataLine);
}
++lineIndex;
}
lines.push_back(data);
}
}
}
shellMenu.SetObjects(paths, lines);
POINT pt = {x, y};
if ((x == -1) && (y == -1))
{
RECT rc;
ListView_GetItemRect(hListControl, ListView_GetSelectionMark(hListControl), &rc, LVIR_LABEL);
pt.x = (rc.right - rc.left) / 2;
pt.y = (rc.bottom - rc.top) / 2;
ClientToScreen(hListControl, &pt);
}
shellMenu.ShowContextMenu(hListControl, pt);
}
bool CSearchDlg::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN)
{
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
auto bCtrl = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
auto bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
auto bAlt = (GetKeyState(VK_MENU) & 0x8000) != 0;
switch (pMsg->wParam)
{
case VK_RETURN:
{
if (GetFocus() == hListControl)
{
int iItem = -1;
while ((iItem = ListView_GetNextItem(hListControl, iItem, LVNI_SELECTED)) != (-1))
{
NMITEMACTIVATE itemActivate = {nullptr};
itemActivate.hdr.code = NM_DBLCLK;
itemActivate.iItem = iItem;
DoListNotify(&itemActivate);
}
return true;
}
}
break;
case 'A':
{
if ((GetFocus() == hListControl) && bCtrl && !bShift && !bAlt)
{
// select all entries
int nCount = ListView_GetItemCount(hListControl);
for (int i = 0; i < nCount; ++i)
{
ListView_SetItemState(hListControl, i, LVIS_SELECTED, LVIS_SELECTED);
}
return true;
}
}
break;
case 'C':
{
if ((GetFocus() == hListControl) && bCtrl && !bShift && !bAlt)
{
// copy all selected entries to the clipboard
std::wstring clipBoardText;
HWND hHeader = ListView_GetHeader(hListControl);
int columns = Header_GetItemCount(hHeader);
WCHAR buf[MAX_PATH] = {0};
for (int i = 0; i < columns; ++i)
{
HD_ITEM hdi = {0};
hdi.mask = HDI_TEXT;
hdi.pszText = buf;
hdi.cchTextMax = _countof(buf);
Header_GetItem(hHeader, i, &hdi);
if (i > 0)
clipBoardText += L"\t";
clipBoardText += hdi.pszText;
}
clipBoardText += L"\r\n";
int iItem = -1;
while ((iItem = ListView_GetNextItem(hListControl, iItem, LVNI_SELECTED)) != (-1))
{
for (int i = 0; i < columns; ++i)
{
ListView_GetItemText(hListControl, iItem, i, buf, _countof(buf));
if (i > 0)
clipBoardText += L"\t";
clipBoardText += buf;
}
clipBoardText += L"\r\n";
}
WriteAsciiStringToClipboard(clipBoardText.c_str(), *this);
}
}
break;
case VK_DELETE:
{
m_autoCompleteFilePatterns.RemoveSelected();
m_autoCompleteSearchPatterns.RemoveSelected();
m_autoCompleteReplacePatterns.RemoveSelected();
m_autoCompleteSearchPaths.RemoveSelected();
}
break;
case 'K':
case 'S':
case 'F':
case 'E':
{
if (bCtrl && !bShift && !bAlt)
{
SetFocus(GetDlgItem(*this, IDC_SEARCHTEXT));
}
}
break;
case 'L':
{
if (bCtrl && !bShift && !bAlt)
{
SetFocus(GetDlgItem(*this, IDC_PATTERN));
}
}
break;
case 'O':
{
if (bCtrl && !bShift && !bAlt)
{
int iItem = -1;
while ((iItem = ListView_GetNextItem(hListControl, iItem, LVNI_SELECTED)) != (-1))
{
int selIndex = GetSelectedListIndex(iItem);
if ((selIndex < 0) || (selIndex >= static_cast<int>(m_items.size())))
continue;
OpenFileAtListIndex(selIndex);
}
}
}
break;
default:
break;
}
}
return false;
}
void CSearchDlg::DoListNotify(LPNMITEMACTIVATE lpNMItemActivate)
{
if (lpNMItemActivate->hdr.code == NM_DBLCLK)
{
if (lpNMItemActivate->iItem >= 0)
{
OpenFileAtListIndex(lpNMItemActivate->iItem);
}
}
if (lpNMItemActivate->hdr.code == LVN_ITEMCHANGED)
{
if ((lpNMItemActivate->uOldState & LVIS_SELECTED) || (lpNMItemActivate->uNewState & LVIS_SELECTED))
{
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
m_selectedItems = ListView_GetSelectedCount(hListControl);
UpdateInfoLabel();
}
}
if (lpNMItemActivate->hdr.code == LVN_BEGINDRAG)
{
CDropFiles dropFiles; // class for creating DROPFILES struct
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
int nCount = ListView_GetItemCount(hListControl);
if (nCount == 0)
return;
int iItem = -1;
while ((iItem = ListView_GetNextItem(hListControl, iItem, LVNI_SELECTED)) != (-1))
{
dropFiles.AddFile(m_items[GetSelectedListIndex(iItem)].filePath);
}
if (dropFiles.GetCount() > 0)
{
dropFiles.CreateStructure(hListControl);
}
}
if (lpNMItemActivate->hdr.code == LVN_COLUMNCLICK)
{
bool fileList = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
m_bAscending = !m_bAscending;
bool bDidSort = false;
switch (lpNMItemActivate->iSubItem)
{
case 0:
if (m_bAscending)
std::ranges::sort(m_items, NameCompareAsc);
else
std::ranges::sort(m_items, NameCompareDesc);
bDidSort = true;
break;
case 1:
if (fileList)
{
if (m_bAscending)
std::ranges::sort(m_items, SizeCompareAsc);
else
std::ranges::sort(m_items, SizeCompareDesc);
bDidSort = true;
}
break;
case 2:
if (fileList)
{
if (m_bAscending)
std::ranges::sort(m_items, MatchesCompareAsc);
else
std::ranges::sort(m_items, MatchesCompareDesc);
bDidSort = true;
}
break;
case 3:
if (m_bAscending)
std::ranges::sort(m_items, PathCompareAsc);
else
std::ranges::sort(m_items, PathCompareDesc);
bDidSort = true;
break;
case 4:
if (m_bAscending)
std::ranges::sort(m_items, ExtCompareAsc);
else
std::ranges::sort(m_items, ExtCompareDesc);
bDidSort = true;
break;
case 5:
if (m_bAscending)
std::ranges::sort(m_items, EncodingCompareAsc);
else
std::ranges::sort(m_items, EncodingCompareDesc);
bDidSort = true;
break;
case 6:
if (m_bAscending)
std::ranges::sort(m_items, ModifiedTimeCompareAsc);
else
std::ranges::sort(m_items, ModifiedTimeCompareDesc);
bDidSort = true;
break;
default:
break;
}
if (bDidSort)
{
auto size = m_listItems.size();
m_listItems.clear();
m_listItems.reserve(size);
int index = 0;
for (const auto& item : m_items)
{
int subIndex = 0;
for (const auto& lineNumber : item.matchLinesNumbers)
{
UNREFERENCED_PARAMETER(lineNumber);
m_listItems.emplace_back(index, subIndex);
++subIndex;
}
++index;
}
}
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
SendMessage(hListControl, WM_SETREDRAW, FALSE, 0);
ListView_SetItemCountEx(hListControl, fileList ? m_items.size() : m_listItems.size(), LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
AutoSizeAllColumns();
HDITEM hd = {0};
hd.mask = HDI_FORMAT;
HWND hHeader = ListView_GetHeader(hListControl);
int iCount = Header_GetItemCount(hHeader);
for (int i = 0; i < iCount; ++i)
{
Header_GetItem(hHeader, i, &hd);
hd.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
Header_SetItem(hHeader, i, &hd);
}
if (bDidSort)
{
Header_GetItem(hHeader, lpNMItemActivate->iSubItem, &hd);
hd.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
Header_SetItem(hHeader, lpNMItemActivate->iSubItem, &hd);
}
SendMessage(hListControl, WM_SETREDRAW, TRUE, 0);
RedrawWindow(hListControl, nullptr, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
}
if (lpNMItemActivate->hdr.code == LVN_GETINFOTIP)
{
NMLVGETINFOTIP* pInfoTip = reinterpret_cast<NMLVGETINFOTIP*>(lpNMItemActivate);
// Which item number?
size_t itemId = pInfoTip->iItem;
int iItem = GetSelectedListIndex(static_cast<int>(itemId));
pInfoTip->pszText[0] = 0;
if (static_cast<int>(m_items.size()) > iItem)
{
CSearchInfo inf = m_items[iItem];
std::wstring matchString = inf.filePath + L"\n";
std::wstring sFormat = TranslatedString(hResource, IDS_CONTEXTLINE);
for (size_t i = 0; i < min(inf.matchLines.size(), 5); ++i)
{
std::wstring matchText = inf.matchLines[i];
CStringUtils::trim(matchText);
matchString += CStringUtils::Format(sFormat.c_str(), inf.matchLinesNumbers[i], matchText.c_str());
}
if (inf.matchLines.size() > 5)
{
std::wstring sx = TranslatedString(hResource, IDS_XMOREMATCHES);
std::wstring ssx = CStringUtils::Format(sx.c_str(), static_cast<int>(inf.matchLines.size() - 5));
matchString += ssx;
}
StringCchCopy(pInfoTip->pszText, pInfoTip->cchTextMax, matchString.c_str());
}
}
if (lpNMItemActivate->hdr.code == LVN_GETDISPINFO)
{
static const std::wstring sBinary = TranslatedString(hResource, IDS_BINARY);
NMLVDISPINFO* pDispInfo = reinterpret_cast<NMLVDISPINFO*>(lpNMItemActivate);
LV_ITEM* pItem = &(pDispInfo)->item;
int iItem = pItem->iItem;
bool fileList = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
if (fileList)
{
const auto& pInfo = &m_items[iItem];
if (pItem->mask & LVIF_TEXT)
{
switch (pItem->iSubItem)
{
case 0: // name of the file
wcsncpy_s(pItem->pszText, pItem->cchTextMax, pInfo->filePath.substr(pInfo->filePath.find_last_of('\\') + 1).c_str(), pItem->cchTextMax - 1LL);
break;
case 1: // file size
if (!pInfo->folder)
StrFormatByteSizeW(pInfo->fileSize, pItem->pszText, pItem->cchTextMax);
break;
case 2: // match count or read error
if (pInfo->readError)
wcsncpy_s(pItem->pszText, pItem->cchTextMax, TranslatedString(hResource, IDS_READERROR).c_str(), pItem->cchTextMax - 1LL);
else
swprintf_s(pItem->pszText, pItem->cchTextMax, L"%lld", pInfo->matchCount);
break;
case 3: // path
if (m_searchPath.find('|') != std::wstring::npos)
wcsncpy_s(pItem->pszText, pItem->cchTextMax, pInfo->filePath.substr(0, pInfo->filePath.size() - pInfo->filePath.substr(pInfo->filePath.find_last_of('\\')).size()).c_str(), pItem->cchTextMax - 1LL);
else
{
auto filePart = pInfo->filePath.substr(pInfo->filePath.find_last_of('\\'));
auto len = pInfo->filePath.size() - m_searchPath.size() - filePart.size();
if (len > 0)
--len;
if (m_searchPath.size() < pInfo->filePath.size())
{
wcsncpy_s(pItem->pszText, pItem->cchTextMax, pInfo->filePath.substr(m_searchPath.size() + 1, len).c_str(), pItem->cchTextMax - 1LL);
if (pItem->pszText[0] == 0)
wcscpy_s(pItem->pszText, pItem->cchTextMax, L"\\.");
}
else
wcsncpy_s(pItem->pszText, pItem->cchTextMax, pInfo->filePath.c_str(), pItem->cchTextMax - 1LL);
}
break;
case 4: // extension of the file
{
pItem->pszText[0] = 0;
if (!pInfo->folder)
{
auto dotPos = pInfo->filePath.find_last_of('.');
if (dotPos != std::wstring::npos)
{
if (pInfo->filePath.find('\\', dotPos) == std::wstring::npos)
wcsncpy_s(pItem->pszText, pItem->cchTextMax, pInfo->filePath.substr(dotPos + 1).c_str(), pItem->cchTextMax - 1LL);
}
}
}
break;
case 5: // encoding
switch (pInfo->encoding)
{
case CTextFile::Ansi:
wcsncpy_s(pItem->pszText, pItem->cchTextMax, L"ANSI", pItem->cchTextMax - 1LL);
break;
case CTextFile::Unicode_Le:
wcsncpy_s(pItem->pszText, pItem->cchTextMax, L"UTF-16-LE", pItem->cchTextMax - 1LL);
break;
case CTextFile::Unicode_Be:
wcsncpy_s(pItem->pszText, pItem->cchTextMax, L"UTF-16-BE", pItem->cchTextMax - 1LL);
break;
case CTextFile::UTF8:
wcsncpy_s(pItem->pszText, pItem->cchTextMax, L"UTF8", pItem->cchTextMax - 1LL);
break;
case CTextFile::Binary:
wcsncpy_s(pItem->pszText, pItem->cchTextMax, L"BINARY", pItem->cchTextMax - 1LL);
break;
default:
wcsncpy_s(pItem->pszText, pItem->cchTextMax, L"", pItem->cchTextMax - 1LL);
break;
}
break;
case 6: // modification date
FormatDate(pItem->pszText, pInfo->modifiedTime, true);
break;
default:
pItem->pszText[0] = 0;
break;
}
}
if (pItem->mask & LVIF_IMAGE)
{
pItem->iImage = pInfo->folder ? CSysImageList::GetInstance().GetDirIconIndex() : CSysImageList::GetInstance().GetFileIconIndex(pInfo->filePath);
}
}
else
{
auto tup = m_listItems[iItem];
auto index = std::get<0>(tup);
auto subIndex = std::get<1>(tup);
const auto& item = m_items[index];
const auto& pInfo = &item;
if (item.encoding == CTextFile::Binary)
{
if (pItem->mask & LVIF_TEXT)
{
switch (pItem->iSubItem)
{
case 0: // name of the file
wcsncpy_s(pItem->pszText, pItem->cchTextMax, pInfo->filePath.substr(pInfo->filePath.find_last_of('\\') + 1).c_str(), pItem->cchTextMax - 1LL);
break;
case 1: // binary
wcsncpy_s(pItem->pszText, pItem->cchTextMax, sBinary.c_str(), pItem->cchTextMax);
break;
case 3: // path
wcsncpy_s(pItem->pszText, pItem->cchTextMax, pInfo->filePath.substr(0, pInfo->filePath.size() - pInfo->filePath.substr(pInfo->filePath.find_last_of('\\') + 1).size() - 1).c_str(), pItem->cchTextMax - 1LL);
break;
default:
pItem->pszText[0] = 0;
break;
}
}
if (pItem->mask & LVIF_IMAGE)
{
pItem->iImage = pInfo->folder ? CSysImageList::GetInstance().GetDirIconIndex() : CSysImageList::GetInstance().GetFileIconIndex(pInfo->filePath);
}
}
else
{
if (pItem->mask & LVIF_TEXT)
{
switch (pItem->iSubItem)
{
case 0: // name of the file
wcsncpy_s(pItem->pszText, pItem->cchTextMax, pInfo->filePath.substr(pInfo->filePath.find_last_of('\\') + 1).c_str(), pItem->cchTextMax - 1LL);
break;
case 1: // line number
swprintf_s(pItem->pszText, pItem->cchTextMax, L"%ld", pInfo->matchLinesNumbers[subIndex]);
break;
case 2: // line
{
std::wstring line;
if (pInfo->matchLines.size() > static_cast<size_t>(subIndex))
line = pInfo->matchLines[subIndex];
std::ranges::replace(line, '\t', ' ');
std::ranges::replace(line, '\n', ' ');
std::ranges::replace(line, '\r', ' ');
wcsncpy_s(pItem->pszText, pItem->cchTextMax, line.c_str(), pItem->cchTextMax - 1LL);
}
break;
case 3: // path
wcsncpy_s(pItem->pszText, pItem->cchTextMax, pInfo->filePath.substr(0, pInfo->filePath.size() - pInfo->filePath.substr(pInfo->filePath.find_last_of('\\') + 1).size() - 1).c_str(), pItem->cchTextMax - 1LL);
break;
default:
pItem->pszText[0] = 0;
break;
}
}
if (pItem->mask & LVIF_IMAGE)
{
pItem->iImage = pInfo->folder ? CSysImageList::GetInstance().GetDirIconIndex() : CSysImageList::GetInstance().GetFileIconIndex(pInfo->filePath);
}
}
}
}
}
static void EscCtrlCharacters(std::wstring& pattern)
{
std::wstring result;
for (const wchar_t& ch : pattern)
{
switch (ch)
{
case L'\n':
result.append(L"\\n");
break;
case L'\r':
result.append(L"\\r");
break;
case L'\t':
result.append(L"\\t");
break;
case L'\f':
result.append(L"\\f");
break;
case L'\v':
result.append(L"\\v");
break;
case L'\a':
result.append(L"\\a");
break;
case L'\b':
result.append(L"\\b");
break;
case L'\x1B':
result.append(L"\\e");
break;
// protect quotes too
case L'"':
result.append(L"\\\"");
break;
default:
result.push_back(ch);
break;
}
if (ch == L'\0')
break; // loop
}
pattern = std::move(result);
}
void CSearchDlg::OpenFileAtListIndex(int listIndex)
{
int iItem = GetSelectedListIndex(listIndex);
CSearchInfo inf = m_items[iItem];
size_t dotPos = inf.filePath.rfind('.');
std::wstring ext;
if (dotPos != std::wstring::npos)
ext = inf.filePath.substr(dotPos);
std::wstring cmd = bPortable ? g_iniFile.GetValue(L"global", L"editorcmd", L"") :
(std::wstring)CRegStdString(L"Software\\grepWinNP3\\editorcmd", L"");
if (!cmd.empty())
{
bool fileList = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
if (!fileList)
{
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
wchar_t textLineBuf[MAX_PATH] = {0};
LVITEM lv = {0};
lv.iItem = listIndex;
lv.iSubItem = 1; // line number
lv.mask = LVIF_TEXT;
lv.pszText = textLineBuf;
lv.cchTextMax = _countof(textLineBuf);
if (ListView_GetItem(hListControl, &lv))
{
SearchReplace(cmd, L"%line%", textLineBuf);
}
}
else
{
// use the first matching line in this file
if (!inf.matchLinesNumbers.empty())
SearchReplace(cmd, L"%line%", CStringUtils::Format(L"%lu", inf.matchLinesNumbers[0]));
else
SearchReplace(cmd, L"%line%", L"0");
}
SearchReplace(cmd, L"%path%", inf.filePath.c_str());
// Notepad3 special
std::wstring mode = L"mb";
if (m_bUseRegex)
mode.append(L"r");
if (m_bCaseSensitive)
mode.append(L"c");
if (m_bDotMatchesNewline)
mode.append(L"a");
SearchReplace(cmd, L"%mode%", mode.c_str());
std::wstring searchfor = m_searchString;
EscCtrlCharacters(searchfor);
SearchReplace(cmd, L"%pattern%", searchfor.c_str());
STARTUPINFO startupInfo{};
PROCESS_INFORMATION processInfo{};
startupInfo.cb = sizeof(STARTUPINFO);
CreateProcess(nullptr, const_cast<wchar_t*>(cmd.c_str()), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
return;
}
DWORD bufLen = 0;
if (AssocQueryString(ASSOCF_INIT_DEFAULTTOSTAR, ASSOCSTR_DDECOMMAND, ext.c_str(), nullptr, nullptr, &bufLen) == S_OK)
{
if (bufLen)
{
// application requires DDE to open the file:
// since we can't do this the easy way with CreateProcess, we use ShellExecute instead
ShellExecute(*this, nullptr, inf.filePath.c_str(), nullptr, nullptr, SW_SHOW);
return;
}
}
bufLen = 0;
AssocQueryString(ASSOCF_INIT_DEFAULTTOSTAR, ASSOCSTR_COMMAND, ext.c_str(), nullptr, nullptr, &bufLen);
auto cmdBuf = std::make_unique<wchar_t[]>(bufLen + 1LL);
AssocQueryString(ASSOCF_INIT_DEFAULTTOSTAR, ASSOCSTR_COMMAND, ext.c_str(), nullptr, cmdBuf.get(), &bufLen);
std::wstring application = cmdBuf.get();
// normalize application path
DWORD len = ExpandEnvironmentStrings(application.c_str(), nullptr, 0);
cmdBuf = std::make_unique<wchar_t[]>(len + 1LL);
ExpandEnvironmentStrings(application.c_str(), cmdBuf.get(), len);
application = cmdBuf.get();
// resolve parameters
if (application.find(L"%1") == std::wstring::npos)
application += L" %1";
bool fileList = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
std::wstring lineNumberParamBefore;
std::wstring lineNumberParam;
wchar_t textLineBuf[MAX_PATH] = {0};
if (!fileList)
{
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
LVITEM lv = {0};
lv.iItem = listIndex;
lv.iSubItem = 1; // line number
lv.mask = LVIF_TEXT;
lv.pszText = textLineBuf;
lv.cchTextMax = _countof(textLineBuf);
if (!ListView_GetItem(hListControl, &lv))
{
textLineBuf[0] = '\0';
}
}
else if (!inf.matchLinesNumbers.empty())
{
// use the first matching line in this file
swprintf_s(textLineBuf, L"%ld", inf.matchLinesNumbers[0]);
}
if (textLineBuf[0] == 0)
wcscpy_s(textLineBuf, L"0");
std::wstring appname = application;
std::ranges::transform(appname, appname.begin(), ::towlower);
// now find out if the application which opens the file is known to us
// and if it has a 'linenumber' switch to jump directly to a specific
// line number.
if (appname.find(L"notepad++.exe") != std::wstring::npos)
{
// notepad++
lineNumberParam = CStringUtils::Format(L"-n%s", textLineBuf);
}
else if (appname.find(L"xemacs.exe") != std::wstring::npos)
{
// XEmacs
lineNumberParam = CStringUtils::Format(L"+%s", textLineBuf);
}
else if (appname.find(L"uedit32.exe") != std::wstring::npos)
{
// UltraEdit
lineNumberParam = CStringUtils::Format(L"-l%s", textLineBuf);
}
else if (appname.find(L"codewright.exe") != std::wstring::npos)
{
// CodeWright
lineNumberParam = CStringUtils::Format(L"-G%s", textLineBuf);
}
else if (appname.find(L"notepad2.exe") != std::wstring::npos)
{
// Notepad2
auto escapedSearch = m_searchString;
SearchReplace(escapedSearch, L"\"", L"\\\"");
lineNumberParamBefore = CStringUtils::Format(L"/g %s /mr \"%s\"", textLineBuf, escapedSearch.c_str());
}
else if (appname.find(L"notepad3.exe") != std::wstring::npos)
{
// Notepad3
std::wstring mode = L"mb";
if (m_bUseRegex)
mode.append(L"r");
if (m_bCaseSensitive)
mode.append(L"c");
if (m_bDotMatchesNewline)
mode.append(L"a");
std::wstring escapedsearch = m_searchString;
EscCtrlCharacters(escapedsearch);
lineNumberParamBefore = CStringUtils::Format(L"/%s \"%s\" /g %s -", mode.c_str(), escapedsearch.c_str(), textLineBuf);
}
else if ((appname.find(L"bowpad.exe") != std::wstring::npos) || (appname.find(L"bowpad64.exe") != std::wstring::npos))
{
// BowPad
lineNumberParam = CStringUtils::Format(L"/line:%s", textLineBuf);
}
else if (appname.find(L"code.exe") != std::wstring::npos)
{
// Visual Studio Code
lineNumberParamBefore = L"--goto";
lineNumberParam = CStringUtils::Format(L":%s", textLineBuf);
}
// replace "%1" with %1
std::wstring tag = L"\"%1\"";
std::wstring repl = L"%1";
std::wstring::iterator itBegin = search(application.begin(), application.end(), tag.begin(), tag.end());
if (itBegin != application.end())
{
std::wstring::iterator itEnd = itBegin + tag.size();
application.replace(itBegin, itEnd, repl);
}
// replace %1 with "path/of/selected/file"
tag = L"%1";
if (application.find(L"rundll32.exe") == std::wstring::npos && application.find(L"--single-argument") == std::wstring::npos)
repl = L"\"" + inf.filePath + L"\"";
else
repl = inf.filePath;
if (!lineNumberParamBefore.empty())
{
repl = lineNumberParamBefore + L" " + repl;
}
itBegin = std::ranges::search(application, tag).begin();
if (itBegin != application.end())
{
std::wstring::iterator itEnd = itBegin + tag.size();
application.replace(itBegin, itEnd, repl);
}
if (!lineNumberParam.empty())
{
if (!lineNumberParam.starts_with(L":"))
{
application += L" ";
}
application += lineNumberParam;
}
STARTUPINFO startupInfo{};
PROCESS_INFORMATION processInfo{};
startupInfo.cb = sizeof(STARTUPINFO);
CreateProcess(nullptr, const_cast<wchar_t*>(application.c_str()), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
}
bool grepWinIsRegexValid(const std::wstring& searchString)
{
// check if the regex is valid
bool bValid = true;
try
{
boost::wregex expression = boost::wregex(searchString);
}
catch (const std::exception&)
{
bValid = false;
}
return bValid;
}
bool CSearchDlg::SaveSettings()
{
// get all the information we need from the dialog
auto buf = GetDlgItemText(IDC_SEARCHPATH);
m_searchPath = buf.get();
buf = GetDlgItemText(IDC_SEARCHTEXT);
m_searchString = buf.get();
buf = GetDlgItemText(IDC_REPLACETEXT);
m_replaceString = buf.get();
buf = GetDlgItemText(IDC_EXCLUDEDIRSPATTERN);
m_excludeDirsPatternRegex = buf.get();
buf = GetDlgItemText(IDC_PATTERN);
m_patternRegex = buf.get();
// split the pattern string into single patterns and
// add them to an array
auto pBuf = buf.get();
size_t pos = 0;
m_patterns.clear();
do
{
pos = wcscspn(pBuf, L"|");
std::wstring s = std::wstring(pBuf, pos);
if (!s.empty())
{
std::ranges::transform(s, s.begin(), ::towlower);
m_patterns.push_back(s);
auto endPart = s.rbegin();
if (*endPart == '*' && s.size() > 2)
{
++endPart;
if (*endPart == '.')
{
m_patterns.push_back(s.substr(0, s.size() - 2));
}
}
}
pBuf += pos;
pBuf++;
} while (*pBuf && (*(pBuf - 1)));
m_bUseRegex = (IsDlgButtonChecked(*this, IDC_REGEXRADIO) == BST_CHECKED);
if (m_bUseRegex)
{
// check if the regex is valid before doing the search
if (!grepWinIsRegexValid(m_searchString) && !m_searchString.empty())
{
return false;
}
}
m_bUseRegexForPaths = (IsDlgButtonChecked(*this, IDC_FILEPATTERNREGEX) == BST_CHECKED);
if (m_bUseRegexForPaths)
{
// check if the regex is valid before doing the search
if (!grepWinIsRegexValid(m_patternRegex) && !m_patternRegex.empty())
{
return false;
}
}
// check if the Exclude Dirs regex is valid before doing the search
if (!grepWinIsRegexValid(m_excludeDirsPatternRegex) && !m_excludeDirsPatternRegex.empty())
{
return false;
}
m_bAllSize = (IsDlgButtonChecked(*this, IDC_ALLSIZERADIO) == BST_CHECKED);
m_lSize = 0;
m_sizeCmp = 0;
if (!m_bAllSize)
{
buf = GetDlgItemText(IDC_SIZEEDIT);
m_lSize = _wtol(buf.get());
m_lSize *= 1024;
m_sizeCmp = static_cast<int>(SendDlgItemMessage(*this, IDC_SIZECOMBO, CB_GETCURSEL, 0, 0));
}
m_bIncludeSystem = (IsDlgButtonChecked(*this, IDC_INCLUDESYSTEM) == BST_CHECKED);
m_bIncludeHidden = (IsDlgButtonChecked(*this, IDC_INCLUDEHIDDEN) == BST_CHECKED);
m_bIncludeSubfolders = (IsDlgButtonChecked(*this, IDC_INCLUDESUBFOLDERS) == BST_CHECKED);
m_bIncludeBinary = (IsDlgButtonChecked(*this, IDC_INCLUDEBINARY) == BST_CHECKED);
m_bCreateBackup = (IsDlgButtonChecked(*this, IDC_CREATEBACKUP) == BST_CHECKED);
m_bWholeWords = (IsDlgButtonChecked(*this, IDC_WHOLEWORDS) == BST_CHECKED);
m_bUTF8 = (IsDlgButtonChecked(*this, IDC_UTF8) == BST_CHECKED);
m_bForceBinary = (IsDlgButtonChecked(*this, IDC_BINARY) == BST_CHECKED);
m_bCaseSensitive = (IsDlgButtonChecked(*this, IDC_CASE_SENSITIVE) == BST_CHECKED);
m_bDotMatchesNewline = (IsDlgButtonChecked(*this, IDC_DOTMATCHNEWLINE) == BST_CHECKED);
m_dateLimit = 0;
if (IsDlgButtonChecked(*this, IDC_RADIO_DATE_ALL) == BST_CHECKED)
m_dateLimit = 0;
if (IsDlgButtonChecked(*this, IDC_RADIO_DATE_NEWER) == BST_CHECKED)
m_dateLimit = IDC_RADIO_DATE_NEWER - IDC_RADIO_DATE_ALL;
if (IsDlgButtonChecked(*this, IDC_RADIO_DATE_OLDER) == BST_CHECKED)
m_dateLimit = IDC_RADIO_DATE_OLDER - IDC_RADIO_DATE_ALL;
if (IsDlgButtonChecked(*this, IDC_RADIO_DATE_BETWEEN) == BST_CHECKED)
m_dateLimit = IDC_RADIO_DATE_BETWEEN - IDC_RADIO_DATE_ALL;
SYSTEMTIME sysTime = {0};
DateTime_GetSystemtime(GetDlgItem(*this, IDC_DATEPICK1), &sysTime);
SystemTimeToFileTime(&sysTime, &m_date1);
DateTime_GetSystemtime(GetDlgItem(*this, IDC_DATEPICK2), &sysTime);
SystemTimeToFileTime(&sysTime, &m_date2);
m_showContent = IsDlgButtonChecked(*this, IDC_RESULTCONTENT) == BST_CHECKED;
if (m_searchPath.empty())
return false;
if (m_bNoSaveSettings)
return true;
if (bPortable)
g_iniFile.SetValue(L"global", L"searchfor", m_searchString.c_str());
//else
// m_regSearchString = m_searchString
if (bPortable)
g_iniFile.SetValue(L"global", L"searchpath", m_searchPath.c_str());
else
m_regSearchPath = m_searchPath;
if (bPortable)
g_iniFile.SetBoolValue(L"global", L"UseRegex", m_bUseRegex);
else
m_regUseRegex = static_cast<DWORD>(m_bUseRegex);
if (bPortable)
g_iniFile.SetBoolValue(L"global", L"UseFileMatchRegex", m_bUseRegexForPaths);
else
m_regUseRegexForPaths = static_cast<DWORD>(m_bUseRegexForPaths);
if (bPortable)
g_iniFile.SetBoolValue(L"global", L"AllSize", m_bAllSize);
else
m_regAllSize = static_cast<DWORD>(m_bAllSize);
if (bPortable)
g_iniFile.SetValue(L"global", L"Size", CStringUtils::Format(L"%I64u", m_lSize / 1024).c_str());
else
m_regSize = CStringUtils::Format(L"%I64u", m_lSize / 1024).c_str();
if (bPortable)
g_iniFile.SetValue(L"global", L"SizeCombo", CStringUtils::Format(L"%d", m_sizeCmp).c_str());
else
m_regSizeCombo = m_sizeCmp;
if (bPortable)
{
g_iniFile.SetBoolValue(L"global", L"IncludeSystem", m_bIncludeSystem);
g_iniFile.SetBoolValue(L"global", L"IncludeHidden", m_bIncludeHidden);
g_iniFile.SetBoolValue(L"global", L"IncludeSubfolders", m_bIncludeSubfolders);
g_iniFile.SetBoolValue(L"global", L"IncludeBinary", m_bIncludeBinary);
g_iniFile.SetBoolValue(L"global", L"CreateBackup", m_bCreateBackup);
g_iniFile.SetBoolValue(L"global", L"WholeWords", m_bWholeWords);
g_iniFile.SetBoolValue(L"global", L"UTF8", m_bUTF8);
g_iniFile.SetBoolValue(L"global", L"StayOnTop", m_bStayOnTop);
g_iniFile.SetBoolValue(L"global", L"Binary", m_bForceBinary);
g_iniFile.SetBoolValue(L"global", L"CaseSensitive", m_bCaseSensitive);
g_iniFile.SetBoolValue(L"global", L"DotMatchesNewline", m_bDotMatchesNewline);
g_iniFile.SetValue(L"global", L"pattern", m_patternRegex.c_str());
g_iniFile.SetValue(L"global", L"ExcludeDirsPattern", m_excludeDirsPatternRegex.c_str());
g_iniFile.SetValue(L"global", L"DateLimit", std::to_wstring(m_dateLimit).c_str());
g_iniFile.SetValue(L"global", L"Date1Low", std::to_wstring(m_date1.dwLowDateTime).c_str());
g_iniFile.SetValue(L"global", L"Date1High", std::to_wstring(m_date1.dwHighDateTime).c_str());
g_iniFile.SetValue(L"global", L"Date2Low", std::to_wstring(m_date2.dwLowDateTime).c_str());
g_iniFile.SetValue(L"global", L"Date2High", std::to_wstring(m_date2.dwHighDateTime).c_str());
if (!m_showContentSet)
g_iniFile.SetBoolValue(L"global", L"showcontent", m_showContent);
}
else
{
m_regIncludeSystem = static_cast<DWORD>(m_bIncludeSystem);
m_regIncludeHidden = static_cast<DWORD>(m_bIncludeHidden);
m_regIncludeSubfolders = static_cast<DWORD>(m_bIncludeSubfolders);
m_regIncludeBinary = static_cast<DWORD>(m_bIncludeBinary);
m_regCreateBackup = static_cast<DWORD>(m_bCreateBackup);
m_regWholeWords = static_cast<DWORD>(m_bWholeWords);
m_regUTF8 = static_cast<DWORD>(m_bUTF8);
m_regStayOnTop = static_cast<DWORD>(m_bStayOnTop);
m_regBinary = static_cast<DWORD>(m_bForceBinary);
m_regCaseSensitive = static_cast<DWORD>(m_bCaseSensitive);
m_regDotMatchesNewline = static_cast<DWORD>(m_bDotMatchesNewline);
m_regPattern = m_patternRegex;
m_regExcludeDirsPattern = m_excludeDirsPatternRegex;
m_regDateLimit = m_dateLimit;
m_regDate1Low = m_date1.dwLowDateTime;
m_regDate1High = m_date1.dwHighDateTime;
m_regDate2Low = m_date2.dwLowDateTime;
m_regDate2High = m_date2.dwHighDateTime;
if (!m_showContentSet)
m_regShowContent = m_showContent;
}
SaveWndPosition();
return true;
}
bool CSearchDlg::NameCompareAsc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
std::wstring name1 = entry1.filePath.substr(entry1.filePath.find_last_of('\\') + 1);
std::wstring name2 = entry2.filePath.substr(entry2.filePath.find_last_of('\\') + 1);
return StrCmpLogicalW(name1.c_str(), name2.c_str()) < 0;
}
bool CSearchDlg::SizeCompareAsc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
return entry1.fileSize < entry2.fileSize;
}
bool CSearchDlg::MatchesCompareAsc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
return entry1.matchCount < entry2.matchCount;
}
bool CSearchDlg::PathCompareAsc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
std::wstring name1 = entry1.filePath.substr(entry1.filePath.find_last_of('\\') + 1);
std::wstring name2 = entry2.filePath.substr(entry2.filePath.find_last_of('\\') + 1);
std::wstring path1 = entry1.filePath.substr(0, entry1.filePath.size() - name1.size() - 1);
std::wstring path2 = entry2.filePath.substr(0, entry2.filePath.size() - name2.size() - 1);
int cmp = path1.compare(path2);
if (cmp != 0)
return cmp < 0;
return StrCmpLogicalW(name1.c_str(), name2.c_str()) < 0;
}
bool CSearchDlg::EncodingCompareAsc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
return entry1.encoding < entry2.encoding;
}
bool CSearchDlg::ModifiedTimeCompareAsc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
return CompareFileTime(&entry1.modifiedTime, &entry2.modifiedTime) < 0;
}
bool CSearchDlg::ExtCompareAsc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
auto dotPos1 = entry1.filePath.find_last_of('.');
auto dotPos2 = entry2.filePath.find_last_of('.');
std::wstring ext1 = dotPos1 != std::wstring::npos ? entry1.filePath.substr(dotPos1 + 1) : L"";
std::wstring ext2 = dotPos2 != std::wstring::npos ? entry2.filePath.substr(dotPos2 + 1) : L"";
return StrCmpLogicalW(ext1.c_str(), ext2.c_str()) < 0;
}
bool CSearchDlg::NameCompareDesc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
std::wstring name1 = entry1.filePath.substr(entry1.filePath.find_last_of('\\') + 1);
std::wstring name2 = entry2.filePath.substr(entry2.filePath.find_last_of('\\') + 1);
return StrCmpLogicalW(name1.c_str(), name2.c_str()) > 0;
}
bool CSearchDlg::SizeCompareDesc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
return entry1.fileSize > entry2.fileSize;
}
bool CSearchDlg::MatchesCompareDesc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
return entry1.matchCount > entry2.matchCount;
}
bool CSearchDlg::PathCompareDesc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
std::wstring name1 = entry1.filePath.substr(entry1.filePath.find_last_of('\\') + 1);
std::wstring name2 = entry2.filePath.substr(entry2.filePath.find_last_of('\\') + 1);
std::wstring path1 = entry1.filePath.substr(0, entry1.filePath.size() - name1.size() - 1);
std::wstring path2 = entry2.filePath.substr(0, entry2.filePath.size() - name2.size() - 1);
int cmp = path1.compare(path2);
if (cmp != 0)
return cmp > 0;
return StrCmpLogicalW(name1.c_str(), name2.c_str()) > 0;
}
bool CSearchDlg::EncodingCompareDesc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
return entry1.encoding > entry2.encoding;
}
bool CSearchDlg::ModifiedTimeCompareDesc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
return CompareFileTime(&entry1.modifiedTime, &entry2.modifiedTime) > 0;
}
bool CSearchDlg::ExtCompareDesc(const CSearchInfo& entry1, const CSearchInfo& entry2)
{
auto dotPos1 = entry1.filePath.find_last_of('.');
auto dotPos2 = entry2.filePath.find_last_of('.');
std::wstring ext1 = dotPos1 != std::wstring::npos ? entry1.filePath.substr(dotPos1 + 1) : L"";
std::wstring ext2 = dotPos2 != std::wstring::npos ? entry2.filePath.substr(dotPos2 + 1) : L"";
return StrCmpLogicalW(ext1.c_str(), ext2.c_str()) > 0;
}
bool grepWinMatchI(const std::wstring& theRegex, const wchar_t* pText)
{
try
{
boost::wregex expression = boost::wregex(theRegex, boost::regex::normal | boost::regbase::icase);
boost::wcmatch whatc;
if (boost::regex_match(pText, whatc, expression))
{
return true;
}
}
catch (const std::exception&)
{
}
return false;
}
DWORD CSearchDlg::SearchThread()
{
ProfileTimer profile(L"SearchThread");
auto pathBuf = std::make_unique<wchar_t[]>(MAX_PATH_NEW);
DWORD const nMaxNumOfWorker = std::thread::hardware_concurrency() << 2;
DWORD const nOfWorker = max(min(bPortable ? g_iniFile.GetLongValue(L"global", L"MaxNumOfWorker", nMaxNumOfWorker >> 1) :
static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWinNP3\\MaxNumOfWorker", nMaxNumOfWorker >> 1)), nMaxNumOfWorker), 1);
s_SearchThreadMap.clear();
s_SearchThreadMap.set_max_worker(nOfWorker);
// split the path string into single paths and
// add them to an array
const auto* pBufSearchPath = m_searchPath.c_str();
size_t pos = 0;
std::vector<std::wstring> pathVector;
do
{
pos = wcscspn(pBufSearchPath, L"|");
std::wstring s = std::wstring(pBufSearchPath, pos);
if (!s.empty())
{
// Remove extra backslashes except for the UNC identifier
std::string::size_type found = s.find_first_of('\\', 1);
while (found != std::string::npos)
{
while (s[found + 1] == '\\')
{
s.erase(found + 1, 1);
}
found = s.find_first_of('\\', found + 1);
}
CStringUtils::rtrim(s, L"\\/");
pathVector.push_back(s);
}
pBufSearchPath += pos;
pBufSearchPath++;
} while (*pBufSearchPath && (*(pBufSearchPath - 1)));
if (!m_bUseRegex && !m_replaceString.empty())
{
// escape all characters in the replace string
std::wstring sRepl;
for (const auto& c : m_replaceString)
{
switch (c)
{
case '$':
case '\\':
case '(':
case ')':
case '?':
case ':':
sRepl += L"\\";
break;
default:
break;
}
sRepl += c;
}
m_replaceString = sRepl;
}
SendMessage(*this, SEARCH_START, 0, 0);
std::wstring searchStringutf16;
for (auto c : m_searchString)
{
searchStringutf16 += c;
searchStringutf16 += L"\\x00";
}
for (std::wstring searchPath : pathVector)
{
size_t endPos = searchPath.find_last_not_of(L" \\");
if (std::wstring::npos != endPos)
{
searchPath = searchPath.substr(0, endPos + 1);
if (searchPath[searchPath.length() - 1] == ':')
searchPath += L"\\";
}
std::wstring searchRoot = searchPath;
if (!searchPath.empty())
{
bool bAlwaysSearch = false;
if (!PathIsDirectory(searchPath.c_str()))
{
bAlwaysSearch = true;
searchRoot = searchRoot.substr(0, searchRoot.find_last_of('\\'));
}
bool bIsDirectory = false;
CDirFileEnum fileEnumerator(searchPath.c_str());
bool bRecurse = m_bIncludeSubfolders;
std::wstring sPath;
while ((fileEnumerator.NextFile(sPath, &bIsDirectory, bRecurse) || bAlwaysSearch) && !IsCancelled())
{
if (bAlwaysSearch && _wcsicmp(searchPath.c_str(), sPath.c_str()))
bAlwaysSearch = false;
if (s_BackupAndTmpFiles.contains(sPath))
continue; // don't search own backup files
wcscpy_s(pathBuf.get(), MAX_PATH_NEW, sPath.c_str());
if (!bIsDirectory)
{
bool bSearch = false;
DWORD nFileSizeLow = 0;
uint64_t fullFileSize = 0;
FILETIME ft = {0};
if (bAlwaysSearch)
{
wcscpy_s(pathBuf.get(), MAX_PATH_NEW, searchPath.c_str());
CAutoFile hFile = CreateFile(searchPath.c_str(), FILE_READ_EA, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile)
{
BY_HANDLE_FILE_INFORMATION bhfi = {0};
GetFileInformationByHandle(hFile, &bhfi);
nFileSizeLow = bhfi.nFileSizeLow;
fullFileSize = (((uint64_t) bhfi.nFileSizeHigh) << 32) | bhfi.nFileSizeLow;
ft = bhfi.ftLastWriteTime;
}
}
else
{
const WIN32_FIND_DATA * pFindData = fileEnumerator.GetFileInfo();
if (pFindData) {
bSearch = ((m_bIncludeHidden) || ((pFindData->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) == 0));
bSearch = bSearch && ((m_bIncludeSystem) || ((pFindData->dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) == 0));
fullFileSize = (static_cast<uint64_t>(pFindData->nFileSizeHigh) << 32) | pFindData->nFileSizeLow;
ft = pFindData->ftLastWriteTime;
nFileSizeLow = pFindData->nFileSizeLow;
if (!m_bAllSize && bSearch)
{
switch (m_sizeCmp)
{
case 0: // less than
bSearch = bSearch && (fullFileSize < m_lSize);
break;
case 1: // equal
bSearch = bSearch && (fullFileSize == m_lSize);
break;
case 2: // greater than
bSearch = bSearch && (fullFileSize > m_lSize);
break;
default:
break;
}
}
}
if (bSearch)
{
switch (m_dateLimit + IDC_RADIO_DATE_ALL)
{
default:
case IDC_RADIO_DATE_ALL:
break;
case IDC_RADIO_DATE_NEWER:
bSearch = CompareFileTime(&ft, &m_date1) >= 0;
break;
case IDC_RADIO_DATE_OLDER:
bSearch = CompareFileTime(&ft, &m_date1) <= 0;
break;
case IDC_RADIO_DATE_BETWEEN:
bSearch = CompareFileTime(&ft, &m_date1) >= 0;
bSearch = bSearch && (CompareFileTime(&ft, &m_date2) <= 0);
break;
}
}
}
bRecurse = ((m_bIncludeSubfolders) && (bSearch));
bool bPattern = MatchPath(pathBuf.get());
auto sInfoPtr = std::make_shared<CSearchInfo>(pathBuf.get());
sInfoPtr->fileSize = fullFileSize;
sInfoPtr->modifiedTime = ft;
SearchFlags_t searchFlags = {
bAlwaysSearch,
m_bUTF8,
m_bForceBinary,
m_bIncludeBinary,
m_bUseRegex,
m_bCaseSensitive,
m_bDotMatchesNewline,
m_bWholeWords,
m_bCreateBackup,
m_bCreateBackupInFolders,
m_bReplace,
m_bCaptureSearch
};
if (nOfWorker > 1)
{
if ((bSearch && bPattern) || bAlwaysSearch)
{
if (m_searchString.empty())
{
s_SearchThreadMap.insert_ready(sInfoPtr, 1);
}
else
{
std::shared_future<int> foundFuture = std::async(std::launch::async, SearchFile, sInfoPtr, searchRoot, searchFlags,
m_searchString, searchStringutf16, m_replaceString);
s_SearchThreadMap.insert_future(sInfoPtr, foundFuture);
}
}
else
{
s_SearchThreadMap.insert_ready(sInfoPtr, -1);
}
}
else
{
int const nFound = SearchFile(sInfoPtr, searchRoot, searchFlags, m_searchString, searchStringutf16, m_replaceString);
SendMessage(*this, SEARCH_PROGRESS, (WPARAM)sInfoPtr.get(), nFound);
}
}
else // directory: search for filename
{
const WIN32_FIND_DATA * pFindData = fileEnumerator.GetFileInfo();
if (pFindData)
{
bool bSearch = ((m_bIncludeHidden) || ((pFindData->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) == 0));
bSearch = bSearch && ((m_bIncludeSystem) || ((pFindData->dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) == 0));
std::wstring relpath = pathBuf.get();
relpath = relpath.substr(searchPath.size());
if (!relpath.empty())
{
if (relpath[0] == '\\')
relpath = relpath.substr(1);
}
bool bExcludeDir = bSearch && !m_excludeDirsPatternRegex.empty() &&
(grepWinMatchI(m_excludeDirsPatternRegex, pFindData->cFileName) ||
grepWinMatchI(m_excludeDirsPatternRegex, pathBuf.get()) ||
grepWinMatchI(m_excludeDirsPatternRegex, relpath.c_str()));
bSearch = bSearch && !bExcludeDir;
bRecurse = ((bIsDirectory) && (m_bIncludeSubfolders) && (bSearch));
if (m_searchString.empty() && m_replaceString.empty())
{
// if there's no search and replace string, include folders in the 'matched' list if they
// match the specified file pattern
if (MatchPath(pathBuf.get()))
{
if (bSearch)
{
switch (m_dateLimit + IDC_RADIO_DATE_ALL)
{
default:
case IDC_RADIO_DATE_ALL:
break;
case IDC_RADIO_DATE_NEWER:
bSearch = CompareFileTime(&pFindData->ftLastWriteTime, &m_date1) >= 0;
break;
case IDC_RADIO_DATE_OLDER:
bSearch = CompareFileTime(&pFindData->ftLastWriteTime, &m_date1) <= 0;
break;
case IDC_RADIO_DATE_BETWEEN:
bSearch = CompareFileTime(&pFindData->ftLastWriteTime, &m_date1) >= 0;
bSearch = bSearch && (CompareFileTime(&pFindData->ftLastWriteTime, &m_date2) <= 0);
break;
}
}
if (!m_bAllSize && bSearch)
{
// assume a 'file'-size of zero for dirs
switch (m_sizeCmp)
{
case 0: // less than
bSearch = bSearch && (0 < m_lSize);
break;
case 1: // equal
bSearch = bSearch && (0 == m_lSize);
break;
case 2: // greater than
bSearch = bSearch && (0 > m_lSize);
break;
default:
break;
}
}
if (bSearch)
{
auto sInfoPtr = std::make_shared<CSearchInfo>(pathBuf.get());
sInfoPtr->modifiedTime = pFindData->ftLastWriteTime;
sInfoPtr->folder = true;
s_SearchThreadMap.insert_ready(sInfoPtr, -1);
}
}
}
}
}
bAlwaysSearch = false;
} // while next file
} // empty searchpath
} // pathvector
if (nOfWorker <= 1)
{
SendMessage(*this, SEARCH_END, 0, 0);
// refresh cursor
POINT pt;
GetCursorPos(&pt);
SetCursorPos(pt.x, pt.y);
PostMessage(m_hwnd, WM_GREPWIN_THREADEND, 0, 0);
}
InterlockedExchange(&s_SearchThreadRunning, FALSE);
return 0L;
}
DWORD WINAPI SearchThreadEntry(LPVOID lpParam)
{
auto* const pThis = (CSearchDlg* const)lpParam;
if (pThis)
return pThis->SearchThread();
return 0L;
}
DWORD CSearchDlg::EvaluationThread()
{
while (IsSearchThreadRunning() || !s_SearchThreadMap.empty())
{
int nFound = -1;
std::shared_ptr<CSearchInfo> const sInfoPtr = s_SearchThreadMap.retrieve(nFound);
if (sInfoPtr != nullptr)
{
SendMessage(*this, SEARCH_PROGRESS, (WPARAM)sInfoPtr.get(), nFound);
}
}
SendMessage(*this, SEARCH_END, 0, 0);
// refresh cursor
POINT pt;
GetCursorPos(&pt);
SetCursorPos(pt.x, pt.y);
s_SearchThreadMap.clear();
InterlockedExchange(&s_EvaluationThreadRunning, FALSE);
PostMessage(m_hwnd, WM_GREPWIN_THREADEND, 0, 0);
return 0L;
}
DWORD WINAPI EvaluationThreadEntry(LPVOID lpParam)
{
auto* const pThis = (CSearchDlg* const)lpParam;
if (pThis)
return pThis->EvaluationThread();
return 0L;
}
void CSearchDlg::SetSearchPath(const std::wstring& path)
{
m_searchPath = path;
SearchReplace(m_searchPath, L"/", L"\\");
}
void CSearchDlg::SetFileMask(const std::wstring& mask, bool reg)
{
m_patternRegex = mask;
m_bUseRegexForPaths = reg;
}
void CSearchDlg::SetPreset(const std::wstring& preset)
{
CBookmarks bookmarks;
bookmarks.Load();
auto bk = bookmarks.GetBookmark(preset);
if (bk.Name == preset)
{
auto removeQuotes = [](std::wstring& str) {
if (!str.empty())
{
if (str[0] == '"')
str = str.substr(1);
if (!str.empty())
{
if (str[str.size() - 1] == '"')
str = str.substr(0, str.size() - 1);
}
}
};
m_searchString = bk.Search;
m_replaceString = bk.Replace;
m_bUseRegex = bk.UseRegex;
m_bCaseSensitive = bk.CaseSensitive;
m_bDotMatchesNewline = bk.DotMatchesNewline;
m_bCreateBackup = bk.Backup;
m_bWholeWords = bk.WholeWords;
m_bUTF8 = bk.Utf8;
m_bForceBinary = bk.Binary;
m_bIncludeSystem = bk.IncludeSystem;
m_bIncludeSubfolders = bk.IncludeFolder;
m_bIncludeHidden = bk.IncludeHidden;
m_bIncludeBinary = bk.IncludeBinary;
m_excludeDirsPatternRegex = bk.ExcludeDirs;
m_patternRegex = bk.FileMatch;
m_bUseRegexForPaths = bk.FileMatchRegex;
if (!bk.Path.empty())
m_searchPath = bk.Path;
m_bIncludeSystemC = true;
m_bIncludeHiddenC = true;
m_bIncludeSubfoldersC = true;
m_bIncludeBinaryC = true;
m_bCreateBackupC = true;
m_bCreateBackupInFoldersC = true;
m_bWholeWordsC = true;
m_bUTF8C = true;
m_bCaseSensitiveC = true;
m_bDotMatchesNewlineC = true;
removeQuotes(m_searchString);
removeQuotes(m_replaceString);
removeQuotes(m_excludeDirsPatternRegex);
removeQuotes(m_patternRegex);
}
}
#if 0
void CSearchDlg::SetCaseSensitive(bool bSet)
{
m_bCaseSensitiveC = true;
m_bCaseSensitive = bSet;
}
void CSearchDlg::SetMatchesNewline(bool bSet)
{
m_bDotMatchesNewlineC = true;
m_bDotMatchesNewline = bSet;
}
void CSearchDlg::SetCreateBackups(bool bSet)
{
m_bCreateBackupC = true;
m_bCreateBackup = bSet;
m_bConfirmationOnReplace = false;
}
void CSearchDlg::SetCreateBackupsInFolders(bool bSet)
{
m_bCreateBackupInFoldersC = true;
m_bCreateBackupInFolders = bSet;
SetCreateBackups(bSet);
}
void CSearchDlg::SetWholeWords(bool bSet)
{
m_bWholeWordsC = true;
m_bWholeWords = bSet;
}
void CSearchDlg::SetUTF8(bool bSet)
{
m_bUTF8C = true;
m_bUTF8 = bSet;
m_bForceBinary = false;
}
void CSearchDlg::SetBinary(bool bSet)
{
m_bUTF8C = true;
m_bForceBinary = bSet;
m_bUTF8 = false;
}
void CSearchDlg::SetSize(uint64_t size, int cmp)
{
m_bSizeC = true;
m_lSize = size;
m_sizeCmp = cmp;
m_bAllSize = (size == static_cast<uint64_t>(-1));
}
void CSearchDlg::SetIncludeSystem(bool bSet)
{
m_bIncludeSystemC = true;
m_bIncludeSystem = bSet;
}
void CSearchDlg::SetIncludeHidden(bool bSet)
{
m_bIncludeHiddenC = true;
m_bIncludeHidden = bSet;
}
void CSearchDlg::SetIncludeSubfolders(bool bSet)
{
m_bIncludeSubfoldersC = true;
m_bIncludeSubfolders = bSet;
}
void CSearchDlg::SetIncludeBinary(bool bSet)
{
m_bIncludeBinaryC = true;
m_bIncludeBinary = bSet;
}
void CSearchDlg::SetDateLimit(int dateLimit, FILETIME t1, FILETIME t2)
{
m_bDateLimitC = true;
m_dateLimit = dateLimit;
m_date1 = t1;
m_date2 = t2;
}
#endif
bool CSearchDlg::MatchPath(LPCTSTR pathBuf)
{
bool bPattern = false;
// find start of pathname
const auto* pName = wcsrchr(pathBuf, '\\');
if (pName == nullptr)
pName = pathBuf;
else
pName++; // skip the last '\\' char
if (m_bUseRegexForPaths)
{
if (m_patterns.empty())
bPattern = true;
else
{
if (grepWinMatchI(m_patternRegex, pName))
bPattern = true;
// for a regex check, also test with the full path
else if (grepWinMatchI(m_patternRegex, pathBuf))
bPattern = true;
}
}
else
{
if (!m_patterns.empty())
{
if (!(m_patterns[0]).empty() && (m_patterns[0][0]=='-'))
bPattern = true;
std::wstring fName = pName;
std::ranges::transform(fName, fName.begin(), ::towlower);
for (const auto& pattern : m_patterns)
{
if (!pattern.empty() && pattern.at(0)=='-')
bPattern = bPattern && !wcswildcmp(&pattern[1], fName.c_str());
else
bPattern = bPattern || wcswildcmp(pattern.c_str(), fName.c_str());
}
}
else
bPattern = true;
}
return bPattern;
}
int CSearchDlg::SearchFile(std::shared_ptr<CSearchInfo> sinfoPtr, const std::wstring& searchRoot, const SearchFlags_t searchFlags,
const std::wstring& searchString, const std::wstring& searchStringUtf16le, const std::wstring& replaceString)
{
if (IsCancelled())
return -1; // don't start this search thread
int nFound = 0;
// we keep it simple:
// files bigger than 30MB are considered binary. Binary files are searched
// as if they're ANSI text files.
std::wstring localSearchString = searchString;
if (!searchFlags.bUseRegex)
{
SearchReplace(localSearchString, L"\\E", L"\\\\E");
localSearchString = L"\\Q" + localSearchString + L"\\E";
if (searchFlags.bWholeWords)
localSearchString = L"\\b" + localSearchString + L"\\b";
}
SearchReplace(localSearchString, L"${filepath}", sinfoPtr->filePath);
std::wstring const fileNameFull = sinfoPtr->filePath.substr(sinfoPtr->filePath.find_last_of('\\') + 1);
auto dotPos = fileNameFull.find_last_of('.');
if (dotPos != std::string::npos)
{
std::wstring fileName = fileNameFull.substr(0, dotPos);
SearchReplace(localSearchString, L"${filepath}", fileName);
if (fileNameFull.size() > dotPos)
{
std::wstring fileExt = fileNameFull.substr(dotPos + 1);
SearchReplace(localSearchString, L"${fileext}", fileExt);
}
}
CTextFile textFile;
CTextFile::UnicodeType type = CTextFile::AutoType;
bool bLoadResult = false;
if (searchFlags.bForceBinary)
{
type = CTextFile::Binary;
}
else
{
ProfileTimer profile((L"file load and parse: " + sinfoPtr->filePath).c_str());
auto nNullCount = bPortable ? int(g_iniFile.GetLongValue(L"settings", L"nullbytes", 0)) :
int(static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWinNP3\\nullbytes", 0)));
if (nNullCount > 0)
{
constexpr __int64 oneMB = 1024 * 1024;
auto megs = sinfoPtr->fileSize / oneMB;
textFile.SetNullbyteCountForBinary(nNullCount * ((int)megs + 1));
}
bLoadResult = textFile.Load(sinfoPtr->filePath.c_str(), type, searchFlags.bUTF8, nullptr);
}
sinfoPtr->encoding = type;
if ((bLoadResult) && ((type != CTextFile::Binary) || (searchFlags.bIncludeBinary) || searchFlags.bSearchAlways))
{
sinfoPtr->readError = false;
std::wstring::const_iterator start, end;
start = textFile.GetFileString().begin();
end = textFile.GetFileString().end();
boost::match_results<std::wstring::const_iterator> what;
try
{
int ft = boost::regex::normal;
if (!searchFlags.bCaseSensitive)
ft |= boost::regbase::icase;
boost::wregex expression = boost::wregex(localSearchString, ft);
boost::match_results<std::wstring::const_iterator> whatC;
boost::match_flag_type flags = boost::match_default | boost::format_all;
if (!searchFlags.bDotMatchesNewline)
flags |= boost::match_not_dot_newline;
long prevLineStart = 0;
long prevLineEnd = 0;
while (regex_search(start, end, whatC, expression, flags) && !IsCancelled())
{
if (whatC[0].matched)
{
nFound++;
if (IsNOTSearch())
break;
long lineStart = textFile.LineFromPosition(long(whatC[0].first - textFile.GetFileString().begin()));
long lineEnd = textFile.LineFromPosition(long(whatC[0].second - textFile.GetFileString().begin()));
if ((lineStart != prevLineStart)||(lineEnd != prevLineEnd))
{
for (long l = lineStart; l <= lineEnd; ++l)
{
auto sLine = textFile.GetLineString(l);
if (searchFlags.bCaptureSearch)
{
auto out = whatC.format(replaceString, flags);
sinfoPtr->matchLines.push_back(std::move(out));
}
else
sinfoPtr->matchLines.push_back(std::move(sLine));
sinfoPtr->matchLinesNumbers.push_back(l);
}
}
++sinfoPtr->matchCount;
prevLineStart = lineStart;
prevLineEnd = lineEnd;
}
// update search position:
if (start == whatC[0].second)
{
if (start == end)
break;
++start;
}
else
start = whatC[0].second;
// update flags:
flags |= boost::match_prev_avail;
flags |= boost::match_not_bob;
}
if (type == CTextFile::Binary)
{
boost::wregex expressionUtf16 = boost::wregex(searchStringUtf16le, ft);
while (regex_search(start, end, whatC, expressionUtf16, flags) && !IsCancelled())
{
if (whatC[0].matched)
{
nFound++;
if (IsNOTSearch())
break;
long lineStart = textFile.LineFromPosition(long(whatC[0].first - textFile.GetFileString().begin()));
long lineEnd = textFile.LineFromPosition(long(whatC[0].second - textFile.GetFileString().begin()));
if (searchFlags.bCaptureSearch)
{
auto out = whatC.format(replaceString, flags);
sinfoPtr->matchLines.push_back(out);
sinfoPtr->matchLinesNumbers.push_back(lineStart);
}
else
{
if ((lineStart != prevLineStart) || (lineEnd != prevLineEnd))
{
for (long l = lineStart; l <= lineEnd; ++l)
{
sinfoPtr->matchLines.push_back(textFile.GetLineString(l));
sinfoPtr->matchLinesNumbers.push_back(l);
}
}
}
++sinfoPtr->matchCount;
prevLineStart = lineStart;
prevLineEnd = lineEnd;
}
// update search position:
if (start == whatC[0].second)
{
if (start == end)
break;
++start;
}
else
start = whatC[0].second;
// update flags:
flags |= boost::match_prev_avail;
flags |= boost::match_not_bob;
}
}
if ((searchFlags.bReplace) && (nFound > 0))
{
flags &= ~boost::match_prev_avail;
flags &= ~boost::match_not_bob;
RegexReplaceFormatter replaceFmt(replaceString);
replaceFmt.SetReplacePair(L"${filepath}", sinfoPtr->filePath);
std::wstring fileNameFullW = sinfoPtr->filePath.substr(sinfoPtr->filePath.find_last_of('\\') + 1);
auto dotPosW = fileNameFullW.find_last_of('.');
if (dotPosW != std::string::npos)
{
std::wstring filename = fileNameFullW.substr(0, dotPosW);
replaceFmt.SetReplacePair(L"${filename}", filename);
if (fileNameFullW.size() > dotPosW)
{
std::wstring fileExt = fileNameFullW.substr(dotPosW + 1);
replaceFmt.SetReplacePair(L"${fileext}", fileExt);
}
}
std::wstring replaced = regex_replace(textFile.GetFileString(), expression, replaceFmt, flags);
if (replaced.compare(textFile.GetFileString()))
{
textFile.SetFileContent(replaced);
if (searchFlags.bCreateBackup)
{
std::wstring backupfile = sinfoPtr->filePath + L".bak";
if (searchFlags.bBackupInFolder)
{
std::wstring backupFolder = searchRoot + L"\\grepWinNP3_backup\\";
backupFolder += sinfoPtr->filePath.substr(searchRoot.size() + 1);
backupFolder = CPathUtils::GetParentDirectory(backupFolder);
CPathUtils::CreateRecursiveDirectory(backupFolder);
backupfile = backupFolder + L"\\" + CPathUtils::GetFileName(sinfoPtr->filePath);
}
s_BackupAndTmpFiles.insert(backupfile);
CopyFile(sinfoPtr->filePath.c_str(), backupfile.c_str(), FALSE);
}
if (!textFile.Save(sinfoPtr->filePath.c_str()))
{
// saving the file failed. Find out why...
DWORD err = GetLastError();
if (err == ERROR_ACCESS_DENIED)
{
// access denied can happen if the file has the
// read-only flag and/or the hidden flag set
// those are not situations where we should fail, so
// we reset those flags and restore them
// again after saving the file
DWORD origAttributes = GetFileAttributes(sinfoPtr->filePath.c_str());
DWORD newAttributes = origAttributes & (~(FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM));
SetFileAttributes(sinfoPtr->filePath.c_str(), newAttributes);
bool bRet = textFile.Save(sinfoPtr->filePath.c_str());
// restore the attributes
SetFileAttributes(sinfoPtr->filePath.c_str(), origAttributes);
if (!bRet)
{
//SendMessage(*this, SEARCH_PROGRESS, 0, 0);
return -1;
}
}
}
}
}
}
catch (const std::exception&)
{
//SendMessage(*this, SEARCH_PROGRESS, 0, 0);
return -1;
}
}
else
{
if (type == CTextFile::AutoType)
{
sinfoPtr->readError = true;
//SendMessage(*this, SEARCH_FOUND, 0, reinterpret_cast<LPARAM>(&sInfo));
//SendMessage(*this, SEARCH_PROGRESS, 1, 0);
return -1;
}
// file is either too big or binary.
// in any case, use the search function that uses a file iterator
// instead of a string iterator to reduce the memory consumption
if ((type != CTextFile::Binary) || searchFlags.bIncludeBinary || searchFlags.bSearchAlways || searchFlags.bForceBinary)
{
sinfoPtr->encoding = type;
std::string filePath = CUnicodeUtils::StdGetANSI(sinfoPtr->filePath);
std::string searchFor = (type == CTextFile::Ansi) ? CUnicodeUtils::StdGetANSI(searchString) : CUnicodeUtils::StdGetUTF8(searchString);
if (!searchFlags.bUseRegex)
{
searchFor = "\\Q";
searchFor += CUnicodeUtils::StdGetUTF8(searchString);
searchFor += "\\E";
}
boost::match_results<std::string::const_iterator> what;
boost::match_flag_type flags = boost::match_default | boost::format_all;
if (!searchFlags.bDotMatchesNewline)
flags |= boost::match_not_dot_newline;
int ft = boost::regex::normal;
if (!searchFlags.bCaseSensitive)
ft |= boost::regbase::icase;
try
{
boost::regex expression = boost::regex(searchFor, ft);
std::vector<DWORD> matchLinesNumbers;
bool bFound = false;
{
boost::spirit::classic::file_iterator<> start(filePath.c_str());
boost::spirit::classic::file_iterator<> fbeg = start;
boost::spirit::classic::file_iterator<> end = start.make_end();
boost::match_results<boost::spirit::classic::file_iterator<>> whatC;
while (boost::regex_search(start, end, whatC, expression, flags) && !IsCancelled())
{
nFound++;
if (IsNOTSearch())
break;
matchLinesNumbers.push_back(static_cast<DWORD>(whatC[0].first - fbeg));
++sinfoPtr->matchCount;
// update search position:
start = whatC[0].second;
// update flags:
flags |= boost::match_prev_avail;
flags |= boost::match_not_bob;
bFound = true;
}
}
if ((type == CTextFile::Binary) && !searchFlags.bReplace)
{
boost::regex expressionUtf16le = boost::regex(CUnicodeUtils::StdGetUTF8(searchStringUtf16le), ft);
boost::spirit::classic::file_iterator<> start(filePath.c_str());
boost::spirit::classic::file_iterator<> fbeg = start;
boost::spirit::classic::file_iterator<> end = start.make_end();
boost::match_results<boost::spirit::classic::file_iterator<>> whatC;
while (boost::regex_search(start, end, whatC, expression, flags) && !IsCancelled())
{
nFound++;
if (IsNOTSearch())
break;
matchLinesNumbers.push_back(static_cast<DWORD>(whatC[0].first - fbeg));
++sinfoPtr->matchCount;
// update search position:
start = whatC[0].second;
// update flags:
flags |= boost::match_prev_avail;
flags |= boost::match_not_bob;
bFound = true;
}
}
if (bFound && !IsCancelled())
{
if (!bLoadResult && (type != CTextFile::Binary) && !IsNOTSearch())
{
linePositions.clear();
// open the file and set up a vector of all lines
CAutoFile hFile;
int retrycounter = 0;
do
{
if (retrycounter)
Sleep(20);
hFile = CreateFile(sinfoPtr->filePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
retrycounter++;
} while (!hFile && retrycounter < 5);
if (hFile)
{
auto fBuf = std::make_unique<char[]>(4096);
DWORD bytesRead = 0;
size_t pos = 0;
while (ReadFile(hFile, fBuf.get(), 4096, &bytesRead, nullptr))
{
if (bytesRead == 0)
break;
for (DWORD br = 0; br < bytesRead; ++br)
{
if (fBuf[br] == '\r')
{
++br;
++pos;
if (br < bytesRead)
{
if (fBuf[br] == '\n')
{
// crlf lineending
auto lp = linePositions.size();
linePositions[pos] = static_cast<DWORD>(lp);
}
else
{
// cr lineending
auto lp = linePositions.size();
linePositions[pos - 1] = static_cast<DWORD>(lp);
}
}
else
break;
}
else if (fBuf[br] == '\n')
{
// lf lineending
auto lp = linePositions.size();
linePositions[pos] = static_cast<DWORD>(lp);
}
++pos;
}
}
for (unsigned long & matchlinesnumber : matchLinesNumbers)
{
auto fp = linePositions.lower_bound(matchlinesnumber);
if (fp != linePositions.end())
matchlinesnumber = fp->second;
}
}
}
sinfoPtr->matchLinesNumbers = matchLinesNumbers;
if (searchFlags.bReplace)
{
std::wstring backupfile = sinfoPtr->filePath + L".bak";
if (searchFlags.bCreateBackup)
{
if (searchFlags.bBackupInFolder)
{
std::wstring backupFolder = searchRoot + L"\\grepWinNP3_backup\\";
backupFolder += sinfoPtr->filePath.substr(searchRoot.size() + 1);
backupFolder = CPathUtils::GetParentDirectory(backupFolder);
CPathUtils::CreateRecursiveDirectory(backupFolder);
backupfile = backupFolder + L"\\" + CPathUtils::GetFileName(sinfoPtr->filePath);
}
CopyFile(sinfoPtr->filePath.c_str(), backupfile.c_str(), FALSE);
s_BackupAndTmpFiles.insert(backupfile);
}
flags &= ~boost::match_prev_avail;
flags &= ~boost::match_not_bob;
RegexReplaceFormatterA replaceFmt(CUnicodeUtils::StdGetUTF8(replaceString));
replaceFmt.SetReplacePair("${filepath}", CUnicodeUtils::StdGetUTF8(sinfoPtr->filePath));
std::string fileNameFullA = CUnicodeUtils::StdGetUTF8(sinfoPtr->filePath.substr(sinfoPtr->filePath.find_last_of('\\') + 1));
auto dotPosA = fileNameFullA.find_last_of('.');
if (dotPosA != std::string::npos)
{
std::string filename = fileNameFullA.substr(0, dotPosA);
replaceFmt.SetReplacePair("${filename}", filename);
if (fileNameFull.size() > dotPosA)
{
std::string fileExt = fileNameFullA.substr(dotPosA + 1);
replaceFmt.SetReplacePair("${fileext}", fileExt);
}
}
std::string filepathout = searchFlags.bCreateBackup ? filePath : filePath + ".grepwinreplaced";
if (!searchFlags.bCreateBackup)
s_BackupAndTmpFiles.insert(sinfoPtr->filePath + L".grepwinreplaced");
boost::iostreams::mapped_file_source replaceinfile(searchFlags.bCreateBackup ? CUnicodeUtils::StdGetANSI(backupfile).c_str() : filePath.c_str());
std::ofstream os(filepathout.c_str(), std::ios::out | std::ios::trunc | std::ios::binary);
std::ostream_iterator<char, char> out(os);
regex_replace(out, replaceinfile.begin(), replaceinfile.end(), expression, replaceFmt, flags);
os.close();
replaceinfile.close();
if (!searchFlags.bCreateBackup)
MoveFileExA(filepathout.c_str(), filePath.c_str(), MOVEFILE_REPLACE_EXISTING);
}
}
}
catch (const std::exception&)
{
return -1;
}
catch (...)
{
return -1;
}
}
else
nFound = -1; // skipped
}
if (IsNOTSearch())
return ((nFound > 0) ? 0 : 1);
return nFound;
}
void CSearchDlg::FormatDate(wchar_t dateNative[], const FILETIME& fileTime, bool forceShortFmt) const
{
dateNative[0] = '\0';
// Convert UTC to local time
SYSTEMTIME systemTime;
FileTimeToSystemTime(&fileTime, &systemTime);
static TIME_ZONE_INFORMATION timeZone = {-1};
if (timeZone.Bias == -1)
GetTimeZoneInformation(&timeZone);
SYSTEMTIME localSystime;
SystemTimeToTzSpecificLocalTime(&timeZone, &systemTime, &localSystime);
wchar_t timeBuf[GREPWIN_DATEBUFFER] = {0};
wchar_t dateBuf[GREPWIN_DATEBUFFER] = {0};
LCID locale = MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT);
/// reusing this instance is vital for \ref formatDate performance
DWORD flags = forceShortFmt ? DATE_SHORTDATE : DATE_LONGDATE;
GetDateFormat(locale, flags, &localSystime, nullptr, dateBuf, GREPWIN_DATEBUFFER);
GetTimeFormat(locale, 0, &localSystime, nullptr, timeBuf, GREPWIN_DATEBUFFER);
wcsncat_s(dateNative, GREPWIN_DATEBUFFER, dateBuf, GREPWIN_DATEBUFFER);
wcsncat_s(dateNative, GREPWIN_DATEBUFFER, L" ", GREPWIN_DATEBUFFER);
wcsncat_s(dateNative, GREPWIN_DATEBUFFER, timeBuf, GREPWIN_DATEBUFFER);
}
int CSearchDlg::CheckRegex()
{
auto buf = GetDlgItemText(IDC_SEARCHTEXT);
int len = static_cast<int>(wcslen(buf.get()));
if (IsDlgButtonChecked(*this, IDC_REGEXRADIO) == BST_CHECKED)
{
m_bUseRegex = true;
// check if the regex is valid
bool bValid = true;
if (len)
{
try
{
std::wstring localSearchString = buf.get();
SearchReplace(localSearchString, L"${filepath}", L"");
SearchReplace(localSearchString, L"${filepath}", L"");
SearchReplace(localSearchString, L"${fileext}", L"");
boost::wregex expression = boost::wregex(localSearchString);
}
catch (const std::exception&)
{
bValid = false;
}
}
if (len)
{
if (bValid)
{
SetDlgItemText(*this, IDC_REGEXOKLABEL, TranslatedString(hResource, IDS_REGEXOK).c_str());
DialogEnableWindow(IDOK, true);
DialogEnableWindow(IDC_REPLACE, true);
DialogEnableWindow(IDC_CREATEBACKUP, true);
}
else
{
SetDlgItemText(*this, IDC_REGEXOKLABEL, TranslatedString(hResource, IDS_REGEXINVALID).c_str());
DialogEnableWindow(IDOK, false);
DialogEnableWindow(IDC_REPLACE, false);
DialogEnableWindow(IDC_CREATEBACKUP, false);
}
}
else
{
SetDlgItemText(*this, IDC_REGEXOKLABEL, L"");
DialogEnableWindow(IDOK, true);
DialogEnableWindow(IDC_REPLACE, false);
DialogEnableWindow(IDC_CREATEBACKUP, false);
}
}
else
{
m_bUseRegex = false;
SetDlgItemText(*this, IDC_REGEXOKLABEL, L"");
DialogEnableWindow(IDOK, true);
DialogEnableWindow(IDC_REPLACE, len > 0);
DialogEnableWindow(IDC_CREATEBACKUP, len > 0);
}
return len;
}
void CSearchDlg::AutoSizeAllColumns()
{
HWND hListControl = GetDlgItem(*this, IDC_RESULTLIST);
auto headerCtrl = ListView_GetHeader(hListControl);
int nItemCount = ListView_GetItemCount(hListControl);
wchar_t textBuf[MAX_PATH * 4] = {0};
std::vector<int> colWidths;
if (headerCtrl)
{
int maxCol = Header_GetItemCount(headerCtrl) - 1;
int imgWidth = 0;
auto hImgList = ListView_GetImageList(hListControl, LVSIL_SMALL);
if ((hImgList) && (ImageList_GetImageCount(hImgList)))
{
IMAGEINFO imgInfo;
ImageList_GetImageInfo(hImgList, 0, &imgInfo);
imgWidth = (imgInfo.rcImage.right - imgInfo.rcImage.left) + 3; // 3 pixels between icon and text
}
for (int col = 0; col <= maxCol; col++)
{
HDITEM hdi = {0};
hdi.mask = HDI_TEXT;
hdi.pszText = textBuf;
hdi.cchTextMax = _countof(textBuf);
Header_GetItem(headerCtrl, col, &hdi);
int cx = ListView_GetStringWidth(hListControl, hdi.pszText) + 20; // 20 pixels for col separator and margin
int inc = max(1, nItemCount / 1000);
for (int index = 0; index < nItemCount; index = index + inc)
{
// get the width of the string and add 14 pixels for the column separator and margins
ListView_GetItemText(hListControl, index, col, textBuf, _countof(textBuf));
int lineWidth = ListView_GetStringWidth(hListControl, textBuf) + 14;
// add the image size
if (col == 0)
lineWidth += imgWidth;
if (cx < lineWidth)
cx = lineWidth;
}
colWidths.push_back(cx);
}
}
bool fileList = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
if (!fileList)
{
RECT rc{};
ListView_GetItemRect(hListControl, 0, &rc, LVIR_BOUNDS);
auto itemWidth = rc.right - rc.left;
ListView_GetItemRect(hListControl, 0, &rc, LVIR_ICON);
auto iconWidth = rc.right - rc.left;
itemWidth -= iconWidth;
itemWidth -= 2 * GetSystemMetrics(SM_CXBORDER);
auto totalWidth = std::accumulate(colWidths.begin(), colWidths.end(), 0);
totalWidth -= colWidths[colWidths.size() - 2];
auto textWidth = itemWidth - totalWidth;
if (textWidth > 0)
colWidths[colWidths.size() - 2] = textWidth;
else
{
colWidths[colWidths.size() - 1] = 100;
totalWidth = std::accumulate(colWidths.begin(), colWidths.end(), 0);
totalWidth -= colWidths[colWidths.size() - 2];
textWidth = itemWidth - totalWidth;
if (textWidth > 0)
colWidths[colWidths.size() - 2] = textWidth;
}
}
int col = 0;
for (const auto& colWidth : colWidths)
{
ListView_SetColumnWidth(hListControl, col, colWidth);
++col;
}
}
int CSearchDlg::GetSelectedListIndex(int index)
{
bool fileList = (IsDlgButtonChecked(*this, IDC_RESULTFILES) == BST_CHECKED);
if (fileList)
return index;
auto tup = m_listItems[index];
return std::get<0>(tup);
}
bool CSearchDlg::FailedShowMessage(HRESULT hr)
{
if (FAILED(hr))
{
_com_error err(hr);
MessageBox(nullptr, L"grepWinNP3", err.ErrorMessage(), MB_ICONERROR);
return true;
}
return false;
}
#ifdef NP3_ALLOW_UPDATE
void CSearchDlg::CheckForUpdates(bool force)
{
bool bNewerAvailable = false;
// check for newer versions
bool doCheck = true;
if (bPortable)
doCheck = g_iniFile.GetBoolValue(L"global", L"CheckForUpdates", true));
else
doCheck = !!static_cast<DWORD>(CRegStdDWORD(L"Software\\grepWinNP3\\CheckForUpdates", 1));
if (doCheck)
{
time_t now;
time(&now);
time_t last = 0;
if (bPortable)
{
last = _wtoll(g_iniFile.GetValue(L"global", L"CheckForUpdatesLast", L"0"));
}
else
{
last = _wtoll(static_cast<std::wstring>(CRegStdString(L"Software\\grepWinNP3\\CheckForUpdatesLast", L"0")).c_str());
}
double days = std::difftime(now, last) / (60LL * 60LL * 24LL);
if ((days >= 7.0) || force)
{
std::wstring tempFile = CTempFiles::Instance().GetTempFilePath(true);
std::wstring sCheckURL = L"https://raw.githubusercontent.com/stefankueng/grepWin/main/version.txt";
HRESULT res = URLDownloadToFile(nullptr, sCheckURL.c_str(), tempFile.c_str(), 0, nullptr);
if (res == S_OK)
{
if (bPortable)
{
g_iniFile.SetValue(L"global", L"CheckForUpdatesLast", std::to_wstring(now).c_str());
}
else
{
// ReSharper disable once CppEntityAssignedButNoRead
auto regLast = CRegStdString(L"Software\\grepWin\\CheckForUpdatesLast", L"0");
regLast = std::to_wstring(now);
}
std::ifstream file;
file.open(tempFile.c_str());
if (file.good())
{
char line[1024];
file.getline(line, sizeof(line));
auto verLine = CUnicodeUtils::StdGetUnicode(line);
bNewerAvailable = IsVersionNewer(verLine);
file.getline(line, sizeof(line));
auto updateUrl = CUnicodeUtils::StdGetUnicode(line);
if (bNewerAvailable)
{
if (bPortable)
{
g_iniFile.SetValue(L"global", L"CheckForUpdatesVersion", verLine.c_str());
g_iniFile.SetValue(L"global", L"CheckForUpdatesUrl", updateUrl.c_str());
}
else
{
// ReSharper disable once CppEntityAssignedButNoRead
auto regVersion = CRegStdString(L"Software\\grepWinNP3\\CheckForUpdatesVersion", L"");
regVersion = verLine;
// ReSharper disable once CppEntityAssignedButNoRead
auto regUpdateUrl = CRegStdString(L"Software\\grepWinNP3\\CheckForUpdatesUrl", L"");
regUpdateUrl = updateUrl;
}
ShowUpdateAvailable();
}
}
file.close();
DeleteFile(tempFile.c_str());
}
}
}
}
void CSearchDlg::ShowUpdateAvailable()
{
std::wstring sVersion;
std::wstring updateUrl;
if (bPortable)
{
sVersion = g_iniFile.GetValue(L"global", L"CheckForUpdatesVersion", L"");
updateUrl = g_iniFile.GetValue(L"global", L"CheckForUpdatesUrl", L"");
}
else
{
sVersion = CRegStdString(L"Software\\grepWinNP3\\CheckForUpdatesVersion", L"");
updateUrl = CRegStdString(L"Software\\grepWinNP3\\CheckForUpdatesUrl", L"");
}
if (IsVersionNewer(sVersion))
{
auto sUpdateAvailable = TranslatedString(hResource, IDS_UPDATEAVAILABLE);
sUpdateAvailable = CStringUtils::Format(sUpdateAvailable.c_str(), sVersion.c_str());
auto sLinkText = CStringUtils::Format(L"<a href=\"%s\">%s</a>", updateUrl.c_str(), sUpdateAvailable.c_str());
SetDlgItemText(*this, IDC_UPDATELINK, sLinkText.c_str());
ShowWindow(GetDlgItem(*this, IDC_UPDATELINK), SW_SHOW);
}
}
bool CSearchDlg::IsVersionNewer(const std::wstring& sVer) const
{
int major = 0;
int minor = 0;
int micro = 0;
int build = 0;
const wchar_t* pLine = sVer.c_str();
major = _wtoi(pLine);
pLine = wcschr(pLine, '.');
if (pLine)
{
pLine++;
minor = _wtoi(pLine);
pLine = wcschr(pLine, '.');
if (pLine)
{
pLine++;
micro = _wtoi(pLine);
pLine = wcschr(pLine, '.');
if (pLine)
{
pLine++;
build = _wtoi(pLine);
}
}
}
bool isNewer = false;
if (major > GREPWIN_VERMAJOR)
isNewer = true;
else if ((minor > GREPWIN_VERMINOR) && (major == GREPWIN_VERMAJOR))
isNewer = true;
else if ((micro > GREPWIN_VERMICRO) && (minor == GREPWIN_VERMINOR) && (major == GREPWIN_VERMAJOR))
isNewer = true;
else if ((build > GREPWIN_VERBUILD) && (micro == GREPWIN_VERMICRO) && (minor == GREPWIN_VERMINOR) && (major == GREPWIN_VERMAJOR))
isNewer = true;
return isNewer;
}
#endif
bool CSearchDlg::CloneWindow()
{
if (!SaveSettings()) { return false; }
if (bPortable) { g_iniFile.SaveFile(g_iniPath.c_str()); }
auto const dir = CPathUtils::GetModuleDir();
auto const file = CPathUtils::GetFileName(CPathUtils::GetModulePath());
std::wstring arguments;
arguments += CStringUtils::Format(L" /inipath:\"%s\"", g_iniPath.c_str());
arguments += CStringUtils::Format(L" /searchpath:\"%s\"", m_searchPath.c_str());
arguments += CStringUtils::Format(L" /searchfor:\"%s\"", m_searchString.c_str());
arguments += CStringUtils::Format(L" /replacewith:\"%s\"", m_replaceString.c_str());
arguments += L" /new"; // prevents searching of existing instance
SHELLEXECUTEINFO sei = {0};
sei.cbSize = sizeof(SHELLEXECUTEINFO);
sei.lpVerb = TEXT("open");
sei.lpFile = file.c_str();
sei.lpParameters = arguments.c_str();
sei.lpDirectory = dir.c_str();
sei.nShow = SW_SHOWNORMAL;
ShellExecuteEx(&sei);
return true;
}
std::wstring CSearchDlg::ExpandString(const std::wstring& replaceString) const
{
// ${now,formatString}
wchar_t buf[4096] = {};
GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, nullptr, nullptr, buf, _countof(buf));
std::wstring dateStr = buf;
GetTimeFormat(LOCALE_USER_DEFAULT, 0, nullptr, nullptr, buf, _countof(buf));
dateStr += L" - ";
dateStr += buf;
std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
int ft = boost::regex::normal;
boost::wregex expression = boost::wregex(L"\\$\\{now\\s*,?([^}]*)\\}", ft);
boost::match_results<std::wstring::const_iterator> whatC;
boost::match_flag_type flags = boost::match_default | boost::format_all;
auto resultString = replaceString;
try
{
while (regex_search(resultString.cbegin(), resultString.cend(), whatC, expression, flags))
{
if (whatC[0].matched)
{
auto fullMatch = whatC.str();
std::wstring formatStr;
if (whatC.size() > 1)
{
formatStr = whatC[1].str();
if (!formatStr.empty())
{
std::wstring formattedDateStr(4096, '\0');
struct tm locTime;
_localtime64_s(&locTime, &now);
std::wcsftime(&formattedDateStr[0], formattedDateStr.size(), formatStr.c_str(), &locTime);
SearchReplace(resultString, fullMatch, formattedDateStr);
}
}
else
{
SearchReplace(resultString, fullMatch, dateStr);
}
}
}
}
catch (const std::exception&)
{
}
return resultString;
}