diff --git a/ClassicNotepad.c b/ClassicNotepad.c index af1b4e8..c5ef881 100644 --- a/ClassicNotepad.c +++ b/ClassicNotepad.c @@ -6,6 +6,7 @@ #include #include #include +#include #ifndef swprintf_s int swprintf_s(wchar_t *buffer, size_t sizeOfBuffer, const wchar_t *format, ...); @@ -26,6 +27,9 @@ int swprintf_s(wchar_t *buffer, size_t sizeOfBuffer, const wchar_t *format, ...) #define IDM_EDIT_DELETE 2005 #define IDM_EDIT_SELECT 2006 #define IDM_EDIT_TIME 2007 +#define IDM_EDIT_FIND 2008 +#define IDM_EDIT_FIND_NEXT 2009 +#define IDM_EDIT_REPLACE 2010 #define IDM_FORMAT_WRAP 3001 #define IDM_FORMAT_FONT 3002 @@ -39,6 +43,27 @@ static HACCEL g_hAccel = NULL; static bool g_wordWrap = false; static WCHAR g_filePath[MAX_PATH] = L""; static LOGFONTW g_logFont = {0}; +static UINT g_msgFindReplace = 0; +static FINDREPLACEW g_fr = {0}; +static WCHAR g_findText[128] = L""; +static WCHAR g_replaceText[128] = L""; +static DWORD g_findFlags = FR_DOWN; +static HWND g_hFindDlg = NULL; +static WNDPROC g_findDlgOldProc = NULL; + +#define IDC_FIND_ALL 6001 +#ifndef edt1 +#define edt1 0x0480 +#endif +#ifndef chx1 +#define chx1 0x0430 +#endif +#ifndef chx2 +#define chx2 0x0431 +#endif + +static bool MatchSubstring(const WCHAR *text, const WCHAR *pattern, int patternLen, int pos, bool matchCase); +static void EnsureFindAllButton(void); #ifndef ARRAYSIZE #define ARRAYSIZE(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -150,6 +175,10 @@ static HMENU BuildMenu(void) AppendMenuW(hEdit, MF_STRING, IDM_EDIT_PASTE, L"&Paste\tCtrl+V"); AppendMenuW(hEdit, MF_STRING, IDM_EDIT_DELETE, L"&Delete\tDel"); AppendMenuW(hEdit, MF_SEPARATOR, 0, NULL); + AppendMenuW(hEdit, MF_STRING, IDM_EDIT_FIND, L"&Find...\tCtrl+F"); + AppendMenuW(hEdit, MF_STRING, IDM_EDIT_FIND_NEXT, L"Find &Next\tF3"); + AppendMenuW(hEdit, MF_STRING, IDM_EDIT_REPLACE, L"&Replace...\tCtrl+H"); + AppendMenuW(hEdit, MF_SEPARATOR, 0, NULL); AppendMenuW(hEdit, MF_STRING, IDM_EDIT_SELECT, L"Select &All\tCtrl+A"); AppendMenuW(hEdit, MF_STRING, IDM_EDIT_TIME, L"Time/&Date\tF5"); @@ -398,6 +427,383 @@ static void InsertTimeDate(void) swprintf_s(text, ARRAYSIZE(text), L"%02d/%02d/%04d %02d:%02d", st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute); SendMessageW(g_hEdit, EM_REPLACESEL, TRUE, (LPARAM)text); } +static bool MatchSubstring(const WCHAR *text, const WCHAR *pattern, int patternLen, int pos, bool matchCase) +{ + if (pos < 0) + { + return false; + } + for (int i = 0; i < patternLen; ++i) + { + WCHAR a = text[pos + i]; + WCHAR b = pattern[i]; + if (!matchCase) + { + a = (WCHAR)towlower(a); + b = (WCHAR)towlower(b); + } + if (a != b) + { + return false; + } + } + return true; +} + +static int CountOccurrences(bool matchCase) +{ + if (g_findText[0] == L'\0') + { + return 0; + } + + int findLen = (int)wcslen(g_findText); + int textLen = GetWindowTextLengthW(g_hEdit); + if (findLen == 0 || textLen < findLen) + { + return 0; + } + + WCHAR *buffer = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, (textLen + 1) * sizeof(WCHAR)); + if (!buffer) + { + return 0; + } + GetWindowTextW(g_hEdit, buffer, textLen + 1); + + int occurrences = 0; + for (int i = 0; i <= textLen - findLen; ) + { + if (MatchSubstring(buffer, g_findText, findLen, i, matchCase)) + { + occurrences++; + i += findLen; + } + else + { + ++i; + } + } + + HeapFree(GetProcessHeap(), 0, buffer); + return occurrences; +} + +static bool FindAndSelectText(HWND hwnd, DWORD flags, bool allowWrap) +{ + if (g_findText[0] == L'\0') + { + return false; + } + + int patternLen = (int)wcslen(g_findText); + int textLen = GetWindowTextLengthW(g_hEdit); + if (patternLen == 0 || textLen == 0 || textLen < patternLen) + { + MessageBoxW(hwnd, L"Cannot find the specified text.", L"ClassicNotepad", MB_OK | MB_ICONINFORMATION); + return false; + } + + WCHAR *buffer = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, (textLen + 1) * sizeof(WCHAR)); + if (!buffer) + { + return false; + } + GetWindowTextW(g_hEdit, buffer, textLen + 1); + + DWORD selStart = 0; + DWORD selEnd = 0; + SendMessageW(g_hEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd); + + bool searchDown = (flags & FR_DOWN) != 0; + bool matchCase = (flags & FR_MATCHCASE) != 0; + int found = -1; + + if (searchDown) + { + for (int i = (int)selEnd; i <= textLen - patternLen; ++i) + { + if (MatchSubstring(buffer, g_findText, patternLen, i, matchCase)) + { + found = i; + break; + } + } + if (found < 0 && allowWrap) + { + for (int i = 0; i < (int)selEnd && i <= textLen - patternLen; ++i) + { + if (MatchSubstring(buffer, g_findText, patternLen, i, matchCase)) + { + found = i; + break; + } + } + } + } + else + { + int startPos = (int)selStart - patternLen; + if (startPos > textLen - patternLen) + { + startPos = textLen - patternLen; + } + for (int i = startPos; i >= 0; --i) + { + if (MatchSubstring(buffer, g_findText, patternLen, i, matchCase)) + { + found = i; + break; + } + } + if (found < 0 && allowWrap) + { + for (int i = textLen - patternLen; i > startPos; --i) + { + if (MatchSubstring(buffer, g_findText, patternLen, i, matchCase)) + { + found = i; + break; + } + } + } + } + + HeapFree(GetProcessHeap(), 0, buffer); + + if (found >= 0) + { + SendMessageW(g_hEdit, EM_SETSEL, (WPARAM)found, (LPARAM)(found + patternLen)); + SendMessageW(g_hEdit, EM_SCROLLCARET, 0, 0); + return true; + } + + MessageBoxW(hwnd, L"Cannot find the specified text.", L"ClassicNotepad", MB_OK | MB_ICONINFORMATION); + return false; +} + +static void ShowFindOrReplaceDialog(HWND hwnd, bool replace) +{ + if (g_hFindDlg) + { + SetFocus(g_hFindDlg); + return; + } + + ZeroMemory(&g_fr, sizeof(g_fr)); + g_fr.lStructSize = sizeof(g_fr); + g_fr.hwndOwner = hwnd; + g_fr.lpstrFindWhat = g_findText; + g_fr.wFindWhatLen = ARRAYSIZE(g_findText); + g_fr.lpstrReplaceWith = g_replaceText; + g_fr.wReplaceWithLen = ARRAYSIZE(g_replaceText); + g_fr.Flags = g_findFlags; + + g_hFindDlg = replace ? ReplaceTextW(&g_fr) : FindTextW(&g_fr); + EnsureFindAllButton(); +} + +static void HandleReplaceCurrent(HWND hwnd) +{ + if (g_findText[0] == L'\0') + { + return; + } + + DWORD selStart = 0; + DWORD selEnd = 0; + SendMessageW(g_hEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd); + int selLen = (int)(selEnd - selStart); + int patternLen = (int)wcslen(g_findText); + + if (selLen == patternLen && selLen > 0) + { + int textLen = GetWindowTextLengthW(g_hEdit); + WCHAR *buffer = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, (textLen + 1) * sizeof(WCHAR)); + if (buffer) + { + GetWindowTextW(g_hEdit, buffer, textLen + 1); + if (MatchSubstring(buffer, g_findText, patternLen, (int)selStart, (g_findFlags & FR_MATCHCASE) != 0)) + { + SendMessageW(g_hEdit, EM_REPLACESEL, TRUE, (LPARAM)g_replaceText); + DWORD newEnd = selStart + (DWORD)wcslen(g_replaceText); + SendMessageW(g_hEdit, EM_SETSEL, selStart, newEnd); + SendMessageW(g_hEdit, EM_SCROLLCARET, 0, 0); + FindAndSelectText(hwnd, g_findFlags, true); + HeapFree(GetProcessHeap(), 0, buffer); + return; + } + HeapFree(GetProcessHeap(), 0, buffer); + } + } + + if (FindAndSelectText(hwnd, g_findFlags, true)) + { + SendMessageW(g_hEdit, EM_REPLACESEL, TRUE, (LPARAM)g_replaceText); + DWORD newEnd = 0; + DWORD newStart = 0; + SendMessageW(g_hEdit, EM_GETSEL, (WPARAM)&newStart, (LPARAM)&newEnd); + SendMessageW(g_hEdit, EM_SCROLLCARET, 0, 0); + FindAndSelectText(hwnd, g_findFlags, true); + } +} + +static void ReplaceAllOccurrences(HWND hwnd) +{ + if (g_findText[0] == L'\0') + { + return; + } + + int findLen = (int)wcslen(g_findText); + int replaceLen = (int)wcslen(g_replaceText); + if (findLen == 0) + { + return; + } + + int textLen = GetWindowTextLengthW(g_hEdit); + WCHAR *buffer = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, (textLen + 1) * sizeof(WCHAR)); + if (!buffer) + { + return; + } + GetWindowTextW(g_hEdit, buffer, textLen + 1); + + bool matchCase = (g_findFlags & FR_MATCHCASE) != 0; + int occurrences = 0; + for (int i = 0; i <= textLen - findLen; ) + { + if (MatchSubstring(buffer, g_findText, findLen, i, matchCase)) + { + occurrences++; + i += findLen; + } + else + { + ++i; + } + } + + if (occurrences == 0) + { + HeapFree(GetProcessHeap(), 0, buffer); + MessageBoxW(hwnd, L"Cannot find the specified text.", L"ClassicNotepad", MB_OK | MB_ICONINFORMATION); + return; + } + + int newLen = textLen + occurrences * (replaceLen - findLen); + if (newLen < 0) + { + HeapFree(GetProcessHeap(), 0, buffer); + return; + } + + WCHAR *outBuffer = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, ((size_t)newLen + 1) * sizeof(WCHAR)); + if (!outBuffer) + { + HeapFree(GetProcessHeap(), 0, buffer); + return; + } + + int outPos = 0; + for (int i = 0; i < textLen; ) + { + if (i <= textLen - findLen && MatchSubstring(buffer, g_findText, findLen, i, matchCase)) + { + memcpy(outBuffer + outPos, g_replaceText, replaceLen * sizeof(WCHAR)); + outPos += replaceLen; + i += findLen; + } + else + { + outBuffer[outPos++] = buffer[i++]; + } + } + outBuffer[outPos] = L'\0'; + + SetWindowTextW(g_hEdit, outBuffer); + SendMessageW(g_hEdit, EM_SETSEL, 0, 0); + + HeapFree(GetProcessHeap(), 0, outBuffer); + HeapFree(GetProcessHeap(), 0, buffer); +} + +static LRESULT CALLBACK FindDialogSubclassProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_COMMAND: + if (LOWORD(wParam) == IDC_FIND_ALL) + { + GetDlgItemTextW(hDlg, edt1, g_findText, ARRAYSIZE(g_findText)); + bool matchCaseChecked = (IsDlgButtonChecked(hDlg, chx1) == BST_CHECKED) || (IsDlgButtonChecked(hDlg, chx2) == BST_CHECKED) || (IsDlgButtonChecked(hDlg, 0x0410) == BST_CHECKED); + g_findFlags = (g_findFlags & ~FR_MATCHCASE) | (matchCaseChecked ? FR_MATCHCASE : 0); + + int count = CountOccurrences((g_findFlags & FR_MATCHCASE) != 0); + SendMessageW(g_hEdit, EM_SETSEL, 0, 0); + FindAndSelectText(GetParent(hDlg), g_findFlags, true); + WCHAR buf[128]; + swprintf_s(buf, ARRAYSIZE(buf), L"Found %d occurrence%s.", count, count == 1 ? L"" : L"s"); + MessageBoxW(hDlg, buf, L"Find All", MB_OK | MB_ICONINFORMATION); + SetFocus(g_hEdit); + return 0; + } + break; + case WM_NCDESTROY: + if (g_findDlgOldProc) + { + SetWindowLongPtrW(hDlg, GWLP_WNDPROC, (LONG_PTR)g_findDlgOldProc); + g_findDlgOldProc = NULL; + } + break; + } + return CallWindowProcW(g_findDlgOldProc ? g_findDlgOldProc : DefWindowProcW, hDlg, msg, wParam, lParam); +} + +static void EnsureFindAllButton(void) +{ + if (!g_hFindDlg) + { + return; + } + + if (g_findDlgOldProc == NULL) + { + g_findDlgOldProc = (WNDPROC)SetWindowLongPtrW(g_hFindDlg, GWLP_WNDPROC, (LONG_PTR)FindDialogSubclassProc); + } + + if (!GetDlgItem(g_hFindDlg, IDC_FIND_ALL)) + { + HWND hFindNext = GetDlgItem(g_hFindDlg, IDOK); + HWND hCancel = GetDlgItem(g_hFindDlg, IDCANCEL); + RECT rcNext = {0}; + RECT rcCancel = {0}; + if (hFindNext) + { + GetWindowRect(hFindNext, &rcNext); + MapWindowPoints(HWND_DESKTOP, g_hFindDlg, (POINT *)&rcNext, 2); + } + if (hCancel) + { + GetWindowRect(hCancel, &rcCancel); + MapWindowPoints(HWND_DESKTOP, g_hFindDlg, (POINT *)&rcCancel, 2); + } + + bool hasCancel = hCancel != NULL; + RECT base = hasCancel ? rcCancel : rcNext; + int btnWidth = base.right - base.left; + int btnHeight = base.bottom - base.top; + int margin = 8; + int x = base.left; + int y = max(rcNext.bottom, rcCancel.bottom) + margin; + + CreateWindowExW(0, L"BUTTON", L"Find All", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, + x, y, btnWidth, btnHeight, + g_hFindDlg, (HMENU)(INT_PTR)IDC_FIND_ALL, GetModuleHandleW(NULL), NULL); + } +} static void ToggleWordWrap(HWND hwnd) { @@ -433,6 +839,46 @@ static void ShowAbout(HWND hwnd) static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if (msg == g_msgFindReplace) + { + LPFINDREPLACEW pfr = (LPFINDREPLACEW)lParam; + if (pfr->Flags & FR_DIALOGTERM) + { + g_hFindDlg = NULL; + } + else + { + if (pfr->lpstrFindWhat) + { + CopyStringSafe(g_findText, ARRAYSIZE(g_findText), pfr->lpstrFindWhat); + } + if (pfr->lpstrReplaceWith) + { + CopyStringSafe(g_replaceText, ARRAYSIZE(g_replaceText), pfr->lpstrReplaceWith); + } + g_findFlags = pfr->Flags & (FR_DOWN | FR_MATCHCASE); + EnsureFindAllButton(); + if (pfr->Flags & FR_FINDNEXT) + { + if (FindAndSelectText(hwnd, g_findFlags, true)) + { + SetFocus(g_hEdit); + } + } + else if (pfr->Flags & FR_REPLACE) + { + HandleReplaceCurrent(hwnd); + SetFocus(g_hEdit); + } + else if (pfr->Flags & FR_REPLACEALL) + { + ReplaceAllOccurrences(hwnd); + SetFocus(g_hEdit); + } + } + return 0; + } + switch (msg) { case WM_CREATE: @@ -477,6 +923,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara case IDM_EDIT_DELETE: SendMessageW(g_hEdit, WM_CLEAR, 0, 0); break; + case IDM_EDIT_FIND: + ShowFindOrReplaceDialog(hwnd, false); + break; + case IDM_EDIT_FIND_NEXT: + if (!FindAndSelectText(hwnd, g_findFlags, true)) + { + ShowFindOrReplaceDialog(hwnd, false); + } + break; + case IDM_EDIT_REPLACE: + ShowFindOrReplaceDialog(hwnd, true); + break; case IDM_EDIT_SELECT: SendMessageW(g_hEdit, EM_SETSEL, 0, -1); break; @@ -521,6 +979,8 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmd } } + g_msgFindReplace = RegisterWindowMessageW(FINDMSGSTRING); + ACCEL accels[] = { {FVIRTKEY | FCONTROL, 'N', IDM_FILE_NEW}, {FVIRTKEY | FCONTROL, 'O', IDM_FILE_OPEN}, @@ -530,6 +990,9 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmd {FVIRTKEY | FCONTROL, 'X', IDM_EDIT_CUT}, {FVIRTKEY | FCONTROL, 'C', IDM_EDIT_COPY}, {FVIRTKEY | FCONTROL, 'V', IDM_EDIT_PASTE}, + {FVIRTKEY | FCONTROL, 'F', IDM_EDIT_FIND}, + {FVIRTKEY, VK_F3, IDM_EDIT_FIND_NEXT}, + {FVIRTKEY | FCONTROL, 'H', IDM_EDIT_REPLACE}, {FVIRTKEY, VK_F5, IDM_EDIT_TIME}, }; g_hAccel = CreateAcceleratorTableW(accels, sizeof(accels) / sizeof(accels[0])); @@ -538,8 +1001,8 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmd wc.cbSize = sizeof(wc); wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; - HICON hIconLarge = LoadIconW(GetModuleHandleW(NULL), MAKEINTRESOURCEW(IDI_APP)); - HICON hIconSmall = LoadIconW(GetModuleHandleW(NULL), MAKEINTRESOURCEW(IDI_APP)); + HICON hIconLarge = (HICON)LoadImageW(hInstance, MAKEINTRESOURCEW(IDI_APP), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); + HICON hIconSmall = (HICON)LoadImageW(hInstance, MAKEINTRESOURCEW(IDI_APP), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); if (!hIconLarge) { hIconLarge = LoadIcon(NULL, IDI_APPLICATION); @@ -561,8 +1024,15 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmd return -1; } + int winWidth = 1600; + int winHeight = 1200; + int screenW = GetSystemMetrics(SM_CXSCREEN); + int screenH = GetSystemMetrics(SM_CYSCREEN); + int posX = (screenW - winWidth) / 2; + int posY = (screenH - winHeight) / 2; + HWND hwnd = CreateWindowExW(0, wc.lpszClassName, L"ClassicNotepad", WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL); + posX, posY, winWidth, winHeight, NULL, NULL, hInstance, NULL); if (!hwnd) { @@ -576,6 +1046,10 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmd MSG msg; while (GetMessageW(&msg, NULL, 0, 0) > 0) { + if (g_hFindDlg && IsDialogMessageW(g_hFindDlg, &msg)) + { + continue; + } if (!TranslateAcceleratorW(hwnd, g_hAccel, &msg)) { TranslateMessage(&msg); diff --git a/ClassicNotepad.iss b/ClassicNotepad.iss index 870658b..0ab8abe 100644 --- a/ClassicNotepad.iss +++ b/ClassicNotepad.iss @@ -1,7 +1,7 @@ [Setup] AppId={{B7CBF4E7-9E8F-4A5B-9B73-7B7E5D5D5E6A}} AppName=ClassicNotepad -AppVersion=1.0.0 +AppVersion=1.1.0 AppPublisher=ClassicNotepad DefaultDirName={autopf}\ClassicNotepad DefaultGroupName=ClassicNotepad